chore: auto-commit (95 archivos)

- cmd/fn/doctor.go
- cmd/fn/main.go
- cpp/apps/primitives_gallery/playground/tables/CMakeLists.txt
- cpp/apps/primitives_gallery/playground/tables/data_table.cpp
- cpp/apps/primitives_gallery/playground/tables/data_table_logic.cpp
- cpp/apps/primitives_gallery/playground/tables/data_table_logic.h
- cpp/apps/primitives_gallery/playground/tables/self_test.cpp
- cpp/apps/primitives_gallery/playground/tables/tql.cpp
- cpp/apps/primitives_gallery/playground/tables/viz.cpp
- cpp/apps/primitives_gallery/playground/tables/viz.h
- ...

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-13 00:50:34 +02:00
parent a2bbf23374
commit e3c8979e8d
189 changed files with 18964 additions and 330 deletions
+1059
View File
File diff suppressed because it is too large Load Diff
+318
View File
@@ -0,0 +1,318 @@
package main
import (
"encoding/json"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
"time"
"fn-registry/functions/infra"
"fn-registry/registry"
)
// fnBinDir holds the temp directory for the compiled fn binary.
// It is created by TestMain and cleaned up at test end.
var fnBinDir string
var fnBinPath string
// TestMain compiles the fn binary once before all tests.
func TestMain(m *testing.M) {
var err error
fnBinDir, err = os.MkdirTemp("", "fn-vault-test-*")
if err != nil {
fmt.Fprintf(os.Stderr, "create temp dir: %v\n", err)
os.Exit(1)
}
defer os.RemoveAll(fnBinDir)
fnBinPath = filepath.Join(fnBinDir, "fn")
// Find registry root by walking up from current directory.
regRoot, err := findRoot()
if err != nil {
fmt.Fprintf(os.Stderr, "find root: %v\n", err)
os.Exit(1)
}
cmd := exec.Command("go", "build", "-tags", "fts5", "-o", fnBinPath, ".")
cmd.Dir = filepath.Join(regRoot, "cmd", "fn")
if out, errB := cmd.CombinedOutput(); errB != nil {
fmt.Fprintf(os.Stderr, "build fn: %v\n%s\n", errB, out)
os.Exit(1)
}
os.Exit(m.Run())
}
func findRoot() (string, error) {
dir, err := os.Getwd()
if err != nil {
return "", err
}
for {
if _, err := os.Stat(filepath.Join(dir, "go.mod")); err == nil {
return dir, nil
}
parent := filepath.Dir(dir)
if parent == dir {
return "", fmt.Errorf("could not find go.mod from %s", dir)
}
dir = parent
}
}
func ensureFnBin(t *testing.T) string {
t.Helper()
return fnBinPath
}
// setupTestRegistry creates a minimal registry root with:
// - registry.db (opened + migrations applied via registry.Open)
// - a project with a vault declared in vault.yaml
// - a vault directory with some test files
// - a symlink from projects/test_proj/vaults/test_vault -> vault dir
//
// Returns (repoRoot, vaultDir).
func setupTestRegistry(t *testing.T) (string, string) {
t.Helper()
repoRoot := t.TempDir()
// Create vault directory with files.
vaultDir := filepath.Join(t.TempDir(), "test_vault")
if err := os.MkdirAll(filepath.Join(vaultDir, "data", "raw"), 0755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(vaultDir, "data", "raw", "report.csv"),
[]byte("name,value\nfoo,1"), 0644); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(vaultDir, "data", "raw", "notes.md"),
[]byte("# Notes\nsome text"), 0644); err != nil {
t.Fatal(err)
}
// Create project directory structure.
projDir := filepath.Join(repoRoot, "projects", "test_proj")
vaultsDir := filepath.Join(projDir, "vaults")
if err := os.MkdirAll(vaultsDir, 0755); err != nil {
t.Fatal(err)
}
// Create vault.yaml.
vaultYAML := "vaults:\n - name: test_vault\n description: Test vault for unit tests\n path: " + vaultDir + "\n tags: [test]\n"
if err := os.WriteFile(filepath.Join(vaultsDir, "vault.yaml"), []byte(vaultYAML), 0644); err != nil {
t.Fatal(err)
}
// Create project.md.
projMD := "---\nname: test_proj\ndescription: Test project\ntags: [test]\n---\n"
if err := os.WriteFile(filepath.Join(projDir, "project.md"), []byte(projMD), 0644); err != nil {
t.Fatal(err)
}
// Open registry.db (creates schema + runs migrations).
db, err := registry.Open(filepath.Join(repoRoot, "registry.db"))
if err != nil {
t.Fatalf("registry.Open: %v", err)
}
// Index so the vault is registered in registry.db.
if _, err := registry.Index(db, repoRoot); err != nil {
t.Fatalf("registry.Index: %v", err)
}
db.Close()
return repoRoot, vaultDir
}
// runFn runs the fn binary in repoRoot with the given args.
func runFn(t *testing.T, repoRoot string, args ...string) (string, string, int) {
t.Helper()
bin := ensureFnBin(t)
cmd := exec.Command(bin, args...)
cmd.Dir = repoRoot
var stdout, stderr strings.Builder
cmd.Stdout = &stdout
cmd.Stderr = &stderr
err := cmd.Run()
code := 0
if err != nil {
if exitErr, ok := err.(*exec.ExitError); ok {
code = exitErr.ExitCode()
} else {
t.Logf("cmd error: %v", err)
}
}
return stdout.String(), stderr.String(), code
}
// TestVaultList verifies that 'fn vault list' shows the indexed vault.
func TestVaultList(t *testing.T) {
repoRoot, _ := setupTestRegistry(t)
out, stderr, code := runFn(t, repoRoot, "vault", "list")
if code != 0 {
t.Fatalf("fn vault list exit %d\nstderr: %s", code, stderr)
}
if !strings.Contains(out, "test_vault") {
t.Errorf("expected 'test_vault' in output, got:\n%s", out)
}
}
// TestVaultIndex verifies that 'fn vault index <name>' runs without error.
func TestVaultIndex(t *testing.T) {
repoRoot, _ := setupTestRegistry(t)
out, stderr, code := runFn(t, repoRoot, "vault", "index", "test_vault")
if code != 0 {
t.Fatalf("fn vault index exit %d\nstderr: %s\nstdout: %s", code, stderr, out)
}
if !strings.Contains(out, "indexed") {
t.Errorf("expected 'indexed' in output, got:\n%s", out)
}
}
// TestVaultSearchJSON verifies that 'fn vault search --json' returns valid JSON array.
func TestVaultSearchJSON(t *testing.T) {
repoRoot, vaultDir := setupTestRegistry(t)
// First index the vault so there is something to search.
if _, _, code := runFn(t, repoRoot, "vault", "index", "test_vault"); code != 0 {
t.Fatal("fn vault index failed")
}
// Seed some content into the vault index for the search to find.
db, err := infra.VaultIndexOpen(vaultDir)
if err != nil {
t.Fatalf("VaultIndexOpen: %v", err)
}
// Update content_text for FTS search.
db.Exec(`DELETE FROM files_fts WHERE rel_path = 'data/raw/report.csv'`)
db.Exec(`INSERT INTO files_fts(rel_path, content_text) VALUES ('data/raw/report.csv', 'foo report data')`)
db.Close()
out, stderr, code := runFn(t, repoRoot, "vault", "search", "report", "--json", "--vault", "test_vault")
if code != 0 {
t.Fatalf("fn vault search exit %d\nstderr: %s", code, stderr)
}
var result []map[string]interface{}
if err := json.Unmarshal([]byte(out), &result); err != nil {
t.Fatalf("output is not valid JSON: %v\nraw: %s", err, out)
}
// Should be a JSON array (possibly empty if search finds nothing, but must be valid).
t.Logf("search returned %d hits", len(result))
}
// TestVaultInfo verifies that 'fn vault info <name>' outputs vault stats.
func TestVaultInfo(t *testing.T) {
repoRoot, _ := setupTestRegistry(t)
// Index first.
if _, _, code := runFn(t, repoRoot, "vault", "index", "test_vault"); code != 0 {
t.Fatal("fn vault index failed")
}
out, stderr, code := runFn(t, repoRoot, "vault", "info", "test_vault")
if code != 0 {
t.Fatalf("fn vault info exit %d\nstderr: %s", code, stderr)
}
if !strings.Contains(out, "test_vault") {
t.Errorf("expected vault name in output, got:\n%s", out)
}
if !strings.Contains(out, "Files:") {
t.Errorf("expected 'Files:' in output, got:\n%s", out)
}
}
// TestFormatBytes verifies the formatBytes helper.
func TestFormatBytes(t *testing.T) {
cases := []struct {
input int64
expected string
}{
{500, "500 B"},
{1024, "1.0 KB"},
{1536, "1.5 KB"},
{1048576, "1.0 MB"},
{1073741824, "1.0 GB"},
}
for _, tc := range cases {
got := formatBytes(tc.input)
if got != tc.expected {
t.Errorf("formatBytes(%d) = %q, want %q", tc.input, got, tc.expected)
}
}
}
// TestVaultLayoutEnsure verifies that 'fn vault layout-ensure --dry-run' works.
func TestVaultLayoutEnsure(t *testing.T) {
repoRoot, _ := setupTestRegistry(t)
out, stderr, code := runFn(t, repoRoot, "vault", "layout-ensure", "test_vault", "--dry-run")
if code != 0 {
t.Fatalf("fn vault layout-ensure exit %d\nstderr: %s\nstdout: %s", code, stderr, out)
}
if !strings.Contains(out, "test_vault") {
t.Errorf("expected vault name in output, got:\n%s", out)
}
}
// TestVaultAggregate verifies that 'fn vault aggregate' runs without error on a clean registry.
func TestVaultAggregate(t *testing.T) {
repoRoot, _ := setupTestRegistry(t)
// Index first so there is something to aggregate.
if _, _, code := runFn(t, repoRoot, "vault", "index", "test_vault"); code != 0 {
t.Fatal("fn vault index failed")
}
_, stderr, code := runFn(t, repoRoot, "vault", "aggregate")
if code != 0 {
t.Fatalf("fn vault aggregate exit %d\nstderr: %s", code, stderr)
}
}
// TestVaultDoctor verifies that 'fn vault doctor' runs and reports on vaults.
func TestVaultDoctor(t *testing.T) {
repoRoot, _ := setupTestRegistry(t)
out, stderr, code := runFn(t, repoRoot, "vault", "doctor")
if code != 0 {
t.Fatalf("fn vault doctor exit %d\nstderr: %s", code, stderr)
}
if !strings.Contains(out, "test_vault") {
t.Errorf("expected 'test_vault' in doctor output, got:\n%s", out)
}
}
// TestVaultDedupe verifies that 'fn vault dedupe' runs without error after indexing.
func TestVaultDedupe(t *testing.T) {
repoRoot, _ := setupTestRegistry(t)
if _, _, code := runFn(t, repoRoot, "vault", "index", "test_vault"); code != 0 {
t.Fatal("fn vault index failed")
}
out, stderr, code := runFn(t, repoRoot, "vault", "dedupe", "test_vault")
if code != 0 {
t.Fatalf("fn vault dedupe exit %d\nstderr: %s", code, stderr)
}
// Should say "No duplicates" or show a table — either is fine.
_ = out
}
// TestVaultAuditDryRun verifies that 'fn vault audit --dry-run-layout --skip-profilers' works.
func TestVaultAuditDryRun(t *testing.T) {
repoRoot, _ := setupTestRegistry(t)
out, stderr, code := runFn(t, repoRoot, "vault", "audit", "test_vault",
"--dry-run-layout", "--skip-profilers")
// Exit 0 = fully ok; exit 4 = warnings (layout issues) — both acceptable here.
if code != 0 && code != 4 {
t.Fatalf("fn vault audit exit %d\nstderr: %s\nstdout: %s", code, stderr, out)
}
if !strings.Contains(out, "summary") {
t.Errorf("expected 'summary' section in audit output, got:\n%s", out)
}
}
// Suppress unused import for time.
var _ = time.Now
+95
View File
@@ -44,6 +44,10 @@ func cmdDoctor(args []string) {
doctorUnused(r, jsonOut)
case "cpp-apps":
doctorCppApps(r, jsonOut)
case "ml":
doctorML(r, jsonOut)
case "vaults":
doctorVaults(r, jsonOut)
default:
fmt.Fprintf(os.Stderr, "unknown doctor subcommand: %s\n", sub)
doctorUsage()
@@ -65,6 +69,8 @@ Subcommands:
uses-functions Audit imports reales vs uses_functions del app.md
unused Funciones del registry sin consumidores
cpp-apps Conformidad de apps C++ con cpp/PATTERNS.md (cfg.about, dockspace, menubar)
ml Entorno ML: GPUs NVIDIA, CUDA toolkit, venv Python, paquetes torch/diffusers, CLIs y vault
vaults Salud de vaults: directorio, layout, índice, staleness, drift
Flags:
--json Salida JSON (para scripting/agentes)`)
@@ -103,6 +109,16 @@ func doctorAll(root string, jsonOut bool) {
} else {
all["cpp_apps_error"] = err.Error()
}
if v, err := infra.AuditMlEnv(root); err == nil {
all["ml"] = v
} else {
all["ml_error"] = err.Error()
}
if v, err := infra.VaultDoctor(root); err == nil {
all["vaults"] = v
} else {
all["vaults_error"] = err.Error()
}
emit(all)
return
}
@@ -119,6 +135,10 @@ func doctorAll(root string, jsonOut bool) {
doctorUnused(root, false)
fmt.Println("\n=== C++ apps standard conformance ===")
doctorCppApps(root, false)
fmt.Println("\n=== ML environment ===")
doctorML(root, false)
fmt.Println("\n=== Vaults ===")
doctorVaults(root, false)
}
func doctorCppApps(root string, jsonOut bool) {
@@ -280,6 +300,81 @@ func doctorUnused(root string, jsonOut bool) {
fmt.Printf("\n%d unused functions (candidates to remove).\n", len(unused))
}
func doctorVaults(root string, jsonOut bool) {
entries, err := infra.VaultDoctor(root)
if err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
}
if jsonOut {
emit(entries)
return
}
if len(entries) == 0 {
fmt.Println("No vaults declared (no projects/*/vaults/vault.yaml found).")
return
}
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
fmt.Fprintln(w, "NAME\tSTATUS\tFILES\tINDEXED\tISSUES")
ok := 0
for _, e := range entries {
issues := "-"
if len(e.Issues) > 0 {
issues = strings.Join(e.Issues, "; ")
}
fmt.Fprintf(w, "%s\t%s\t%d\t%d\t%s\n",
e.VaultName, e.Status, e.DiskFiles, e.IndexedFiles, issues)
if e.Status == "ok" {
ok++
}
}
w.Flush()
fmt.Printf("\n%d/%d vaults healthy.\n", ok, len(entries))
}
func doctorML(root string, jsonOut bool) {
report, err := infra.AuditMlEnv(root)
if err != nil {
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)
}
if jsonOut {
emit(report)
return
}
fmt.Printf("GPUs detected: %d\n", len(report.Gpus))
for _, g := range report.Gpus {
fmt.Printf(" [%d] %s VRAM: %d/%d MiB Driver: %s CUDA: %s\n",
g.Index, g.Name, g.VramFreeMb, g.VramTotalMb, g.DriverVersion, g.CudaVersion)
}
fmt.Println()
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
fmt.Fprintln(w, "CHECK\tSTATUS\tVERSION\tDETAIL")
for _, c := range report.Checks {
version := c.Version
if version == "" {
version = "-"
}
detail := c.Detail
if len(detail) > 60 {
detail = detail[:60] + "..."
}
if detail == "" {
detail = "-"
}
fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", c.Name, c.Status, version, detail)
}
w.Flush()
overall := "OK"
if !report.OverallOK {
overall = "INCOMPLETE"
}
fmt.Printf("\nOverall ML environment: %s\n", overall)
}
func emit(v any) {
b, err := json.MarshalIndent(v, "", " ")
if err != nil {
+3
View File
@@ -45,6 +45,8 @@ func main() {
cmdAnalysis(os.Args[2:])
case "sync":
cmdSync(os.Args[2:])
case "vault":
cmdVault(os.Args[2:])
case "doctor":
cmdDoctor(os.Args[2:])
case "help", "-h", "--help":
@@ -73,6 +75,7 @@ Usage:
fn app <list|clone|pull> Gestiona apps externas (Gitea)
fn analysis <list|clone|pull> Gestiona analyses externas (Gitea)
fn sync [status|locations] Sincroniza con servidor central
fn vault <list|search|index|info> Gestiona y busca en data vaults
fn doctor [artefacts|services|sync|uses-functions|unused] [--json]
Diagnostico read-only del registry`)
}