package platform import ( "errors" "os" "os/exec" "path/filepath" "strings" "testing" ) func TestStorageSATCommands(t *testing.T) { t.Parallel() nvme := storageSATCommands("/dev/nvme0n1") if len(nvme) != 3 || nvme[2].cmd[0] != "nvme" { t.Fatalf("unexpected nvme commands: %#v", nvme) } sata := storageSATCommands("/dev/sda") if len(sata) != 2 || sata[0].cmd[0] != "smartctl" { t.Fatalf("unexpected sata commands: %#v", sata) } } func TestRunNvidiaAcceptancePackIncludesGPUStress(t *testing.T) { t.Parallel() jobs := nvidiaSATJobs() if len(jobs) != 5 { t.Fatalf("jobs=%d want 5", len(jobs)) } if got := jobs[4].cmd[0]; got != "bee-gpu-burn" { t.Fatalf("gpu stress command=%q want bee-gpu-burn", got) } if got := jobs[3].cmd[1]; got != "--output-file" { t.Fatalf("bug report flag=%q want --output-file", got) } } func TestAMDStressConfigUsesSingleGSTAction(t *testing.T) { t.Parallel() cfg := amdStressRVSConfig(123) if !strings.Contains(cfg, "module: gst") { t.Fatalf("config missing gst module:\n%s", cfg) } if strings.Contains(cfg, "module: mem") { t.Fatalf("config should not include mem module:\n%s", cfg) } if !strings.Contains(cfg, "copy_matrix: false") { t.Fatalf("config should use copy_matrix=false:\n%s", cfg) } if strings.Count(cfg, "duration: 123000") != 1 { t.Fatalf("config should apply duration once:\n%s", cfg) } for _, field := range []string{"matrix_size_a: 8640", "matrix_size_b: 8640", "matrix_size_c: 8640"} { if !strings.Contains(cfg, field) { t.Fatalf("config missing %s:\n%s", field, cfg) } } } func TestAMDStressJobsIncludeBandwidthAndGST(t *testing.T) { t.Parallel() jobs := amdStressJobs(300, "/tmp/test-amd-gst.conf") if len(jobs) != 4 { t.Fatalf("jobs=%d want 4", len(jobs)) } if got := jobs[1].cmd[0]; got != "rocm-bandwidth-test" { t.Fatalf("jobs[1]=%q want rocm-bandwidth-test", got) } if got := jobs[2].cmd[0]; got != "rvs" { t.Fatalf("jobs[2]=%q want rvs", got) } if got := jobs[2].cmd[2]; got != "/tmp/test-amd-gst.conf" { t.Fatalf("jobs[2] cfg=%q want /tmp/test-amd-gst.conf", got) } } func TestNvidiaSATJobsUseBuiltinBurnDefaults(t *testing.T) { jobs := nvidiaSATJobs() got := jobs[4].cmd want := []string{"bee-gpu-burn", "--seconds", "5", "--size-mb", "64"} if len(got) != len(want) { t.Fatalf("cmd len=%d want %d", len(got), len(want)) } for i := range want { if got[i] != want[i] { t.Fatalf("cmd[%d]=%q want %q", i, got[i], want[i]) } } } func TestBuildNvidiaStressJobUsesSelectedLoaderAndDevices(t *testing.T) { t.Parallel() oldExecCommand := satExecCommand satExecCommand = func(name string, args ...string) *exec.Cmd { if name == "nvidia-smi" { return exec.Command("sh", "-c", "printf '0\n1\n2\n'") } return exec.Command(name, args...) } t.Cleanup(func() { satExecCommand = oldExecCommand }) job, err := buildNvidiaStressJob(NvidiaStressOptions{ DurationSec: 600, Loader: NvidiaStressLoaderJohn, ExcludeGPUIndices: []int{1}, }) if err != nil { t.Fatalf("buildNvidiaStressJob error: %v", err) } wantCmd := []string{"bee-john-gpu-stress", "--seconds", "600", "--devices", "0,2"} if len(job.cmd) != len(wantCmd) { t.Fatalf("cmd len=%d want %d (%v)", len(job.cmd), len(wantCmd), job.cmd) } for i := range wantCmd { if job.cmd[i] != wantCmd[i] { t.Fatalf("cmd[%d]=%q want %q", i, job.cmd[i], wantCmd[i]) } } if got := joinIndexList(job.gpuIndices); got != "0,2" { t.Fatalf("gpuIndices=%q want 0,2", got) } } func TestBuildNvidiaStressJobUsesNCCLLoader(t *testing.T) { t.Parallel() oldExecCommand := satExecCommand satExecCommand = func(name string, args ...string) *exec.Cmd { if name == "nvidia-smi" { return exec.Command("sh", "-c", "printf '0\n1\n2\n'") } return exec.Command(name, args...) } t.Cleanup(func() { satExecCommand = oldExecCommand }) job, err := buildNvidiaStressJob(NvidiaStressOptions{ DurationSec: 120, Loader: NvidiaStressLoaderNCCL, GPUIndices: []int{2, 0}, }) if err != nil { t.Fatalf("buildNvidiaStressJob error: %v", err) } wantCmd := []string{"bee-nccl-gpu-stress", "--seconds", "120", "--devices", "0,2"} if len(job.cmd) != len(wantCmd) { t.Fatalf("cmd len=%d want %d (%v)", len(job.cmd), len(wantCmd), job.cmd) } for i := range wantCmd { if job.cmd[i] != wantCmd[i] { t.Fatalf("cmd[%d]=%q want %q", i, job.cmd[i], wantCmd[i]) } } if got := joinIndexList(job.gpuIndices); got != "0,2" { t.Fatalf("gpuIndices=%q want 0,2", got) } } func TestNvidiaStressArchivePrefixByLoader(t *testing.T) { t.Parallel() tests := []struct { loader string want string }{ {loader: NvidiaStressLoaderBuiltin, want: "gpu-nvidia-burn"}, {loader: NvidiaStressLoaderJohn, want: "gpu-nvidia-john"}, {loader: NvidiaStressLoaderNCCL, want: "gpu-nvidia-nccl"}, {loader: "", want: "gpu-nvidia-burn"}, } for _, tt := range tests { if got := nvidiaStressArchivePrefix(tt.loader); got != tt.want { t.Fatalf("loader=%q prefix=%q want %q", tt.loader, got, tt.want) } } } func TestEnvIntFallback(t *testing.T) { os.Unsetenv("BEE_MEMTESTER_SIZE_MB") if got := envInt("BEE_MEMTESTER_SIZE_MB", 123); got != 123 { t.Fatalf("got %d want 123", got) } t.Setenv("BEE_MEMTESTER_SIZE_MB", "bad") if got := envInt("BEE_MEMTESTER_SIZE_MB", 123); got != 123 { t.Fatalf("got %d want 123", got) } t.Setenv("BEE_MEMTESTER_SIZE_MB", "256") if got := envInt("BEE_MEMTESTER_SIZE_MB", 123); got != 256 { t.Fatalf("got %d want 256", got) } } func TestClassifySATResult(t *testing.T) { tests := []struct { name string job string out string err error status string }{ {name: "ok", job: "memtester", out: "done", err: nil, status: "OK"}, {name: "unsupported", job: "smartctl-self-test-short", out: "Self-test not supported", err: errors.New("rc 1"), status: "UNSUPPORTED"}, {name: "failed", job: "bee-gpu-burn", out: "cuda error", err: errors.New("rc 1"), status: "FAILED"}, {name: "cuda not ready", job: "bee-gpu-burn", out: "cuInit failed: CUDA_ERROR_SYSTEM_NOT_READY", err: errors.New("rc 1"), status: "UNSUPPORTED"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, _ := classifySATResult(tt.job, []byte(tt.out), tt.err) if got != tt.status { t.Fatalf("status=%q want %q", got, tt.status) } }) } } func TestParseStorageDevicesSkipsUSBDisks(t *testing.T) { t.Parallel() raw := "nvme0n1 disk nvme\nsda disk usb\nloop0 loop\nsdb disk sata\n" got := parseStorageDevices(raw) want := []string{"/dev/nvme0n1", "/dev/sdb"} if len(got) != len(want) { t.Fatalf("len(devices)=%d want %d (%v)", len(got), len(want), got) } for i := range want { if got[i] != want[i] { t.Fatalf("devices[%d]=%q want %q", i, got[i], want[i]) } } } func TestResolveROCmSMICommandFromPATH(t *testing.T) { t.Setenv("PATH", t.TempDir()) toolPath := filepath.Join(os.Getenv("PATH"), "rocm-smi") if err := os.WriteFile(toolPath, []byte("#!/bin/sh\nexit 0\n"), 0755); err != nil { t.Fatalf("write rocm-smi: %v", err) } cmd, err := resolveROCmSMICommand("--showproductname") if err != nil { t.Fatalf("resolveROCmSMICommand error: %v", err) } if len(cmd) != 2 { t.Fatalf("cmd len=%d want 2 (%v)", len(cmd), cmd) } if cmd[0] != toolPath { t.Fatalf("cmd[0]=%q want %q", cmd[0], toolPath) } } func TestResolveSATCommandUsesLookPathForGenericTools(t *testing.T) { oldLookPath := satLookPath satLookPath = func(file string) (string, error) { if file == "stress-ng" { return "/usr/bin/stress-ng", nil } return "", exec.ErrNotFound } t.Cleanup(func() { satLookPath = oldLookPath }) cmd, err := resolveSATCommand([]string{"stress-ng", "--cpu", "0"}) if err != nil { t.Fatalf("resolveSATCommand error: %v", err) } if len(cmd) != 3 { t.Fatalf("cmd len=%d want 3 (%v)", len(cmd), cmd) } if cmd[0] != "/usr/bin/stress-ng" { t.Fatalf("cmd[0]=%q want /usr/bin/stress-ng", cmd[0]) } } func TestResolveSATCommandFailsForMissingGenericTool(t *testing.T) { oldLookPath := satLookPath satLookPath = func(file string) (string, error) { return "", exec.ErrNotFound } t.Cleanup(func() { satLookPath = oldLookPath }) _, err := resolveSATCommand([]string{"stress-ng", "--cpu", "0"}) if err == nil { t.Fatal("expected error") } if !strings.Contains(err.Error(), "stress-ng not found in PATH") { t.Fatalf("error=%q", err) } } func TestResolveROCmSMICommandFallsBackToROCmTree(t *testing.T) { tmp := t.TempDir() execPath := filepath.Join(tmp, "opt", "rocm", "bin", "rocm-smi") if err := os.MkdirAll(filepath.Dir(execPath), 0755); err != nil { t.Fatalf("mkdir: %v", err) } if err := os.WriteFile(execPath, []byte("#!/bin/sh\nexit 0\n"), 0755); err != nil { t.Fatalf("write rocm-smi: %v", err) } oldGlob := rocmSMIExecutableGlobs oldScriptGlobs := rocmSMIScriptGlobs rocmSMIExecutableGlobs = []string{execPath} rocmSMIScriptGlobs = nil t.Cleanup(func() { rocmSMIExecutableGlobs = oldGlob rocmSMIScriptGlobs = oldScriptGlobs }) t.Setenv("PATH", "") cmd, err := resolveROCmSMICommand("--showallinfo") if err != nil { t.Fatalf("resolveROCmSMICommand error: %v", err) } if len(cmd) != 2 { t.Fatalf("cmd len=%d want 2 (%v)", len(cmd), cmd) } if cmd[0] != execPath { t.Fatalf("cmd[0]=%q want %q", cmd[0], execPath) } } func TestRunROCmSMIReportsMissingCommand(t *testing.T) { oldLookPath := satLookPath oldExecGlobs := rocmSMIExecutableGlobs oldScriptGlobs := rocmSMIScriptGlobs satLookPath = func(string) (string, error) { return "", exec.ErrNotFound } rocmSMIExecutableGlobs = nil rocmSMIScriptGlobs = nil t.Cleanup(func() { satLookPath = oldLookPath rocmSMIExecutableGlobs = oldExecGlobs rocmSMIScriptGlobs = oldScriptGlobs }) if _, err := runROCmSMI("--showproductname"); err == nil { t.Fatal("expected missing rocm-smi error") } }