package firecracker import ( "strings" "testing" ) func TestParseVersionOutput(t *testing.T) { t.Parallel() for _, tc := range []struct { name string input string want SemVer wantErr bool }{ {name: "canonical", input: "Firecracker v1.14.1\n", want: SemVer{Major: 1, Minor: 14, Patch: 1}}, {name: "with_trailing_log", input: "Firecracker v1.14.1\n\n2026-04-28T17:38:12.392171332 [anonymous-instance:main] exit_code=0\n", want: SemVer{Major: 1, Minor: 14, Patch: 1}}, {name: "prerelease", input: "Firecracker v1.10.0-rc1\n", want: SemVer{Major: 1, Minor: 10, Patch: 0, PreRelease: "rc1"}}, {name: "two_digit_minor", input: "Firecracker v2.0.42\n", want: SemVer{Major: 2, Minor: 0, Patch: 42}}, {name: "garbage", input: "not a firecracker", wantErr: true}, {name: "empty", input: "", wantErr: true}, {name: "missing_v", input: "Firecracker 1.14.1", wantErr: true}, } { tc := tc t.Run(tc.name, func(t *testing.T) { t.Parallel() got, err := ParseVersionOutput(tc.input) if tc.wantErr { if err == nil { t.Fatalf("ParseVersionOutput(%q) succeeded, want error", tc.input) } return } if err != nil { t.Fatalf("ParseVersionOutput(%q) = %v", tc.input, err) } if got != tc.want { t.Fatalf("ParseVersionOutput(%q) = %+v, want %+v", tc.input, got, tc.want) } }) } } func TestSemVerCompare(t *testing.T) { t.Parallel() for _, tc := range []struct { name string a, b string want int }{ {name: "equal", a: "1.14.1", b: "1.14.1", want: 0}, {name: "patch_lower", a: "1.14.0", b: "1.14.1", want: -1}, {name: "patch_higher", a: "1.14.2", b: "1.14.1", want: 1}, {name: "minor_dominates_patch", a: "1.10.999", b: "1.11.0", want: -1}, {name: "major_dominates", a: "2.0.0", b: "1.99.99", want: 1}, {name: "min_vs_tested_today", a: MinSupportedVersion, b: KnownTestedVersion, want: -1}, } { tc := tc t.Run(tc.name, func(t *testing.T) { t.Parallel() a := MustParseSemVer(tc.a) b := MustParseSemVer(tc.b) if got := a.Compare(b); got != tc.want { t.Fatalf("(%s).Compare(%s) = %d, want %d", tc.a, tc.b, got, tc.want) } }) } } func TestSemVerString(t *testing.T) { t.Parallel() if got := MustParseSemVer("1.14.1").String(); got != "v1.14.1" { t.Fatalf("v1.14.1.String() = %q", got) } pre := SemVer{Major: 1, Minor: 10, Patch: 0, PreRelease: "rc1"} if got := pre.String(); got != "v1.10.0-rc1" { t.Fatalf("rc String() = %q", got) } } // MustParseSemVer panics on malformed input; pin a few inputs so a // future refactor doesn't accidentally widen what counts as valid. func TestMustParseSemVerRejectsMalformed(t *testing.T) { t.Parallel() for _, bad := range []string{"", "1", "1.2", "1.2.3.4", "v1.2.x", "vfoo"} { bad := bad t.Run(strings.ReplaceAll(bad, ".", "_"), func(t *testing.T) { defer func() { if r := recover(); r == nil { t.Errorf("MustParseSemVer(%q) did not panic", bad) } }() _ = MustParseSemVer(bad) }) } }