feat(cmd): add logpile-cpu-audit offline CPU identity audit CLI
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>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
a430a8c46f
commit
fc68134ed9
@@ -0,0 +1,130 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user