Non-server entry point over the parser registry: recursively discovers supported dumps, parses them with bounded concurrency, and writes one path-mirrored CPU identity report per input plus a deterministic summary. Failures are isolated as JSON. A -reanimator mode emits a flat CPU-only dataset via the production exporter. See ADL-059. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
131 lines
3.9 KiB
Go
131 lines
3.9 KiB
Go
package main
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"git.mchus.pro/mchus/logpile/internal/exporter"
|
|
"git.mchus.pro/mchus/logpile/internal/models"
|
|
)
|
|
|
|
func TestIsAuditCandidate(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
path string
|
|
includePlain bool
|
|
want bool
|
|
}{
|
|
{name: "tar gzip", path: "dump.tar.gz", want: true},
|
|
{name: "zip", path: "dump.zip", want: true},
|
|
{name: "plain log excluded", path: "component.log", want: false},
|
|
{name: "plain log requested", path: "nvidia-bug-report.log", includePlain: true, want: true},
|
|
{name: "JSON report excluded", path: "report.json", want: false},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if got := isAuditCandidate(tt.path, tt.includePlain); got != tt.want {
|
|
t.Fatalf("isAuditCandidate(%q, %v) = %v, want %v", tt.path, tt.includePlain, got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCPUOnlyReanimatorPayloadPreservesRequiredFields(t *testing.T) {
|
|
payload := &exporter.ReanimatorExport{
|
|
Filename: "dump.tar.gz",
|
|
CollectedAt: "2026-07-27T10:34:40Z",
|
|
Hardware: exporter.ReanimatorHardware{
|
|
Board: exporter.ReanimatorBoard{SerialNumber: "23E102624"},
|
|
CPUs: []exporter.ReanimatorCPU{{Socket: 0, SerialNumber: "D46E5D6B1D3E40E1"}},
|
|
Firmware: []exporter.ReanimatorFirmware{{DeviceName: "BIOS", Version: "1.0"}},
|
|
},
|
|
}
|
|
|
|
got := cpuOnlyReanimatorPayload(payload)
|
|
if got.CollectedAt == "" || got.Hardware.Board.SerialNumber == "" || len(got.Hardware.CPUs) != 1 {
|
|
t.Fatalf("required ingest fields were lost: %+v", got)
|
|
}
|
|
if len(got.Hardware.Firmware) != 0 {
|
|
t.Fatalf("expected non-CPU hardware to be removed: %+v", got.Hardware.Firmware)
|
|
}
|
|
}
|
|
|
|
func TestFlatReanimatorName(t *testing.T) {
|
|
got := flatReanimatorName("v1", "NL/example/dump.tar.gz")
|
|
if got != "v1__dump.tar.gz.reanimator.json" {
|
|
t.Fatalf("unexpected flat name %q", got)
|
|
}
|
|
}
|
|
|
|
func TestBuildCPUAudit(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
cpu models.CPU
|
|
finding string
|
|
promoted bool
|
|
expectedSer string
|
|
}{
|
|
{
|
|
name: "PPIN promoted",
|
|
cpu: models.CPU{Socket: 0, PPIN: "D46E5D6B1D3E40E1", SerialNumber: "D46E5D6B1D3E40E1"},
|
|
finding: "ppin_used_as_serial",
|
|
promoted: true,
|
|
expectedSer: "D46E5D6B1D3E40E1",
|
|
},
|
|
{
|
|
name: "regression detected",
|
|
cpu: models.CPU{Socket: 1, PPIN: "D44F8D6B9155EE0E"},
|
|
finding: "missing_serial_with_ppin",
|
|
expectedSer: "D44F8D6B9155EE0E",
|
|
},
|
|
{
|
|
name: "placeholder serial with PPIN",
|
|
cpu: models.CPU{Socket: 1, PPIN: "D44F8D6B9155EE0E", SerialNumber: "Unknown"},
|
|
finding: "missing_serial_with_ppin",
|
|
expectedSer: "D44F8D6B9155EE0E",
|
|
},
|
|
{
|
|
name: "native serial",
|
|
cpu: models.CPU{Socket: 0, SerialNumber: "CPU-SERIAL"},
|
|
finding: "source_serial_present",
|
|
expectedSer: "CPU-SERIAL",
|
|
},
|
|
{
|
|
name: "placeholder PPIN",
|
|
cpu: models.CPU{Socket: 0, PPIN: "N/A"},
|
|
finding: "cpu_identity_missing",
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got := buildCPUAudit(tt.cpu)
|
|
if got.Finding != tt.finding || got.PPINPromoted != tt.promoted || got.ExpectedSerialNumber != tt.expectedSer {
|
|
t.Fatalf("unexpected audit: %+v", got)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRunAuditWritesFailureReportAndSummary(t *testing.T) {
|
|
input := t.TempDir()
|
|
output := t.TempDir()
|
|
badArchive := filepath.Join(input, "bad.zip")
|
|
if err := os.WriteFile(badArchive, []byte("not a zip"), 0o644); err != nil {
|
|
t.Fatalf("write fixture: %v", err)
|
|
}
|
|
|
|
summary, err := runAudit(input, output, 1, false)
|
|
if err != nil {
|
|
t.Fatalf("runAudit failed: %v", err)
|
|
}
|
|
if summary.FilesFound != 1 || summary.FilesFailed != 1 || summary.FilesParsed != 0 {
|
|
t.Fatalf("unexpected summary: %+v", summary)
|
|
}
|
|
for _, name := range []string{"bad.zip.cpu-audit.json", "_summary.json"} {
|
|
if _, err := os.Stat(filepath.Join(output, name)); err != nil {
|
|
t.Fatalf("expected %s: %v", name, err)
|
|
}
|
|
}
|
|
}
|