package hash import ( "os" "os/exec" "path/filepath" "strings" "testing" ) // TestSingleFileMatchesSha256sumCutC116 asserts the central parity claim: // Derive([f], 16) == `sha256sum f | cut -c1-16`. This is what the existing // shell helper does (HASH=$(sha256sum docker/conformance.Dockerfile | cut -c1-16)) // and the new tool must produce the same key for the same input. func TestSingleFileMatchesSha256sumCutC116(t *testing.T) { if _, err := exec.LookPath("sha256sum"); err != nil { t.Skip("sha256sum not on PATH; skipping shell parity check") } path := filepath.Join(t.TempDir(), "sample") if err := os.WriteFile(path, []byte("hello world\n"), 0o600); err != nil { t.Fatal(err) } out, err := exec.Command("sha256sum", path).Output() if err != nil { t.Fatalf("sha256sum: %v", err) } want := strings.Fields(string(out))[0][:16] got, err := Derive([]string{path}, 16) if err != nil { t.Fatalf("Derive: %v", err) } if got != want { t.Errorf("Derive=%q want=%q", got, want) } } func TestDirHashIsStable(t *testing.T) { dir := t.TempDir() mustWrite := func(rel, body string) { p := filepath.Join(dir, rel) if err := os.MkdirAll(filepath.Dir(p), 0o700); err != nil { t.Fatal(err) } if err := os.WriteFile(p, []byte(body), 0o600); err != nil { t.Fatal(err) } } mustWrite("a/x", "x-body") mustWrite("b/y/z", "z-body") h1, err := Derive([]string{dir}, 32) if err != nil { t.Fatal(err) } h2, err := Derive([]string{dir}, 32) if err != nil { t.Fatal(err) } if h1 != h2 { t.Errorf("dir hash not stable: %q vs %q", h1, h2) } } func TestDirHashChangesOnContentChange(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "f") _ = os.WriteFile(path, []byte("v1"), 0o600) h1, _ := Derive([]string{dir}, 32) _ = os.WriteFile(path, []byte("v2"), 0o600) h2, _ := Derive([]string{dir}, 32) if h1 == h2 { t.Error("dir hash unchanged after content change") } } func TestMultiPathCombines(t *testing.T) { dir := t.TempDir() a := filepath.Join(dir, "a") b := filepath.Join(dir, "b") _ = os.WriteFile(a, []byte("A"), 0o600) _ = os.WriteFile(b, []byte("B"), 0o600) ha, _ := Derive([]string{a}, 16) hab, _ := Derive([]string{a, b}, 16) hba, _ := Derive([]string{b, a}, 16) if ha == hab { t.Error("combining two paths produced same hash as single path") } if hab == hba { t.Error("order of --hash-from paths should matter") } } func TestApplyTemplateHashPlaceholder(t *testing.T) { got := ApplyTemplate("img/{hash}.tar.zst", "abcd1234", "linux", "amd64", false) if got != "img/abcd1234.tar.zst" { t.Errorf("ApplyTemplate placeholder = %q", got) } } func TestApplyTemplateNoPlaceholderAppends(t *testing.T) { got := ApplyTemplate("img/build.tar.zst", "abcd1234", "linux", "amd64", false) if got != "img/build-abcd1234.tar.zst" { t.Errorf("ApplyTemplate append = %q", got) } } func TestApplyTemplateArchSuffix(t *testing.T) { got := ApplyTemplate("img/build.tar.zst", "abcd", "linux", "amd64", true) if got != "img/build-abcd-linux-amd64.tar.zst" { t.Errorf("ApplyTemplate arch = %q", got) } } func TestApplyTemplateArchSuffixSimpleExt(t *testing.T) { got := ApplyTemplate("bin/cacher", "", "linux", "amd64", true) if got != "bin/cacher-linux-amd64" { t.Errorf("ApplyTemplate arch no-ext = %q", got) } } func TestDeriveRejectsEmpty(t *testing.T) { if _, err := Derive(nil, 16); err == nil { t.Error("expected error for empty paths") } }