8284afcba5
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
115 lines
3.3 KiB
Go
115 lines
3.3 KiB
Go
package ml
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// TestSdcliResolveBinary_NotFound verifica que SdcliResolveBinary retorna error
|
|
// cuando no hay binario en PATH ni hint. Forzamos PATH="" para que LookPath
|
|
// no encuentre nada, lo que hace el test determinista independientemente del
|
|
// entorno del desarrollador.
|
|
func TestSdcliResolveBinary_NotFound(t *testing.T) {
|
|
t.Setenv("PATH", "")
|
|
_, err := SdcliResolveBinary("")
|
|
if err == nil {
|
|
t.Fatal("expected error when sd not in PATH, got nil")
|
|
}
|
|
}
|
|
|
|
// TestSdcliResolveBinary_Hint verifica que un hint valido (archivo ejecutable)
|
|
// se resuelve con Source="config" sin necesidad de PATH.
|
|
func TestSdcliResolveBinary_Hint(t *testing.T) {
|
|
// Crear archivo temporal ejecutable que simula el binario sd.
|
|
// El script simplemente sale con 0; --version devolvera string vacio
|
|
// pero eso no es error (version es best-effort).
|
|
f, err := os.CreateTemp("", "sd-test-*")
|
|
if err != nil {
|
|
t.Fatalf("creating temp file: %v", err)
|
|
}
|
|
defer os.Remove(f.Name())
|
|
|
|
f.Close()
|
|
|
|
script := []byte("#!/bin/sh\necho 'sd-test 0.1'\n")
|
|
if err := os.WriteFile(f.Name(), script, 0o755); err != nil {
|
|
t.Fatalf("writing temp file: %v", err)
|
|
}
|
|
if err := os.Chmod(f.Name(), 0o755); err != nil {
|
|
t.Fatalf("chmod temp file: %v", err)
|
|
}
|
|
|
|
bin, err := SdcliResolveBinary(f.Name())
|
|
if err != nil {
|
|
t.Fatalf("SdcliResolveBinary(hint): %v", err)
|
|
}
|
|
if bin.Source != "config" {
|
|
t.Fatalf("expected source=config, got %q", bin.Source)
|
|
}
|
|
if bin.Path != f.Name() {
|
|
t.Fatalf("expected path=%q, got %q", f.Name(), bin.Path)
|
|
}
|
|
}
|
|
|
|
// TestSdcliGenerate_RequiresBinary es un test de integracion que salta si el
|
|
// binario sd no esta instalado en PATH. Si esta disponible, tambien requiere
|
|
// el modelo SD Turbo en el vault para ejecutar una generacion real.
|
|
func TestSdcliGenerate_RequiresBinary(t *testing.T) {
|
|
bin, err := SdcliResolveBinary("")
|
|
if err != nil {
|
|
t.Skipf("sd binary not in PATH, skipping integration test: %v", err)
|
|
}
|
|
|
|
modelPath := "/home/lucas/vaults/imagegen_models/diffusers/sd-turbo/sd_turbo.safetensors"
|
|
if _, err := os.Stat(modelPath); err != nil {
|
|
t.Skipf("SD Turbo model not in vault (%s), skipping: %v", modelPath, err)
|
|
}
|
|
|
|
cfg := GenerationConfig{
|
|
Prompt: "a red apple",
|
|
Seed: 42,
|
|
Steps: 4,
|
|
CfgScale: 1.0,
|
|
Sampler: "euler_a",
|
|
Width: 512,
|
|
Height: 512,
|
|
Model: ModelRef{
|
|
Name: "sd-turbo",
|
|
ModelType: "sd15",
|
|
Quantization: "fp16",
|
|
Path: modelPath,
|
|
},
|
|
}
|
|
|
|
outPath := t.TempDir() + "/out.png"
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
|
|
defer cancel()
|
|
|
|
var progressCalled bool
|
|
res, err := SdcliGenerate(ctx, bin, cfg, outPath, func(p SdcliProgress) {
|
|
progressCalled = true
|
|
t.Logf("progress: step %d/%d (%.1f%%) %.2fit/s",
|
|
p.Step, p.TotalSteps, p.Percent, p.ItPerSec)
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("SdcliGenerate: %v", err)
|
|
}
|
|
if len(res.ImageBytes) == 0 {
|
|
t.Fatal("expected non-empty image bytes")
|
|
}
|
|
if res.Format != "png" {
|
|
t.Fatalf("expected format=png, got %q", res.Format)
|
|
}
|
|
if res.DurationMs <= 0 {
|
|
t.Fatalf("expected positive duration_ms, got %d", res.DurationMs)
|
|
}
|
|
if res.Meta["backend"] != "sdcli" {
|
|
t.Fatalf("expected meta.backend=sdcli, got %v", res.Meta["backend"])
|
|
}
|
|
t.Logf("generated %d bytes in %dms (progress_called=%v)",
|
|
len(res.ImageBytes), res.DurationMs, progressCalled)
|
|
}
|