package src import ( "os" "path/filepath" "slices" "strings" "testing" ) // srcinfoFixtures is the committed testdata tree, relative to this package // directory (internal/arch/src). const srcinfoFixtures = "../../../testdata/srcinfo" func readFixture(t *testing.T, path string) string { t.Helper() b, err := os.ReadFile(path) if err != nil { t.Fatalf("read fixture %s: %v", path, err) } return string(b) } // TestSrcInfoParseMytoolSRCINFO parses the committed mytool .SRCINFO with // arch x86_64 and checks every field the converter consumes. func TestSrcInfoParseMytoolSRCINFO(t *testing.T) { src := readFixture(t, filepath.Join(srcinfoFixtures, "mytool", ".SRCINFO")) got, err := ParseSRCINFO(src, "x86_64") if err != nil { t.Fatalf("ParseSRCINFO: %v", err) } if got.PkgBase != "mytool" { t.Errorf("PkgBase = %q, want %q", got.PkgBase, "mytool") } if !slices.Equal(got.PkgNames, []string{"mytool"}) { t.Errorf("PkgNames = %v, want [mytool]", got.PkgNames) } if got.Version != "1.0" || got.Rel != "1" { t.Errorf("Version/Rel = %q/%q, want %q/%q", got.Version, got.Rel, "1.0", "1") } if got.Desc != "Example custom-build package" { t.Errorf("Desc = %q, want %q", got.Desc, "Example custom-build package") } if got.URL != "https://example.org" { t.Errorf("URL = %q, want %q", got.URL, "https://example.org") } if !slices.Equal(got.Arch, []string{"x86_64"}) { t.Errorf("Arch = %v, want [x86_64]", got.Arch) } if !slices.Equal(got.Depends, []string{"glibc"}) { t.Errorf("Depends = %v, want [glibc]", got.Depends) } if !slices.Equal(got.Sources, []string{"mytool-1.0.tar.gz"}) { t.Errorf("Sources = %v, want [mytool-1.0.tar.gz]", got.Sources) } if !slices.Equal(got.SHA256Sums, []string{"SKIP"}) { t.Errorf("SHA256Sums = %v, want [SKIP]", got.SHA256Sums) } } // TestSrcInfoParseMytoolPKGBUILD exercises the best-effort bash fallback on // the same package and expects the essential fields to agree. func TestSrcInfoParseMytoolPKGBUILD(t *testing.T) { src := readFixture(t, filepath.Join(srcinfoFixtures, "mytool", "PKGBUILD")) got, err := ParsePKGBUILD(src) if err != nil { t.Fatalf("ParsePKGBUILD: %v", err) } if !slices.Equal(got.PkgNames, []string{"mytool"}) { t.Errorf("PkgNames = %v, want [mytool]", got.PkgNames) } if got.Version != "1.0" || got.Rel != "1" { t.Errorf("Version/Rel = %q/%q, want %q/%q", got.Version, got.Rel, "1.0", "1") } if got.Desc != "Example custom-build package" { t.Errorf("Desc = %q, want %q", got.Desc, "Example custom-build package") } if got.URL != "https://example.org" { t.Errorf("URL = %q, want %q", got.URL, "https://example.org") } if !slices.Equal(got.Arch, []string{"x86_64"}) { t.Errorf("Arch = %v, want [x86_64]", got.Arch) } if !slices.Equal(got.Depends, []string{"glibc"}) { t.Errorf("Depends = %v, want [glibc]", got.Depends) } if !slices.Equal(got.Sources, []string{"mytool-1.0.tar.gz"}) { t.Errorf("Sources = %v, want [mytool-1.0.tar.gz]", got.Sources) } if !slices.Equal(got.SHA256Sums, []string{"SKIP"}) { t.Errorf("SHA256Sums = %v, want [SKIP]", got.SHA256Sums) } } // TestSrcInfoParseSplitPKGBUILD: a split package must PARSE fine (rejection // happens later in the converter), yielding both pkgnames. func TestSrcInfoParseSplitPKGBUILD(t *testing.T) { src := readFixture(t, filepath.Join(srcinfoFixtures, "split", "PKGBUILD")) got, err := ParsePKGBUILD(src) if err != nil { t.Fatalf("ParsePKGBUILD: %v", err) } if !slices.Equal(got.PkgNames, []string{"foo", "bar"}) { t.Errorf("PkgNames = %v, want [foo bar]", got.PkgNames) } if got.PkgBase != "foobar" { t.Errorf("PkgBase = %q, want %q", got.PkgBase, "foobar") } if got.Version != "1.0" { t.Errorf("Version = %q, want %q", got.Version, "1.0") } } // TestSrcInfoParseVCSPKGBUILD: VCS sources must come through verbatim so the // converter can detect and reject them with a clear message. func TestSrcInfoParseVCSPKGBUILD(t *testing.T) { src := readFixture(t, filepath.Join(srcinfoFixtures, "vcs", "PKGBUILD")) got, err := ParsePKGBUILD(src) if err != nil { t.Fatalf("ParsePKGBUILD: %v", err) } if !slices.Equal(got.PkgNames, []string{"mygit"}) { t.Errorf("PkgNames = %v, want [mygit]", got.PkgNames) } if !slices.Contains(got.Sources, "git+https://example.org/repo.git") { t.Errorf("Sources = %v, want it to contain %q", got.Sources, "git+https://example.org/repo.git") } } // TestSrcInfoArchFilter feeds a .SRCINFO with arch-specific keys and checks // that only the requested arch's variants are folded in. func TestSrcInfoArchFilter(t *testing.T) { src := `pkgbase = demo pkgver = 2.0 pkgrel = 3 epoch = 1 depends = bash depends_x86_64 = zsh depends_aarch64 = sh source_x86_64 = demo-2.0.tar.gz source_aarch64 = demo-2.0-aarch64.tar.gz sha256sums_x86_64 = SKIP sha256sums_aarch64 = DEADBEEF pkgname = demo ` got, err := ParseSRCINFO(src, "x86_64") if err != nil { t.Fatalf("ParseSRCINFO(x86_64): %v", err) } if !slices.Equal(got.Depends, []string{"bash", "zsh"}) { t.Errorf("Depends(x86_64) = %v, want [bash zsh] (aarch64 variant must NOT leak in)", got.Depends) } if !slices.Equal(got.Sources, []string{"demo-2.0.tar.gz"}) { t.Errorf("Sources(x86_64) = %v, want [demo-2.0.tar.gz]", got.Sources) } if !slices.Equal(got.SHA256Sums, []string{"SKIP"}) { t.Errorf("SHA256Sums(x86_64) = %v, want [SKIP]", got.SHA256Sums) } if got.Version != "2.0" || got.Rel != "3" || got.Epoch != "1" { t.Errorf("Version/Rel/Epoch = %q/%q/%q, want %q/%q/%q", got.Version, got.Rel, got.Epoch, "2.0", "3", "1") } got, err = ParseSRCINFO(src, "aarch64") if err != nil { t.Fatalf("ParseSRCINFO(aarch64): %v", err) } if !slices.Equal(got.Depends, []string{"bash", "sh"}) { t.Errorf("Depends(aarch64) = %v, want [bash sh]", got.Depends) } if !slices.Equal(got.Sources, []string{"demo-2.0-aarch64.tar.gz"}) { t.Errorf("Sources(aarch64) = %v, want [demo-2.0-aarch64.tar.gz]", got.Sources) } if !slices.Equal(got.SHA256Sums, []string{"DEADBEEF"}) { t.Errorf("SHA256Sums(aarch64) = %v, want [DEADBEEF]", got.SHA256Sums) } } // TestSrcInfoMultiPkgnameSRCINFO: multiple pkgname sections must not fail // parsing; every name is collected, only the FIRST section's keys apply. func TestSrcInfoMultiPkgnameSRCINFO(t *testing.T) { src := `pkgbase = split pkgver = 1.0 pkgrel = 1 pkgname = foo depends = a pkgname = bar depends = b ` got, err := ParseSRCINFO(src, "x86_64") if err != nil { t.Fatalf("ParseSRCINFO: %v", err) } if !slices.Equal(got.PkgNames, []string{"foo", "bar"}) { t.Errorf("PkgNames = %v, want [foo bar]", got.PkgNames) } if !slices.Equal(got.Depends, []string{"a"}) { t.Errorf("Depends = %v, want [a] (only the first pkgname section applies)", got.Depends) } } // TestSrcInfoPKGBUILDMissingPkgver: a PKGBUILD without a usable pkgver // declaration is malformed and must error. func TestSrcInfoPKGBUILDMissingPkgver(t *testing.T) { src := `pkgname=foo pkgrel=1 pkgdesc="no version here" arch=(x86_64) depends=('glibc') ` got, err := ParsePKGBUILD(src) if err == nil { t.Fatalf("ParsePKGBUILD = %+v, want error for missing pkgver", got) } if !strings.Contains(err.Error(), "pkgver") { t.Errorf("error %q does not mention pkgver", err) } } // TestSrcInfoLoadDirPrefersSRCINFO: loading a directory uses .SRCINFO first // (PkgBase is only present in the .SRCINFO fixture, not the PKGBUILD). func TestSrcInfoLoadDirPrefersSRCINFO(t *testing.T) { got, err := Load(filepath.Join(srcinfoFixtures, "mytool"), "x86_64") if err != nil { t.Fatalf("Load: %v", err) } if got.PkgBase != "mytool" { t.Errorf("PkgBase = %q, want %q (proves .SRCINFO was preferred over PKGBUILD)", got.PkgBase, "mytool") } if got.Version != "1.0" { t.Errorf("Version = %q, want %q", got.Version, "1.0") } } // TestSrcInfoLoadDirFallsBackToPKGBUILD: a directory with only a PKGBUILD // falls back to the best-effort parser. func TestSrcInfoLoadDirFallsBackToPKGBUILD(t *testing.T) { got, err := Load(filepath.Join(srcinfoFixtures, "split"), "x86_64") if err != nil { t.Fatalf("Load: %v", err) } if !slices.Equal(got.PkgNames, []string{"foo", "bar"}) { t.Errorf("PkgNames = %v, want [foo bar]", got.PkgNames) } } // TestSrcInfoLoadFilePKGBUILD: loading a file named PKGBUILD parses it as // such regardless of directory. func TestSrcInfoLoadFilePKGBUILD(t *testing.T) { got, err := Load(filepath.Join(srcinfoFixtures, "vcs", "PKGBUILD"), "x86_64") if err != nil { t.Fatalf("Load: %v", err) } if !slices.Equal(got.PkgNames, []string{"mygit"}) { t.Errorf("PkgNames = %v, want [mygit]", got.PkgNames) } } // TestSrcInfoLoadFileSRCINFO: loading a file named .SRCINFO parses it as // such. func TestSrcInfoLoadFileSRCINFO(t *testing.T) { got, err := Load(filepath.Join(srcinfoFixtures, "mytool", ".SRCINFO"), "x86_64") if err != nil { t.Fatalf("Load: %v", err) } if got.PkgBase != "mytool" { t.Errorf("PkgBase = %q, want %q", got.PkgBase, "mytool") } } // TestSrcInfoLoadUnknownFile: a file that is neither .SRCINFO nor PKGBUILD // is rejected with a clear error. func TestSrcInfoLoadUnknownFile(t *testing.T) { dir := t.TempDir() other := filepath.Join(dir, "notes.txt") if err := os.WriteFile(other, []byte("pkgname=foo\npkgver=1\n"), 0o644); err != nil { t.Fatalf("WriteFile: %v", err) } _, err := Load(other, "x86_64") if err == nil { t.Fatalf("Load(%s) = nil error, want unknown-file error", other) } if !strings.Contains(err.Error(), "unknown") { t.Errorf("error %q does not mention the unknown file type", err) } } // TestSrcInfoLoadMissingDir: a nonexistent path errors clearly. func TestSrcInfoLoadMissingDir(t *testing.T) { _, err := Load(filepath.Join(t.TempDir(), "does-not-exist"), "x86_64") if err == nil { t.Fatalf("Load(nonexistent) = nil error, want error") } }