Files
fn_registry/dev/issues/completed/0167-fn-run-library-go-paquete-entero.md
T
egutierrez fa09ff9866 feat(infra): auto-commit con 4 cambios
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-03 16:44:23 +02:00

163 lines
8.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
id: "0167"
title: "fn run de library function Go ejecuta go test del paquete entero (arrastra tests flaky vecinos)"
status: completado
type: enhancement
domain:
- registry-quality
scope: registry-only
priority: media
depends: []
blocks: []
related: ["0077"]
created: 2026-06-03
updated: 2026-06-03
tags: [fn-run, go, testing, flaky, dag-engine, reliability]
---
# 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 use`
- `TestDockerContainerExec``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 ejecuta `fn doctor <sub>`
> real en vez de `go test` del paquete. Este issue es la **causa raíz general** del
> dispatcher, que sigue afectando a cualquier `fn run <library_go_fn_con_tests>`.
## Problema
1. `fn run` de una library function NO ejecuta la función — corre el paquete de test entero.
2. Los tests impuros de un paquete (puertos/sockets/red fijos) no son seguros para
ejecuciones concurrentes ni reproducibles en cualquier entorno (TMPDIR, CI).
3. Un único test flaky en `functions/infra` rompe `fn run` de 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ía `fn 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, `-run` no matchea y `go test` sale 0 con
"no tests to run" → **falso-verde** en una primitiva crítica de todo el ecosistema.
Mitigación obligatoria si se elige B: reconciliar `fn.Tests` con los tests extraídos por
el indexer (`registry/test_parser.go::parseGoTests`, que ya puebla `unit_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 fijo `19876` pasa a **puerto efímero** (`freeTCPPort` pide `:0` al kernel). Elimina el `bind: address already in use` bajo concurrencia.
- `docker_container_exec_test.go`: el socket Unix deja de colgar de `t.TempDir()` (path largo con el nombre del subtest) y usa un **directorio corto** bajo `/tmp` (`os.MkdirTemp("/tmp", "dk")` + cleanup). Elimina el `bind: invalid argument` por exceder los ~108 bytes de `sun_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ía `db.GetUnitTestsByFunction`), no los del frontmatter `.md` (que pueden driftar).
Así `fn run` ejecuta 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**: `cmdRun` refleja el output de un `go test -run` a un
buffer; si go test reporta `no tests to run` (que sale con exit 0), el run se trata
como **fallo** (exit 1 + mensaje pidiendo `fn 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`
(campo `Tests`), `registry/test_parser.go` (extracción de nombres de test),
`functions/infra/ssh_tunnel_open_close_test.go` y `functions/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`.