package worker_test import ( "encoding/json" "os" "testing" "github.com/jfraeys/fetch_ml/internal/worker" ) // GoldenGPUStatus represents the expected GPU status output for golden file testing type GoldenGPUStatus struct { GPUCount int `json:"gpu_count"` GPUType string `json:"gpu_type"` ConfiguredVendor string `json:"configured_vendor"` DetectionMethod string `json:"detection_method"` EnvOverrideType string `json:"env_override_type,omitempty"` EnvOverrideCount int `json:"env_override_count,omitempty"` BuildTags map[string]bool `json:"build_tags"` NativeAvailable bool `json:"native_available"` Extra map[string]interface{} `json:"extra,omitempty"` } // detectBuildTags returns which build tags are active func detectBuildTags() map[string]bool { tags := map[string]bool{ "cgo": false, "native_libs": false, "darwin": false, "linux": false, } // CGO is determined at compile time - we can detect by trying to use native // If native functions return "disabled", we know native_libs is not set simdName := worker.GetSIMDImplName() tags["native_libs"] = simdName != "disabled" && simdName != "disabled (no CGO)" tags["cgo"] = simdName != "disabled (no CGO)" // OS detection if worker.IsMacOS() { tags["darwin"] = true } else { tags["linux"] = true } return tags } // TestGoldenGPUStatusNVML validates GPU status against golden file for NVML path // This test runs under all build configurations but expectations differ: // - cgo+native_libs: Real GPU count and NVML detection // - cgo without native_libs: Returns 0, nil (stub behavior) // - !cgo: Returns 0, nil (stub behavior) func TestGoldenGPUStatusNVML(t *testing.T) { // Setup: Configure for NVIDIA detection cfg := &worker.Config{ GPUVendor: "nvidia", } factory := &worker.GPUDetectorFactory{} result := factory.CreateDetectorWithInfo(cfg) // Get actual detected count (behavior varies by build tags) count := result.Detector.DetectGPUCount() buildTags := detectBuildTags() // Build the golden status object got := GoldenGPUStatus{ GPUCount: count, GPUType: string(result.Info.GPUType), ConfiguredVendor: result.Info.ConfiguredVendor, DetectionMethod: string(result.Info.DetectionMethod), BuildTags: buildTags, NativeAvailable: buildTags["native_libs"] && buildTags["cgo"], } // Validate against build-specific expectations if buildTags["native_libs"] && buildTags["cgo"] { // Real NVML build: Should detect actual GPUs or get real NVML error // GPU count may be 0 if no NVIDIA hardware, but detection method should be config if got.DetectionMethod != "config" { t.Errorf("cgo+native_libs: DetectionMethod = %v, want 'config'", got.DetectionMethod) } } else if buildTags["cgo"] { // CGO without native_libs: Stub returns 0 if got.GPUCount != 0 { t.Logf("cgo-only build: GPUCount = %d (expected 0 from stub)", got.GPUCount) } if got.NativeAvailable { t.Error("cgo-only build: NativeAvailable should be false") } } else { // No CGO: Stub returns 0 if got.GPUCount != 0 { t.Logf("nocgo build: GPUCount = %d (expected 0 from stub)", got.GPUCount) } if got.NativeAvailable { t.Error("nocgo build: NativeAvailable should be false") } } // Common validations if got.ConfiguredVendor != "nvidia" { t.Errorf("ConfiguredVendor = %v, want 'nvidia'", got.ConfiguredVendor) } } // TestGoldenGPUStatusAMDVendorAlias validates AMD aliasing is visible in output // Build tags: all three configurations // Runtime scenarios: amd config func TestGoldenGPUStatusAMDVendorAlias(t *testing.T) { cfg := &worker.Config{ GPUVendor: "amd", } factory := &worker.GPUDetectorFactory{} result := factory.CreateDetectorWithInfo(cfg) buildTags := detectBuildTags() got := GoldenGPUStatus{ GPUCount: result.Detector.DetectGPUCount(), GPUType: string(result.Info.GPUType), ConfiguredVendor: result.Info.ConfiguredVendor, DetectionMethod: string(result.Info.DetectionMethod), BuildTags: buildTags, NativeAvailable: buildTags["native_libs"] && buildTags["cgo"], } // The key assertion: configured_vendor should be "amd" but GPUType should be "nvidia" // This makes the aliasing visible in status output if got.ConfiguredVendor != "amd" { t.Errorf("AMD config: ConfiguredVendor = %v, want 'amd'", got.ConfiguredVendor) } if got.GPUType != "nvidia" { t.Errorf("AMD config: GPUType = %v, want 'nvidia' (AMD aliased to NVIDIA implementation)", got.GPUType) } } // TestGoldenGPUStatusEnvOverride validates env override behavior across build configs // Build tags: all three // Runtime scenarios: env override set func TestGoldenGPUStatusEnvOverride(t *testing.T) { // Set env override os.Setenv("FETCH_ML_GPU_TYPE", "nvidia") os.Setenv("FETCH_ML_GPU_COUNT", "4") defer os.Unsetenv("FETCH_ML_GPU_TYPE") defer os.Unsetenv("FETCH_ML_GPU_COUNT") factory := &worker.GPUDetectorFactory{} result := factory.CreateDetectorWithInfo(&worker.Config{GPUVendor: "apple"}) buildTags := detectBuildTags() got := GoldenGPUStatus{ GPUCount: result.Detector.DetectGPUCount(), GPUType: string(result.Info.GPUType), ConfiguredVendor: result.Info.ConfiguredVendor, DetectionMethod: string(result.Info.DetectionMethod), EnvOverrideType: result.Info.EnvOverrideType, EnvOverrideCount: result.Info.EnvOverrideCount, BuildTags: buildTags, } // Env should take precedence over config if got.DetectionMethod != "env_override_both" { t.Errorf("Env override: DetectionMethod = %v, want 'env_override_both'", got.DetectionMethod) } if got.GPUType != "nvidia" { t.Errorf("Env override: GPUType = %v, want 'nvidia'", got.GPUType) } if got.EnvOverrideType != "nvidia" { t.Errorf("Env override: EnvOverrideType = %v, want 'nvidia'", got.EnvOverrideType) } if got.EnvOverrideCount != 4 { t.Errorf("Env override: EnvOverrideCount = %v, want 4", got.EnvOverrideCount) } } // TestGoldenGPUStatusMacOS validates macOS detection when running on Darwin // Build tags: cgo+native_libs on Darwin // Runtime scenarios: darwin func TestGoldenGPUStatusMacOS(t *testing.T) { if !worker.IsMacOS() { t.Skip("Skipping macOS-specific test on non-Darwin platform") } cfg := &worker.Config{ GPUVendor: "apple", AppleGPU: worker.AppleGPUConfig{Enabled: true}, } factory := &worker.GPUDetectorFactory{} result := factory.CreateDetectorWithInfo(cfg) buildTags := detectBuildTags() got := GoldenGPUStatus{ GPUCount: result.Detector.DetectGPUCount(), GPUType: string(result.Info.GPUType), ConfiguredVendor: result.Info.ConfiguredVendor, DetectionMethod: string(result.Info.DetectionMethod), BuildTags: buildTags, NativeAvailable: buildTags["darwin"], } if got.ConfiguredVendor != "apple" { t.Errorf("macOS: ConfiguredVendor = %v, want 'apple'", got.ConfiguredVendor) } if got.GPUType != "apple" { t.Errorf("macOS: GPUType = %v, want 'apple'", got.GPUType) } if !got.BuildTags["darwin"] { t.Error("macOS: darwin build tag should be true") } } // TestGoldenGPUStatusNone validates no-GPU configuration // Build tags: all three // Runtime scenarios: none func TestGoldenGPUStatusNone(t *testing.T) { cfg := &worker.Config{ GPUVendor: "none", } factory := &worker.GPUDetectorFactory{} result := factory.CreateDetectorWithInfo(cfg) if result.Detector.DetectGPUCount() != 0 { t.Errorf("none config: GPUCount = %v, want 0", result.Detector.DetectGPUCount()) } if result.Info.ConfiguredVendor != "none" { t.Errorf("none config: ConfiguredVendor = %v, want 'none'", result.Info.ConfiguredVendor) } } // TestGoldenJSONSerialization validates the GPU status serializes to JSON correctly func TestGoldenJSONSerialization(t *testing.T) { os.Setenv("FETCH_ML_GPU_TYPE", "nvidia") os.Setenv("FETCH_ML_GPU_COUNT", "2") defer os.Unsetenv("FETCH_ML_GPU_TYPE") defer os.Unsetenv("FETCH_ML_GPU_COUNT") factory := &worker.GPUDetectorFactory{} result := factory.CreateDetectorWithInfo(nil) status := GoldenGPUStatus{ GPUCount: result.Detector.DetectGPUCount(), GPUType: string(result.Info.GPUType), ConfiguredVendor: result.Info.ConfiguredVendor, DetectionMethod: string(result.Info.DetectionMethod), EnvOverrideType: result.Info.EnvOverrideType, EnvOverrideCount: result.Info.EnvOverrideCount, BuildTags: detectBuildTags(), } // Serialize to JSON (this mimics what ml status --json would output) jsonData, err := json.MarshalIndent(status, "", " ") if err != nil { t.Fatalf("JSON serialization failed: %v", err) } // Verify JSON can be parsed back var parsed GoldenGPUStatus if err := json.Unmarshal(jsonData, &parsed); err != nil { t.Fatalf("JSON deserialization failed: %v", err) } if parsed.ConfiguredVendor != status.ConfiguredVendor { t.Errorf("JSON roundtrip: ConfiguredVendor mismatch") } if parsed.DetectionMethod != status.DetectionMethod { t.Errorf("JSON roundtrip: DetectionMethod mismatch") } } // TestBuildTagMatrix validates that all expected build tag combinations are testable // This test documents the three build configurations: // 1. cgo + native_libs: Real native library implementations // 2. cgo without native_libs: Stubs that return errors // 3. !cgo: Stubs that return "disabled (no CGO)" func TestBuildTagMatrix(t *testing.T) { tags := detectBuildTags() // Log the current build configuration for CI visibility t.Logf("Build configuration: cgo=%v native_libs=%v darwin=%v linux=%v", tags["cgo"], tags["native_libs"], tags["darwin"], tags["linux"]) // Validate SIMD implementation name matches build tags simdName := worker.GetSIMDImplName() t.Logf("SIMD implementation: %s", simdName) switch { case tags["native_libs"]: // Should have real implementation name (avx2, sha_ni, armv8_crypto, or generic) if simdName == "disabled" || simdName == "disabled (no CGO)" { t.Errorf("native_libs build: SIMD impl should be active, got %q", simdName) } case tags["cgo"]: // Should be disabled without native_libs if simdName != "disabled" { t.Errorf("cgo-only build: SIMD impl should be 'disabled', got %q", simdName) } default: // No CGO if simdName != "disabled (no CGO)" { t.Errorf("nocgo build: SIMD impl should be 'disabled (no CGO)', got %q", simdName) } } }