fix(test-curl): keep the socket pass off the environment (#41) #45

Merged
maximus merged 3 commits from issue-41-test-curl-no-proxy into main 2026-10-02 01:25:02 +00:00
Owner

Fixes #41

Corps mis à jour après la revue (commentaire 17831) : il décrit la tête actuelle, trois commits au-dessus de main. Le troisième applique les suggestions non bloquantes de la seconde revue (commentaire 17881).

Ce qui change

test-curl.sh envoyait ses 18 lignes curl au proxy de l'environnement au lieu de ses propres serveurs. Derrière un proxy mort, il sortait en code 7 après 11 s, sans une ligne.

Dans test-curl.sh :

  • export NO_PROXY='*' no_proxy='*' en tête, comme scripts/boot-from-copy.sh.
  • require_free <url>, avant tout lancement : les deux ports doivent refuser la connexion (code curl 7, même règle que boot-from-copy.sh). C'est ce contrôle qui écarte un voisin déjà en place.
  • wait_for <url> <pid> remplace les deux boucles d'attente. Il arrête le script, avec un message sur stderr et le code 1, quand le serveur meurt ou ne répond pas dans les 5 s (borne par horloge). Il vérifie le processus avant et après la sonde.
  • unset FORCE_COLOR : sous cette variable, node -e 'console.log(3)' colore le nombre même dans un tube, et 8 cas échouaient (mesuré sur main avec FORCE_COLOR=3 : 37 passed, 8 failed).
  • Le trap EXIT protège chaque kill par || true et finit par un wait. Sous set -e, un kill en échec sur le premier serveur déjà mort terminait le trap et laissait le second tourner sur 3098.

Dans __tests__/ci.test.js, un describe("test-curl.sh") de 8 cas, qui lance le script en entier :

  1. Derrière un faux proxy qui répond à tout (http_proxy, ALL_PROXY, no_proxy=example.invalid, NO_PROXY retiré, FORCE_COLOR=3) : vivacité du proxy prouvée par un curl nu, puis rien ne l'atteint, sortie 0, 45 passed, 0 failed.
  2. et 3. Un voisin HTTP réel sur 3099, puis sur 3098 : message « is already taken », code 1, aucun cas lancé, le voisin n'a vu que la sonde /, aucun serveur du script laissé sur l'autre port.
  3. et 5. Un occupant non HTTP sur 3099, qui ferme aussitôt (code curl 52 ou 56), puis qui accepte sans répondre (28) : même refus.
  4. Un serveur vivant qui n’écoute jamais sur 3099 : message d’échéance et code 1, entre 4 et 10 s.
  5. et 8. Un faux node en tête de PATH sort en 1 pour le port 3099, puis pour 3098 : message qui nomme l'URL, code 1 en moins de 3 s, aucun cas lancé, ports libérés.

run() rend aussi stdout et la durée. Les cas retirent BASE_URL de l'environnement : vite le pose à / dans le processus de test, et le script lit une variable de ce nom.

Aucun code runtime ne change : pas de redéploiement après le merge.

Réponse à la revue

Le point bloquant était exact : wait_for n'écartait pas un voisin déjà en place, et trois textes disaient le contraire. La garantie est maintenant tenue par require_free, et les trois textes (commentaire du script, CLAUDE.md, ce corps) disent ce que chaque contrôle fait.

Scénario du relecteur rejoué, un serveur HTTP quelconque tenu sur 3099 :

  • avant (096f95d), mesure du relecteur : exit=1, des FAIL: ... got 200, aucun message, les cas tournent contre le voisin ;
  • après : exit=1, stderr test-curl.sh: http://localhost:3099 is already taken (curl exit 0), stdout vide.

Suggestions reprises : borne par horloge au lieu de 50 tours, et borne de durée dans les cas d'échec, qui tue le mutant « exit 1 retiré ». Les ports paramétrables restent hors périmètre.

Preuve par mutation

Mutations de test-curl.sh appliquées depuis une copie de sauvegarde, ci.test.js relancé à chaque fois (30 cas pour a à n, 33 pour o à q), script restauré identique à la copie et git diff --stat inchangé.

Mutation Rouges Cas rouges
aucune 0
a export retiré 1 proxy
b NO_PROXY exporté seul 1 proxy
d unset FORCE_COLOR retiré 1 proxy
e premier kill -0 retiré 2 démarrage raté 3099 et 3098
f exit 1 retiré après « exited before answering » 2 démarrage raté 3099 et 3098 (0 avant ce commit)
g second wait_for retiré 1 démarrage raté 3098
h ancien trap rétabli 3 voisin 3099, démarrage raté 3099 et 3098
i wait retiré du trap 1 voisin 3099
j les deux require_free retirés 2 voisin 3099 et 3098
k require_free de 3098 retiré 1 voisin 3098
l require_free de 3099 retiré 1 voisin 3099
m exit 1 retiré de require_free 1 voisin 3099
n second kill -0 (après la sonde) retiré 0
o port tenu pour pris seulement si curl sort en 0 2 occupant qui ferme, occupant muet (0 avant le troisième commit)
p wait_for rend 0 à l’échéance 1 serveur vivant qui n’écoute jamais (0 avant)
q message et exit 1 d’échéance retirés 1 serveur vivant qui n’écoute jamais (0 avant)

Non épinglés :

  • n : le second kill -0 ne couvre que la fenêtre entre require_free et le listen du serveur, qu'un test ne peut pas ouvrir de façon fiable.
  • L'export de la majuscule NO_PROXY : curl lit la minuscule d'abord, no_proxy seul suffit. Gardée par cohérence avec boot-from-copy.sh.
  • m ne rougit que sur 3099 : avec un voisin sur 3098 et le contrôle neutralisé, le script échoue quand même avant tout cas, par wait_for.

Les mutations h et i touchent le nettoyage en fin de script : leur nombre de rouges dépend de l'ordre des cas et peut varier d'un passage à l'autre, comme le relecteur l'a vu.

Vérification

  • npm test : 363/363 (355 sur main + 8), avec FORCE_COLOR=3 dans l'environnement.
  • bash test-curl.sh : 45 passed, 0 failed.
  • http_proxy=http://127.0.0.1:9 bash test-curl.sh : 45 passed, 0 failed, sortie 0 (critère 1 de l'issue).
  • sh scripts/boot-from-copy.sh : PASS.
  • ci.test.js trois fois de suite : 33/33 à chaque fois, aucun processus restant sur 3099 ou 3098.

Limites

  • Les ports 3099 et 3098 restent fixes, et npm test les occupe maintenant quelques secondes. Deux npm test simultanés, ou un bash test-curl.sh lancé en même temps, se gênent : au moins l’un des deux échoue, parfois les deux (2 tours sur 6 dans la mesure du relecteur), mais jamais un cas ne tourne contre le serveur de l’autre. Ports paramétrables : issue de suivi ouverte.
  • Le nombre 45 est écrit en dur dans le test : ajouter un cas à test-curl.sh impose de le changer là aussi, en plus de CLAUDE.md.
  • La PR #44 est mergée : cette branche est directement au-dessus de main.

🤖 Generated with Claude Code

Fixes #41 Corps mis à jour après la revue (commentaire 17831) : il décrit la tête actuelle, trois commits au-dessus de `main`. Le troisième applique les suggestions non bloquantes de la seconde revue (commentaire 17881). ## Ce qui change `test-curl.sh` envoyait ses 18 lignes `curl` au proxy de l'environnement au lieu de ses propres serveurs. Derrière un proxy mort, il sortait en code 7 après 11 s, sans une ligne. Dans `test-curl.sh` : - `export NO_PROXY='*' no_proxy='*'` en tête, comme `scripts/boot-from-copy.sh`. - `require_free <url>`, avant tout lancement : les deux ports doivent refuser la connexion (code curl 7, même règle que `boot-from-copy.sh`). C'est ce contrôle qui écarte un voisin déjà en place. - `wait_for <url> <pid>` remplace les deux boucles d'attente. Il arrête le script, avec un message sur stderr et le code 1, quand le serveur meurt ou ne répond pas dans les 5 s (borne par horloge). Il vérifie le processus avant et après la sonde. - `unset FORCE_COLOR` : sous cette variable, `node -e 'console.log(3)'` colore le nombre même dans un tube, et 8 cas échouaient (mesuré sur `main` avec `FORCE_COLOR=3` : 37 passed, 8 failed). - Le `trap EXIT` protège chaque `kill` par `|| true` et finit par un `wait`. Sous `set -e`, un `kill` en échec sur le premier serveur déjà mort terminait le trap et laissait le second tourner sur 3098. Dans `__tests__/ci.test.js`, un `describe("test-curl.sh")` de 8 cas, qui lance le script en entier : 1. Derrière un faux proxy qui répond à tout (`http_proxy`, `ALL_PROXY`, `no_proxy=example.invalid`, `NO_PROXY` retiré, `FORCE_COLOR=3`) : vivacité du proxy prouvée par un `curl` nu, puis rien ne l'atteint, sortie 0, `45 passed, 0 failed`. 2. et 3. Un voisin HTTP réel sur 3099, puis sur 3098 : message « is already taken », code 1, aucun cas lancé, le voisin n'a vu que la sonde `/`, aucun serveur du script laissé sur l'autre port. 4. et 5. Un occupant non HTTP sur 3099, qui ferme aussitôt (code curl 52 ou 56), puis qui accepte sans répondre (28) : même refus. 6. Un serveur vivant qui n’écoute jamais sur 3099 : message d’échéance et code 1, entre 4 et 10 s. 7. et 8. Un faux `node` en tête de `PATH` sort en 1 pour le port 3099, puis pour 3098 : message qui nomme l'URL, code 1 en moins de 3 s, aucun cas lancé, ports libérés. `run()` rend aussi stdout et la durée. Les cas retirent `BASE_URL` de l'environnement : vite le pose à `/` dans le processus de test, et le script lit une variable de ce nom. Aucun code runtime ne change : pas de redéploiement après le merge. ## Réponse à la revue Le point bloquant était exact : `wait_for` n'écartait pas un voisin déjà en place, et trois textes disaient le contraire. La garantie est maintenant tenue par `require_free`, et les trois textes (commentaire du script, `CLAUDE.md`, ce corps) disent ce que chaque contrôle fait. Scénario du relecteur rejoué, un serveur HTTP quelconque tenu sur 3099 : - avant (`096f95d`), mesure du relecteur : `exit=1`, des `FAIL: ... got 200`, aucun message, les cas tournent contre le voisin ; - après : `exit=1`, stderr `test-curl.sh: http://localhost:3099 is already taken (curl exit 0)`, stdout vide. Suggestions reprises : borne par horloge au lieu de 50 tours, et borne de durée dans les cas d'échec, qui tue le mutant « `exit 1` retiré ». Les ports paramétrables restent hors périmètre. ## Preuve par mutation Mutations de `test-curl.sh` appliquées depuis une copie de sauvegarde, `ci.test.js` relancé à chaque fois (30 cas pour a à n, 33 pour o à q), script restauré identique à la copie et `git diff --stat` inchangé. | | Mutation | Rouges | Cas rouges | |---|---|---|---| | aucune | | 0 | | | a | export retiré | 1 | proxy | | b | `NO_PROXY` exporté seul | 1 | proxy | | d | `unset FORCE_COLOR` retiré | 1 | proxy | | e | premier `kill -0` retiré | 2 | démarrage raté 3099 et 3098 | | f | `exit 1` retiré après « exited before answering » | 2 | démarrage raté 3099 et 3098 (0 avant ce commit) | | g | second `wait_for` retiré | 1 | démarrage raté 3098 | | h | ancien `trap` rétabli | 3 | voisin 3099, démarrage raté 3099 et 3098 | | i | `wait` retiré du `trap` | 1 | voisin 3099 | | j | les deux `require_free` retirés | 2 | voisin 3099 et 3098 | | k | `require_free` de 3098 retiré | 1 | voisin 3098 | | l | `require_free` de 3099 retiré | 1 | voisin 3099 | | m | `exit 1` retiré de `require_free` | 1 | voisin 3099 | | n | second `kill -0` (après la sonde) retiré | **0** | | | o | port tenu pour pris seulement si curl sort en 0 | 2 | occupant qui ferme, occupant muet (0 avant le troisième commit) | | p | `wait_for` rend 0 à l’échéance | 1 | serveur vivant qui n’écoute jamais (0 avant) | | q | message et `exit 1` d’échéance retirés | 1 | serveur vivant qui n’écoute jamais (0 avant) | Non épinglés : - **n** : le second `kill -0` ne couvre que la fenêtre entre `require_free` et le `listen` du serveur, qu'un test ne peut pas ouvrir de façon fiable. - L'export de la majuscule `NO_PROXY` : curl lit la minuscule d'abord, `no_proxy` seul suffit. Gardée par cohérence avec `boot-from-copy.sh`. - **m** ne rougit que sur 3099 : avec un voisin sur 3098 et le contrôle neutralisé, le script échoue quand même avant tout cas, par `wait_for`. Les mutations h et i touchent le nettoyage en fin de script : leur nombre de rouges dépend de l'ordre des cas et peut varier d'un passage à l'autre, comme le relecteur l'a vu. ## Vérification - `npm test` : 363/363 (355 sur `main` + 8), avec `FORCE_COLOR=3` dans l'environnement. - `bash test-curl.sh` : 45 passed, 0 failed. - `http_proxy=http://127.0.0.1:9 bash test-curl.sh` : 45 passed, 0 failed, sortie 0 (critère 1 de l'issue). - `sh scripts/boot-from-copy.sh` : PASS. - `ci.test.js` trois fois de suite : 33/33 à chaque fois, aucun processus restant sur 3099 ou 3098. ## Limites - Les ports 3099 et 3098 restent fixes, et `npm test` les occupe maintenant quelques secondes. Deux `npm test` simultanés, ou un `bash test-curl.sh` lancé en même temps, se gênent : au moins l’un des deux échoue, parfois les deux (2 tours sur 6 dans la mesure du relecteur), mais jamais un cas ne tourne contre le serveur de l’autre. Ports paramétrables : issue de suivi ouverte. - Le nombre `45` est écrit en dur dans le test : ajouter un cas à `test-curl.sh` impose de le changer là aussi, en plus de `CLAUDE.md`. - La PR #44 est mergée : cette branche est directement au-dessus de `main`. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
maximus added 1 commit 2026-10-02 00:12:59 +00:00
fix(test-curl): keep the socket pass off the environment (#41)
All checks were successful
CI / verification (pull_request) Successful in 21s
7003197abe
Behind a proxy from the environment, test-curl.sh sent its 18 curl lines to
the proxy instead of the servers it boots: with a dead proxy it exited 7
after 11 s without a line of output.

- export NO_PROXY and no_proxy, as scripts/boot-from-copy.sh does
- unset FORCE_COLOR: node colours a number even into a pipe, and 8 cases
  compared "3" against a coloured "3"
- wait_for checks the server process before the port and stops the run with
  a message when a server died or never answered
- the EXIT trap guards each kill: under set -e, a failed kill on the first
  server ended the trap and left the second one running on 3098

ci.test.js runs the whole script three times: behind a fake proxy that
answers everything, and with a fake node that fails for each port in turn.

Fixes #41

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
maximus changed target branch from main to issue-40-count-every-write 2026-10-02 00:13:36 +00:00
maximus force-pushed issue-41-test-curl-no-proxy from 7003197abe to 096f95da05 2026-10-02 00:13:37 +00:00 Compare
Author
Owner

Verdict : REQUEST_CHANGES

Résumé. Le correctif tient ses critères d'acceptation et ses épingles rougissent sous mes propres mutations. Un point bloque : la garantie « un voisin qui répond sur le même port ne passe pas pour le serveur » est écrite à trois endroits et elle est fausse dans le cas courant, mesure à l'appui.

Suite et mutations rejouées par le relecteur sur une copie hors dépôt de la tête 096f95d (git archive), ports 3097 à 3099 vérifiés libres avant et après.

Problème bloquant

  1. test-curl.sh:177-185 : wait_for n'écarte pas un voisin déjà en place. kill -0 passe au premier tour, parce que le node lancé par le script n'est pas encore mort de son EADDRINUSE ; le curl qui suit joint le voisin et wait_for rend 0. Le contrôle du processus n'attrape que le serveur mort avant le premier tour, ce que le faux node des tests produit (sortie immédiate), pas une collision de port réelle.

    Mesuré : un serveur HTTP quelconque tenu sur 3099 et 3098, puis bash test-curl.sh :

    exit=1
    FAIL: no-auth -> got 200
    FAIL: wrong-token -> got 200
    ...
    SyntaxError: Unexpected token 'e', "neighbour
    

    Aucun message de wait_for : les cas tournent contre le voisin. C'est le scénario de la revue parallèle (deux npm test, ou un bash test-curl.sh pendant npm test), où le voisin est un autre index.js, peut-être d'une autre branche, qui rend des réponses crédibles.

    La garantie est affirmée dans :

    • le commentaire test-curl.sh:177-178 (« so that a neighbour answering on the same port cannot stand in ») ;
    • CLAUDE.md, paragraphe bash test-curl.sh (« un voisin qui repond sur le meme port ne passe pas pour lui ») ;
    • le corps de la PR, section « Ce qui change » et section « Limites » (« L'échec est lisible (message de wait_for) »).

    Deux sorties possibles, au choix :

    • corriger les trois textes : wait_for détecte un serveur mort avant de répondre, pas un port déjà pris ; une collision reste un échec sans message dédié. Suffit pour cette PR, l'issue #41 n'exigeant pas la détection du voisin ;
    • tenir la garantie : contrôle du port avant le lancement, comme boot-from-copy.sh (tout code curl autre que 7 = refus), avec un cas de test à voisin réel. Un second kill -0 après le curl réussi réduirait la fenêtre sans la fermer.

Vérifié

  • npm test : 358/358. bash test-curl.sh : 45 passed, 0 failed. http_proxy=http://127.0.0.1:9 bash test-curl.sh : 45 passed, 0 failed (critère 1). sh scripts/boot-from-copy.sh : PASS. Mon environnement porte FORCE_COLOR=3.
  • CI de la tête : success (statut de commit).
  • Timeline de #41 : pull_ref de #45 en closes.
  • Mutations du relecteur sur test-curl.sh, ci.test.js relancé (28 cas), script restauré identique (cmp) :
Mutation Rouges
export retiré 1
NO_PROXY exporté seul 1
unset FORCE_COLOR retiré 1
contrôle kill -0 neutralisé 2
ancien trap rétabli 2, puis 1 sur un autre passage

Conforme au tableau de la PR, sauf la dernière ligne : le nombre de rouges varie d'un passage à l'autre (au moins 1 à chaque fois), et la mutation laisse bien un node ./index.js orphelin sur 3098, ce qui confirme le défaut du trap.

Suggestions (non bloquantes)

  • --max-time 1 par tour porte le pire cas à environ 55 s (50 × 1,1 s) si un occupant accepte la connexion sans répondre, alors que le message annonce « after 5 s ». Borne par horloge (SECONDS) ou message sans durée.
  • Le mutant équivalent « exit 1 retiré » serait tué par une borne de durée dans les deux cas d'échec (moins de 3 s, par exemple).
  • Ports fixes pendant npm test : rendre PORT paramétrable mérite une issue à part, le risque étant désormais présent à chaque npm test et plus seulement dans l'étape CI.
## Verdict : REQUEST_CHANGES **Résumé.** Le correctif tient ses critères d'acceptation et ses épingles rougissent sous mes propres mutations. Un point bloque : la garantie « un voisin qui répond sur le même port ne passe pas pour le serveur » est écrite à trois endroits et elle est fausse dans le cas courant, mesure à l'appui. Suite et mutations rejouées par le relecteur sur une copie hors dépôt de la tête `096f95d` (`git archive`), ports 3097 à 3099 vérifiés libres avant et après. ### Problème bloquant 1. **`test-curl.sh:177-185` : `wait_for` n'écarte pas un voisin déjà en place.** `kill -0` passe au premier tour, parce que le `node` lancé par le script n'est pas encore mort de son `EADDRINUSE` ; le `curl` qui suit joint le voisin et `wait_for` rend 0. Le contrôle du processus n'attrape que le serveur mort *avant* le premier tour, ce que le faux `node` des tests produit (sortie immédiate), pas une collision de port réelle. Mesuré : un serveur HTTP quelconque tenu sur 3099 et 3098, puis `bash test-curl.sh` : ``` exit=1 FAIL: no-auth -> got 200 FAIL: wrong-token -> got 200 ... SyntaxError: Unexpected token 'e', "neighbour ``` Aucun message de `wait_for` : les cas tournent contre le voisin. C'est le scénario de la revue parallèle (deux `npm test`, ou un `bash test-curl.sh` pendant `npm test`), où le voisin est un autre `index.js`, peut-être d'une autre branche, qui rend des réponses crédibles. La garantie est affirmée dans : - le commentaire `test-curl.sh:177-178` (« so that a neighbour answering on the same port cannot stand in ») ; - `CLAUDE.md`, paragraphe `bash test-curl.sh` (« un voisin qui repond sur le meme port ne passe pas pour lui ») ; - le corps de la PR, section « Ce qui change » et section « Limites » (« L'échec est lisible (message de `wait_for`) »). Deux sorties possibles, au choix : - **corriger les trois textes** : `wait_for` détecte un serveur mort avant de répondre, pas un port déjà pris ; une collision reste un échec sans message dédié. Suffit pour cette PR, l'issue #41 n'exigeant pas la détection du voisin ; - **tenir la garantie** : contrôle du port avant le lancement, comme `boot-from-copy.sh` (tout code curl autre que 7 = refus), avec un cas de test à voisin réel. Un second `kill -0` après le `curl` réussi réduirait la fenêtre sans la fermer. ### Vérifié - `npm test` : 358/358. `bash test-curl.sh` : 45 passed, 0 failed. `http_proxy=http://127.0.0.1:9 bash test-curl.sh` : 45 passed, 0 failed (critère 1). `sh scripts/boot-from-copy.sh` : PASS. Mon environnement porte `FORCE_COLOR=3`. - CI de la tête : `success` (statut de commit). - Timeline de #41 : `pull_ref` de #45 en `closes`. - Mutations du relecteur sur `test-curl.sh`, `ci.test.js` relancé (28 cas), script restauré identique (`cmp`) : | Mutation | Rouges | |---|---| | export retiré | 1 | | `NO_PROXY` exporté seul | 1 | | `unset FORCE_COLOR` retiré | 1 | | contrôle `kill -0` neutralisé | 2 | | ancien `trap` rétabli | 2, puis 1 sur un autre passage | Conforme au tableau de la PR, sauf la dernière ligne : le nombre de rouges varie d'un passage à l'autre (au moins 1 à chaque fois), et la mutation laisse bien un `node ./index.js` orphelin sur 3098, ce qui confirme le défaut du `trap`. ### Suggestions (non bloquantes) - `--max-time 1` par tour porte le pire cas à environ 55 s (50 × 1,1 s) si un occupant accepte la connexion sans répondre, alors que le message annonce « after 5 s ». Borne par horloge (`SECONDS`) ou message sans durée. - Le mutant équivalent « `exit 1` retiré » serait tué par une borne de durée dans les deux cas d'échec (moins de 3 s, par exemple). - Ports fixes pendant `npm test` : rendre `PORT` paramétrable mérite une issue à part, le risque étant désormais présent à chaque `npm test` et plus seulement dans l'étape CI.
maximus changed target branch from issue-40-count-every-write to main 2026-10-02 00:46:11 +00:00
Author
Owner

Correctif après la revue 17831, poussé sur la branche. Tête non relue.

Le point bloquant est traité par la seconde sortie proposée : require_free contrôle les deux ports avant tout lancement (code curl 7 exigé, comme boot-from-copy.sh), avec un cas de test à voisin réel par port.

Scénario de la revue, voisin HTTP sur 3099 :

Sortie stderr Cas lancés
avant (096f95d) 1 aucun message oui, contre le voisin
après 1 http://localhost:3099 is already taken (curl exit 0) aucun

Mutation correspondante, les deux require_free retirés : 2 rouges sur 30 (les deux cas à voisin). Suite sans mutation : 0 rouge avant, 0 après restauration.

Suggestions reprises : wait_for borné par horloge, et une borne de 3 s dans les cas de démarrage raté, qui fait passer le mutant « exit 1 retiré » de 0 à 2 rouges. Le trap attend maintenant la fin des serveurs qu'il tue. Tableau complet des 13 mutations dans le corps de la PR, mis à jour. Reste non épinglé : le second kill -0 de wait_for.

npm test 360/360, test-curl.sh 45/45 (aussi derrière un proxy mort), boot check PASS.

Correctif après la revue 17831, poussé sur la branche. Tête non relue. Le point bloquant est traité par la seconde sortie proposée : `require_free` contrôle les deux ports avant tout lancement (code curl 7 exigé, comme `boot-from-copy.sh`), avec un cas de test à voisin réel par port. Scénario de la revue, voisin HTTP sur 3099 : | | Sortie | stderr | Cas lancés | |---|---|---|---| | avant (`096f95d`) | 1 | aucun message | oui, contre le voisin | | après | 1 | `http://localhost:3099 is already taken (curl exit 0)` | aucun | Mutation correspondante, les deux `require_free` retirés : 2 rouges sur 30 (les deux cas à voisin). Suite sans mutation : 0 rouge avant, 0 après restauration. Suggestions reprises : `wait_for` borné par horloge, et une borne de 3 s dans les cas de démarrage raté, qui fait passer le mutant « `exit 1` retiré » de 0 à 2 rouges. Le `trap` attend maintenant la fin des serveurs qu'il tue. Tableau complet des 13 mutations dans le corps de la PR, mis à jour. Reste non épinglé : le second `kill -0` de `wait_for`. `npm test` 360/360, `test-curl.sh` 45/45 (aussi derrière un proxy mort), boot check PASS.
maximus added 1 commit 2026-10-02 00:51:45 +00:00
fix(test-curl): refuse a port already taken before starting (#41)
All checks were successful
CI / verification (pull_request) Successful in 21s
193635a68e
Review of PR #45: wait_for did not keep a neighbour out. With a listener
already on 3099, the server started by the script only dies of EADDRINUSE
after the wait loop has reached the neighbour, and the cases then ran
against it.

- require_free checks both ports before anything starts: free means
  "connection refused" (curl exit 7), as in scripts/boot-from-copy.sh
- wait_for runs on a clock deadline, and checks the process again after a
  successful probe
- the EXIT trap waits for the servers it killed, so a run started right
  after finds the two ports free

ci.test.js adds a real neighbour on each port, and bounds the two
failed-start cases to 3 s: a message that does not stop the run is now red.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Author
Owner

Verdict : APPROVE

Résumé. Le point bloquant de la revue 17831 est levé : require_free écarte le voisin avant tout lancement, et les trois textes disent maintenant ce que chaque contrôle fait. Les épingles rougissent sous mes propres mutations ; deux comportements restent sans épingle et ne figurent pas dans la liste « Non épinglés », sans bloquer le merge.

Suite et mutations rejouées par le relecteur sur une copie hors dépôt de la tête 193635a (git archive), ports 3097 à 3099 vérifiés libres avant et après, FORCE_COLOR présent dans mon environnement.

Vérifié

  • npm test : 360/360. bash test-curl.sh : 45 passed, 0 failed. http_proxy=http://127.0.0.1:9 bash test-curl.sh : 45 passed, 0 failed (critère 1 de l'issue). sh scripts/boot-from-copy.sh : PASS.
  • CI de la tête 193635a : success (statut de commit).
  • Timeline de #41 : pull_ref de #45 en closes. Deux commits au format conventionnel.
  • Scénario bloquant rejoué, au-delà du voisin HTTP des tests :
Occupant Sortie stderr Cas lancés
écouteur TCP qui accepte et ne répond pas, sur 3099 1 en 2,0 s is already taken (curl exit 28) aucun
écouteur qui ferme aussitôt, sur 3098 1 is already taken (curl exit 56) aucun
faux node vivant qui n'écoute jamais (3099) 1 en 4,3 s no server answered at http://localhost:3099 within 5 s aucun
  • Deux bash test-curl.sh lancés au même instant, 6 tours : jamais un cas lancé contre le serveur de l'autre. 4 tours avec un gagnant (45/45) et un perdant, 2 tours où les deux échouent (chacun perd un port). Le message est celui de wait_for (« exited before answering »), la course se jouant après require_free.

  • Mutations du relecteur sur test-curl.sh, ci.test.js relancé à chaque fois (30 cas), script restauré identique (cmp), 30/30 avant et après :

Mutation Rouges Cas rouges
export retiré 1 proxy
les deux require_free retirés 2 voisin 3099 et 3098
exit 1 retiré après « exited before answering » 2 démarrage raté 3099 et 3098
wait retiré du trap 1 voisin 3099
ancien trap rétabli 3 voisin 3099, démarrage raté 3099 et 3098
require_free : pris seulement si code curl 0 (au lieu de « tout sauf 7 ») 0
wait_for : return 0 au lieu de exit 1 à l'échéance 0
wait_for : message et exit 1 d'échéance supprimés 0

Les cinq premières lignes confirment le tableau de la PR. L'ancien trap laisse bien un node ./index.js orphelin sur 3098.

Suggestions (non bloquantes)

  1. Deux survivants absents de la liste « Non épinglés » (corps de la PR et CLAUDE.md) :
    • test-curl.sh:64, la règle « tout code autre que 7 = pris ». Les deux cas à voisin n'exercent que le code 0. Le commentaire test-curl.sh:58-60 promet 28, 52 et 56 ; le comportement est correct (mesures ci-dessus), mais rien ne le fige. Un voisin net.createServer(() => {}) l'épinglerait, pour 2 s de plus.
    • test-curl.sh:216-217, la sortie à l'échéance des 5 s. Le faux node des tests meurt aussitôt et ne passe que par la première branche. Un faux node qui fait exec sleep l'épinglerait, pour environ 5 s de plus. À défaut, ajouter les deux à la liste des non épinglés.
  2. CLAUDE.md, paragraphe bash test-curl.sh : « Trois protections » en annonce trois et en liste quatre (export, unset FORCE_COLOR, require_free, wait_for).
  3. test-curl.sh:47-53 : le bloc de commentaire « Fixtures : » se retrouve au-dessus de require_free (l. 54-70), séparé des mkdir qu'il décrit (l. 72). Déplacer require_free au-dessus du commentaire.
  4. Section « Limites » : « l'un des deux échoue » est optimiste, les deux échouent dans 2 tours sur 6. L'échec reste franc et nommé.
  5. Les ports fixes sont maintenant occupés à chaque npm test : l'issue séparée pour les rendre paramétrables reste à ouvrir.
## Verdict : APPROVE **Résumé.** Le point bloquant de la revue 17831 est levé : `require_free` écarte le voisin avant tout lancement, et les trois textes disent maintenant ce que chaque contrôle fait. Les épingles rougissent sous mes propres mutations ; deux comportements restent sans épingle et ne figurent pas dans la liste « Non épinglés », sans bloquer le merge. Suite et mutations rejouées par le relecteur sur une copie hors dépôt de la tête `193635a` (`git archive`), ports 3097 à 3099 vérifiés libres avant et après, `FORCE_COLOR` présent dans mon environnement. ### Vérifié - `npm test` : 360/360. `bash test-curl.sh` : 45 passed, 0 failed. `http_proxy=http://127.0.0.1:9 bash test-curl.sh` : 45 passed, 0 failed (critère 1 de l'issue). `sh scripts/boot-from-copy.sh` : PASS. - CI de la tête `193635a` : `success` (statut de commit). - Timeline de #41 : `pull_ref` de #45 en `closes`. Deux commits au format conventionnel. - Scénario bloquant rejoué, au-delà du voisin HTTP des tests : | Occupant | Sortie | stderr | Cas lancés | |---|---|---|---| | écouteur TCP qui accepte et ne répond pas, sur 3099 | 1 en 2,0 s | `is already taken (curl exit 28)` | aucun | | écouteur qui ferme aussitôt, sur 3098 | 1 | `is already taken (curl exit 56)` | aucun | | faux `node` vivant qui n'écoute jamais (3099) | 1 en 4,3 s | `no server answered at http://localhost:3099 within 5 s` | aucun | - Deux `bash test-curl.sh` lancés au même instant, 6 tours : jamais un cas lancé contre le serveur de l'autre. 4 tours avec un gagnant (45/45) et un perdant, 2 tours où les deux échouent (chacun perd un port). Le message est celui de `wait_for` (« exited before answering »), la course se jouant après `require_free`. - Mutations du relecteur sur `test-curl.sh`, `ci.test.js` relancé à chaque fois (30 cas), script restauré identique (`cmp`), 30/30 avant et après : | Mutation | Rouges | Cas rouges | |---|---|---| | export retiré | 1 | proxy | | les deux `require_free` retirés | 2 | voisin 3099 et 3098 | | `exit 1` retiré après « exited before answering » | 2 | démarrage raté 3099 et 3098 | | `wait` retiré du `trap` | 1 | voisin 3099 | | ancien `trap` rétabli | 3 | voisin 3099, démarrage raté 3099 et 3098 | | `require_free` : pris seulement si code curl 0 (au lieu de « tout sauf 7 ») | **0** | | | `wait_for` : `return 0` au lieu de `exit 1` à l'échéance | **0** | | | `wait_for` : message et `exit 1` d'échéance supprimés | **0** | | Les cinq premières lignes confirment le tableau de la PR. L'ancien `trap` laisse bien un `node ./index.js` orphelin sur 3098. ### Suggestions (non bloquantes) 1. **Deux survivants absents de la liste « Non épinglés »** (corps de la PR et `CLAUDE.md`) : - `test-curl.sh:64`, la règle « tout code autre que 7 = pris ». Les deux cas à voisin n'exercent que le code 0. Le commentaire `test-curl.sh:58-60` promet 28, 52 et 56 ; le comportement est correct (mesures ci-dessus), mais rien ne le fige. Un voisin `net.createServer(() => {})` l'épinglerait, pour 2 s de plus. - `test-curl.sh:216-217`, la sortie à l'échéance des 5 s. Le faux `node` des tests meurt aussitôt et ne passe que par la première branche. Un faux `node` qui fait `exec sleep` l'épinglerait, pour environ 5 s de plus. À défaut, ajouter les deux à la liste des non épinglés. 2. `CLAUDE.md`, paragraphe `bash test-curl.sh` : « Trois protections » en annonce trois et en liste quatre (export, `unset FORCE_COLOR`, `require_free`, `wait_for`). 3. `test-curl.sh:47-53` : le bloc de commentaire « Fixtures : » se retrouve au-dessus de `require_free` (l. 54-70), séparé des `mkdir` qu'il décrit (l. 72). Déplacer `require_free` au-dessus du commentaire. 4. Section « Limites » : « l'un des deux échoue » est optimiste, les deux échouent dans 2 tours sur 6. L'échec reste franc et nommé. 5. Les ports fixes sont maintenant occupés à chaque `npm test` : l'issue séparée pour les rendre paramétrables reste à ouvrir.
maximus added 1 commit 2026-10-02 01:18:27 +00:00
test(ci): pin the non-HTTP occupant and the wait deadline of test-curl.sh (#41)
All checks were successful
CI / verification (pull_request) Successful in 27s
CI / verification (push) Successful in 25s
7427577053
Second review of PR #45, non-blocking: two behaviours were right by hand
and held by no test.

- a listener on 3099 that closes at once, then one that accepts and stalls:
  the port rule is "anything but connection refused", not "an HTTP answer"
- a server that stays alive and never listens: the wait ends on its
  deadline, with a message and exit 1

Also moves the port check above the "Fixtures" comment it had split from
its mkdir lines, and counts four protections in CLAUDE.md, not three.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Author
Owner

Suggestions de la revue 17881 appliquées, à la demande de Max, puis merge. Le commit 7427577 est ajouté après l'APPROVE, qui portait sur 193635a : il n'a pas été relu.

  • Trois cas de plus dans ci.test.js (33 au total) : occupant de 3099 qui ferme aussitôt, occupant qui accepte sans répondre, serveur vivant qui n'écoute jamais.
  • Les trois mutants verts de la revue rougissent : port tenu pour pris seulement sur le code 0 (0 avant, 2 après), wait_for qui rend 0 à l'échéance (0, puis 1), message et exit 1 d'échéance retirés (0, puis 1). Suite sans mutation : 0 rouge, avant comme après restauration.
  • CLAUDE.md compte quatre protections ; le commentaire « Fixtures » retrouve ses mkdir ; la section « Limites » du corps dit que les deux passages simultanés peuvent échouer tous les deux.
  • Ports paramétrables : issue de suivi ouverte.

npm test 363/363, test-curl.sh 45/45 (aussi derrière un proxy mort), boot check PASS.

Suggestions de la revue 17881 appliquées, à la demande de Max, puis merge. Le commit `7427577` est ajouté après l'APPROVE, qui portait sur `193635a` : il n'a pas été relu. - Trois cas de plus dans `ci.test.js` (33 au total) : occupant de 3099 qui ferme aussitôt, occupant qui accepte sans répondre, serveur vivant qui n'écoute jamais. - Les trois mutants verts de la revue rougissent : port tenu pour pris seulement sur le code 0 (0 avant, 2 après), `wait_for` qui rend 0 à l'échéance (0, puis 1), message et `exit 1` d'échéance retirés (0, puis 1). Suite sans mutation : 0 rouge, avant comme après restauration. - `CLAUDE.md` compte quatre protections ; le commentaire « Fixtures » retrouve ses `mkdir` ; la section « Limites » du corps dit que les deux passages simultanés peuvent échouer tous les deux. - Ports paramétrables : issue de suivi ouverte. `npm test` 363/363, `test-curl.sh` 45/45 (aussi derrière un proxy mort), boot check PASS.
maximus merged commit 7427577053 into main 2026-10-02 01:25:02 +00:00
maximus deleted branch issue-41-test-curl-no-proxy 2026-10-02 01:25:02 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: maximus/vps-health-api#45
No description provided.