package delivery import ( "strings" "testing" ) // The new form's inputs (novox/hq ADR 0429 clause 5 as ADR 0437 clause 5 changed it, ADR 0405 clause 7). The base and // builder values are module fingerprints in the new form; a lock is the caller's hash of a lock file, which this // function only enters (its form is the builder's to settle; the values here are stand-in tokens); the toolchain is a // language, the installed toolchain's version and the build module's recipe version. func inputs2() SourceInputs2 { return SourceInputs2{ Module: "mesh-mail", Tree: "t-module", Bases: []string{SourcePrefix2 + strings.Repeat("a", 64), SourcePrefix2 + strings.Repeat("b", 64)}, Builder: SourcePrefix2 + strings.Repeat("c", 64), Contexts: map[string]string{"mail": "t-root", "tool": "t-root2"}, Locks: map[string]string{"mail": "sha256:1111", "tool": "sha256:2222"}, Toolchains: map[string]Toolchain2{ "mail": {Language: "go", Version: "1.26.0", Recipe: "3"}, "tool": {Language: "typescript", Version: "5.9.2", Recipe: "1"}, }, } } func mustFP2(t *testing.T, in SourceInputs2) string { t.Helper() fp, none := SourceFingerprint2(in) if fp == "" || none != "" { t.Fatalf("fingerprint = %q, none = %q", fp, none) } return fp } // 1356/AC1: a fixed input has the fingerprint the test fixes, with the new prefix. The hex was computed from the function and checked against an independent sha256 of the lines; // it is pinned here, so a change of the lines is a change of every fingerprint, and shows. func TestFingerprint2Vector(t *testing.T) { const want = "src2:56b6f2f6ffc1767225e0b277ab78539521e9a9fbaadfe4ec158d2fddee1d63ef" if got := mustFP2(t, inputs2()); got != want { t.Errorf("fingerprint = %s, want %s", got, want) } if SourcePrefix2 != "src2:" || SourcePrefix == SourcePrefix2 { t.Errorf("the new prefix is %q", SourcePrefix2) } } // 1356/AC2: each input on its own changes the fingerprint. func TestFingerprint2Changes(t *testing.T) { want := mustFP2(t, inputs2()) for name, f := range map[string]func(*SourceInputs2){ "module": func(in *SourceInputs2) { in.Module = "other" }, "tree": func(in *SourceInputs2) { in.Tree = "t-other" }, "a base": func(in *SourceInputs2) { in.Bases[0] = SourcePrefix2 + strings.Repeat("d", 64) }, "a base added": func(in *SourceInputs2) { in.Bases = append(in.Bases, SourcePrefix2+strings.Repeat("e", 64)) }, "the builder": func(in *SourceInputs2) { in.Builder = SourcePrefix2 + strings.Repeat("f", 64) }, "a lock": func(in *SourceInputs2) { in.Locks["mail"] = "sha256:changed" }, "a lock added": func(in *SourceInputs2) { in.Locks["extra"] = "sha256:3333" }, "a toolchain": func(in *SourceInputs2) { in.Toolchains["mail"] = Toolchain2{"go", "1.26.1", "3"} }, "a toolchain lang": func(in *SourceInputs2) { in.Toolchains["mail"] = Toolchain2{"typescript", "1.26.0", "3"} }, "a recipe": func(in *SourceInputs2) { in.Toolchains["mail"] = Toolchain2{"go", "1.26.0", "4"} }, "a context": func(in *SourceInputs2) { in.Contexts["mail"] = "t-changed" }, "a context added": func(in *SourceInputs2) { in.Contexts["extra"] = "t-root" }, "a toolchain added": func(in *SourceInputs2) { in.Toolchains["extra"] = Toolchain2{"go", "1.26.0", "3"} }, } { in := inputs2() f(&in) if got := mustFP2(t, in); got == want { t.Errorf("a changed %s keeps the fingerprint", name) } } } // 1356/AC3: a base given twice, and maps in any order, give the same fingerprint. func TestFingerprint2OrderAndDuplicates(t *testing.T) { want := mustFP2(t, inputs2()) in := inputs2() in.Bases = []string{in.Bases[1], in.Bases[0]} if got := mustFP2(t, in); got != want { t.Errorf("the order of bases changes it: %s", got) } in = inputs2() in.Bases = append(in.Bases, in.Bases[0], in.Bases[1]) if got := mustFP2(t, in); got != want { t.Errorf("a base given twice changes it: %s", got) } for i := 0; i < 20; i++ { in = inputs2() in.Contexts, in.Locks, in.Toolchains = map[string]string{}, map[string]string{}, map[string]Toolchain2{} for _, k := range []string{"tool", "mail"} { in.Contexts[k] = inputs2().Contexts[k] in.Locks[k] = inputs2().Locks[k] in.Toolchains[k] = inputs2().Toolchains[k] } if got := mustFP2(t, in); got != want { t.Fatalf("map order changes it: %s", got) } } } // 1356/AC4: an artifact that resolves packages when built, with no lock, gives no fingerprint and the reason "no lock"; // a module with no tree gets none with today's reason, and the reason the source is unpinned stands over both. func TestFingerprint2None(t *testing.T) { in := inputs2() in.Locks["mail"] = "" if fp, none := SourceFingerprint2(in); fp != "" || none != "no lock" { t.Errorf("no lock: fp = %q, none = %q", fp, none) } in = inputs2() in.Tree = "" if fp, none := SourceFingerprint2(in); fp != "" || none != "no tree" { t.Errorf("no tree: fp = %q, none = %q", fp, none) } in = inputs2() in.Tree, in.Unpinned = "", "a context is a Go program's build source" if _, none := SourceFingerprint2(in); none != in.Unpinned { t.Errorf("the reason for unpinned stands over no tree: %q", none) } } // 1356/AC5: src1 is unchanged by the new form; its vectors in fingerprint_test.go run unchanged. This pins that the // two forms do not collide for equal module and tree, and that src1 keeps its prefix. func TestSource1StaysSource1(t *testing.T) { if SourcePrefix != "src1:" { t.Fatalf("src1's prefix is %q", SourcePrefix) } fp1, _ := SourceFingerprint(SourceInputs{Module: "m", Tree: "t"}) fp2, _ := SourceFingerprint2(SourceInputs2{Module: "m", Tree: "t"}) if fp1 == "" || fp2 == "" || strings.TrimPrefix(fp1, SourcePrefix) == strings.TrimPrefix(fp2, SourcePrefix2) { t.Errorf("src1 %q and src2 %q", fp1, fp2) } } // 1356/AC6: the key part holds only bus key characters and parsing gives the fingerprint back; a src1 or malformed // value is refused, naming it. func TestFingerprintKeyPart(t *testing.T) { fp := mustFP2(t, inputs2()) part, err := FingerprintKeyPart(fp) if err != nil { t.Fatal(err) } if len(part) != 64 || strings.Trim(part, "0123456789abcdef") != "" || !nameOK.MatchString(part) { t.Errorf("key part %q is not 64 lowercase hex characters a key allows", part) } back, err := ParseFingerprintKeyPart(part) if err != nil || back != fp { t.Errorf("parse(%q) = %q, %v; want %q", part, back, err, fp) } src1, _ := SourceFingerprint(SourceInputs{Module: "m", Tree: "t"}) for _, bad := range []string{src1, "src2:", "src2:xyz", "", "src2:" + strings.Repeat("A", 64), "src2:" + strings.Repeat("a", 63)} { if _, err := FingerprintKeyPart(bad); err == nil || !strings.Contains(err.Error(), "\""+bad+"\"") { t.Errorf("FingerprintKeyPart(%q) = %v, want a refusal naming it", bad, err) } } for _, bad := range []string{src1, "src2:" + strings.Repeat("a", 64), strings.Repeat("a", 63), strings.Repeat("A", 64), "", "zz"} { if _, err := ParseFingerprintKeyPart(bad); err == nil || !strings.Contains(err.Error(), "\""+bad+"\"") { t.Errorf("ParseFingerprintKeyPart(%q) = %v, want a refusal naming it", bad, err) } } }