Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
8.3 KiB
id, title, status, type, domain, scope, priority, depends, blocks, related, created, updated, tags
| id | title | status | type | domain | scope | priority | depends | blocks | related | created | updated | tags | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0167 | fn run de library function Go ejecuta go test del paquete entero (arrastra tests flaky vecinos) | completado | enhancement |
|
registry-only | media |
|
2026-06-03 | 2026-06-03 |
|
0167 — fn run de library function Go ejecuta go test del paquete entero
APP Metadata
| Campo | Valor |
|---|---|
| ID | 0167 |
| Estado | pendiente |
| Prioridad | media |
| Tipo | enhancement — dispatcher de fn run |
Contexto
Cuando fn run <id> recibe una library function Go sin main.go que tiene tests
declarados (tested: true + test_file_path), el dispatcher (cmd/fn/run.go:171-181)
ejecuta:
go test -v -count=1 -tags fts5 ./functions/<domain> # el PAQUETE ENTERO
Es decir, no ejecuta "la función" (no se puede: no tiene main), sino que corre todos
los tests del paquete. Consecuencia: el éxito de fn run miFuncion depende de que pasen
los tests de todas las demás funciones del mismo paquete, no solo los suyos.
Cómo se manifestó
Los DAGs daily-registry-audit y weekly-deep-scan del dag_engine invocaban funciones
*_go_infra (find_unused_functions, artefact_doctor, etc.) como function: steps.
Cada step disparaba go test ./functions/infra (paquete completo), que contiene tests
impuros con recursos fijos:
TestSSHTunnelOpenClose→bind [127.0.0.1]:19876: Address already in useTestDockerContainerExec→listen unix .../docker_exec_test.sock: bind: invalid argument(path de socket > 108 chars con TMPDIR largo)
Al correr dos function: steps en paralelo (ambos depends del mismo padre), las dos
invocaciones de go test ./functions/infra colisionaban en el puerto fijo 19876 →
una pasaba y la otra fallaba de forma no determinista. Resultado: el DAG fallaba sin
auditar nada, y el fallo parecía "la auditoría encontró un problema" cuando en realidad
era un test de red vecino.
Nota: el síntoma operativo en los DAGs ya se resolvió por otra vía (2026-06-03): los steps ahora usan
audit_doctor_snapshot_bash_infra(Bash), que ejecutafn doctor <sub>real en vez dego testdel paquete. Este issue es la causa raíz general del dispatcher, que sigue afectando a cualquierfn run <library_go_fn_con_tests>.
Problema
fn runde una library function NO ejecuta la función — corre el paquete de test entero.- Los tests impuros de un paquete (puertos/sockets/red fijos) no son seguros para ejecuciones concurrentes ni reproducibles en cualquier entorno (TMPDIR, CI).
- Un único test flaky en
functions/infrarompefn runde las ~N funciones testeadas del paquete, y por extensión cualquier DAG/cron que las invoque.
Opciones de solución (decidir en implementación)
Opción A — library Go sin main → siempre compile-check (go vet/go build)
fn run <lib_fn> significa "verifica que la función va"; para código sin main eso es
"compila". Testear es responsabilidad de go test / CI, no de fn run en un cron.
- Pro: determinista, rápido, elimina el flaky de raíz.
- Contra: rompe el comportamiento documentado en
CLAUDE.md("fn run filter_slice_go_core→ Go function con tests →go test -v"). Perderíamos la capacidad de correr los tests de una función víafn run.
Opción B — go test acotado con -run a los tests de la función
Si la función declara sus tests, ejecutar solo esos:
go test -v -count=1 -tags fts5 -run '^(TestX|TestY)$' ./functions/<domain>
- Pro: aísla del flaky vecino manteniendo "fn run corre mis tests".
- Contra / RIESGO: si los nombres de
fn.Tests(frontmatter YAML,registry/parser.go:32) tienen drift respecto al código,-runno matchea ygo testsale 0 con "no tests to run" → falso-verde en una primitiva crítica de todo el ecosistema. Mitigación obligatoria si se elige B: reconciliarfn.Testscon los tests extraídos por el indexer (registry/test_parser.go::parseGoTests, que ya pueblaunit_tests) y/o detectar "0 tests ejecutados" parseando el output y tratarlo como fallo.
Opción C — aislar los tests impuros del paquete
Hacer robustos los tests culpables: puerto efímero (:0 en vez de 19876), socket en path
corto bajo /tmp con nombre acotado, t.Parallel-safe. No cambia el dispatcher pero reduce
la probabilidad de colisión.
- Pro: no toca
fn run(cero blast radius sistémico). - Contra: no resuelve el problema conceptual (sigue corriendo el paquete entero); otros paquetes pueden introducir tests impuros nuevos y reincidir.
Recomendación
Combinar C (saneamiento inmediato de TestSSHTunnelOpenClose y TestDockerContainerExec,
bajo riesgo) con B endurecida (acotar -run + guard anti-falso-verde apoyado en
unit_tests extraídos, no en el frontmatter manual). La Opción A es la más limpia
conceptualmente pero rompe comportamiento documentado; evaluar si ese comportamiento
("fn run corre los tests") aún se usa de verdad o puede deprecarse hacia go test directo.
Definition of Done
| Escenario | Tipo | Comando / evidencia | Resultado esperado |
|---|---|---|---|
Golden: fn run de library fn testeada |
e2e | ./fn run find_unused_functions_go_infra |
exit 0 sin depender de tests de funciones vecinas |
Edge: dos fn run concurrentes del mismo paquete |
e2e | dos invocaciones en paralelo de funciones de functions/infra |
ambas exit 0, sin colisión de puerto/socket |
| Error: nombres de test con drift (si se elige B) | unit | fn.Tests con un nombre inexistente |
NO produce falso-verde (se detecta "0 tests run" → fallo) |
| Tests impuros saneados | unit | go test -run 'TestSSHTunnelOpenClose|TestDockerContainerExec' ./functions/infra repetido 5× |
5/5 PASS deterministas |
Resolución (2026-06-03)
Implementada la combinación C + B recomendada.
C — Tests impuros saneados (functions/infra/)
ssh_tunnel_test.go: el puerto fijo19876pasa a puerto efímero (freeTCPPortpide:0al kernel). Elimina elbind: address already in usebajo concurrencia.docker_container_exec_test.go: el socket Unix deja de colgar det.TempDir()(path largo con el nombre del subtest) y usa un directorio corto bajo/tmp(os.MkdirTemp("/tmp", "dk")+ cleanup). Elimina elbind: invalid argumentpor exceder los ~108 bytes desun_path.- Verificado:
go test -run 'TestSSHTunnelOpenClose|TestDockerContainerExec' -count=5 ./functions/infra/→ok(5×, determinista).
B — fn run acota los tests a la función (cmd/fn/run.go)
- Para una library Go function con tests, el dispatcher ahora añade
-run '^(<tests>)$'con los nombres extraídos por el indexer (unit_tests, víadb.GetUnitTestsByFunction), no los del frontmatter.md(que pueden driftar). Asífn runejecuta solo los tests de esa función, aislándola de tests flaky de funciones vecinas del mismo paquete. Si no hay nombres extraídos, cae al paquete entero (comportamiento previo). - Guard anti-falso-verde:
cmdRunrefleja el output de ungo test -runa un buffer; si go test reportano tests to run(que sale con exit 0), el run se trata como fallo (exit 1 + mensaje pidiendofn index). Evita que un drift de nombres produzca un verde silencioso.
Evidencia (DoD)
| Escenario | Resultado |
|---|---|
Golden: fn run find_unused_functions_go_infra |
Corre solo sus 2 tests (TestFindUnusedFunctions_*) en 0.06s, exit 0. No toca SSH/Docker. |
Edge concurrente: 2 fn run del paquete infra en paralelo |
Ambos exit 0, sin colisión de puerto. |
Error/drift: unit_tests con nombre inexistente |
go test da [no tests to run]; el guard lo intercepta → exit 1 con mensaje. NO falso-verde. |
| Tests saneados 5× | ok determinista. |
go vet ./cmd/fn/ y go test ./cmd/fn/ verdes tras los cambios.
Notas
- Archivos clave:
cmd/fn/run.go(dispatcher, líneas 145-194),registry/parser.go(campoTests),registry/test_parser.go(extracción de nombres de test),functions/infra/ssh_tunnel_open_close_test.goyfunctions/infra/docker_container_exec_test.go(tests culpables). - Relacionado con 0077 (fn-run-bash-output-mudo): familia de issues sobre la semántica y
observabilidad de
fn run.