---
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`.
