Artifact 80431cabf6ffdf5f20b97c7768e2763facb0c70c04039aed1a97fd8f9b8157a6:
- File
fossil_test.go
— part of check-in
[f804878bd3]
at
2026-07-22 23:58:46
on branch trunk
— test: route every fossil-binary gate through one REQUIRE_FOSSIL_BIN helper (#125)
* test: route fossil-binary tests through one REQUIRE_FOSSIL_BIN helper
REQUIRE_FOSSIL_BIN=1 turns a missing fossil binary into a test failure instead of an invisible skip, but only internal/manifest honored it. The six other fossil-dependent sites each did their own exec.LookPath + unconditional t.Skip, so on those a run with no fossil was byte-identical to a passing one.
Add testutil.RequireFossilBin, which resolves the binary the same way FossilBinary does (FOSSIL_BIN override, then PATH) and either skips or fails based on REQUIRE_FOSSIL_BIN. Route testutil.NewTestRepo, both internal/delta CLI round-trips, the four internal/merge validation tests, the internal/xfer sanity check, and the ls-branch-tip repro through it, deleting their bespoke lookup+skip logic. CI already sets REQUIRE_FOSSIL_BIN=1 on the unit-test steps, so this makes all seven sites fail loud on absence while local runs without fossil stay opt-in.
* test: route every fossil-binary gate through RequireFossilBin
The first pass left six sites still deciding for themselves whether a missing fossil binary is a skip or a failure. Three of them (internal/sync's startFossilServer and its two integration tests, manifest's two rebuild tests, the root TestPhaseB_Integration, and hash's TestSHA1_FossilValidation) skipped unconditionally, so with REQUIRE_FOSSIL_BIN=1 and no fossil on PATH those packages still reported ok -- the exact silent pass the flag exists to prevent. The other three had hand-rolled copies of the flag check, which is the duplication that let the original drift happen.
All of them now call testutil.RequireFossilBin, and HasFossil is gone: it was a second, skip-only way to ask the same question, and it is what produced the sites that ignored the flag.
Also validate the FOSSIL_BIN override with LookPath, so a stale path reports "fossil not found" instead of surfacing later as a raw fork/exec error.
* test: resolve fossil binary via RequireFossilBin at remaining call sites
* fix: correct stale module path missed by rebase auto-merge
Three imports added by earlier commits in this branch predated the rebase target and had no corresponding line on main to conflict against, so git's auto-merge carried the old module path through verbatim. (user: [email protected], size: 4334) [annotate] [blame] [check-ins using]
package libfossil import ( "bytes" "os" "os/exec" "path/filepath" "regexp" "strings" "testing" _ "github.com/danmestas/go-libfossil/internal/testdriver" "github.com/danmestas/go-libfossil/testutil" ) func TestCreateAndOpen(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "test.fossil") r, err := Create(path, CreateOpts{User: "test"}) if err != nil { t.Fatal(err) } if r.Path() != path { t.Errorf("Path() = %q, want %q", r.Path(), path) } if err := r.Close(); err != nil { t.Fatal(err) } if _, err := os.Stat(path); err != nil { t.Fatal("repo file should exist after Create") } r2, err := Open(path) if err != nil { t.Fatal(err) } defer r2.Close() if r2.Path() != path { t.Errorf("Path() = %q, want %q", r2.Path(), path) } } func TestCreateAlreadyExists(t *testing.T) { dir := t.TempDir() path := filepath.Join(dir, "test.fossil") r, err := Create(path, CreateOpts{User: "test"}) if err != nil { t.Fatal(err) } r.Close() _, err = Create(path, CreateOpts{User: "test"}) if err == nil { t.Error("Create on existing repo should fail") } } func TestOpenNotFound(t *testing.T) { _, err := Open("/nonexistent/path.fossil") if err == nil { t.Error("Open on nonexistent path should fail") } } func TestCreate_ProjectCode_Explicit(t *testing.T) { const code = "0123456789abcdef0123456789abcdef01234567" path := filepath.Join(t.TempDir(), "test.fossil") r, err := Create(path, CreateOpts{User: "test", ProjectCode: code}) if err != nil { t.Fatalf("Create: %v", err) } if err := r.Close(); err != nil { t.Fatal(err) } r2, err := Open(path) if err != nil { t.Fatalf("Open: %v", err) } defer r2.Close() got, err := r2.inner.Config("project-code") if err != nil { t.Fatalf("Config(project-code): %v", err) } if got != code { t.Errorf("project-code = %q, want %q", got, code) } } func TestCreate_ProjectCode_Invalid(t *testing.T) { cases := []struct { name string code string }{ {"too short", "deadbeef"}, {"uppercase", strings.Repeat("DEADBEEF", 5)}, {"non-hex char", strings.Repeat("g", 40)}, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { path := filepath.Join(t.TempDir(), "test.fossil") _, err := Create(path, CreateOpts{User: "test", ProjectCode: tc.code}) if err == nil { t.Fatalf("Create with ProjectCode=%q should fail", tc.code) } if !strings.Contains(err.Error(), "invalid CreateOpts.ProjectCode") { t.Errorf("error %q missing 'invalid CreateOpts.ProjectCode'", err) } if _, statErr := os.Stat(path); statErr == nil { t.Error("repo file should not exist after rejected Create") } }) } } // TestCreate_ProjectCode_FossilValidation creates a Go-side repo with an // explicit ProjectCode, then runs vanilla `fossil rebuild` against it and // reads the project-code back via `fossil sql` to confirm vanilla Fossil // accepts the value and stores it as-is. func TestCreate_ProjectCode_FossilValidation(t *testing.T) { if testing.Short() { t.Skip("skipping fossil validation") } fossilBin := testutil.RequireFossilBin(t) const code = "0123456789abcdef0123456789abcdef01234567" path := filepath.Join(t.TempDir(), "test.fossil") r, err := Create(path, CreateOpts{User: "test", ProjectCode: code}) if err != nil { t.Fatalf("Create: %v", err) } if err := r.Close(); err != nil { t.Fatalf("Close: %v", err) } out, err := exec.Command(fossilBin, "rebuild", path).CombinedOutput() if err != nil { t.Fatalf("fossil rebuild failed: %v\n%s", err, out) } out, err = exec.Command(fossilBin, "sql", "-R", path, "SELECT value FROM config WHERE name='project-code';").Output() if err != nil { t.Fatalf("fossil sql: %v", err) } got := string(bytes.Trim(out, "'\n \t")) if got != code { t.Errorf("project-code via fossil sql = %q, want %q", got, code) } } func TestCreate_ProjectCode_EmptyGeneratesHex(t *testing.T) { path := filepath.Join(t.TempDir(), "test.fossil") r, err := Create(path, CreateOpts{User: "test"}) if err != nil { t.Fatalf("Create: %v", err) } defer r.Close() code, err := r.inner.Config("project-code") if err != nil { t.Fatalf("Config(project-code): %v", err) } if !regexp.MustCompile(`^[0-9a-f]{40}$`).MatchString(code) { t.Errorf("generated project-code %q does not match ^[0-9a-f]{40}$", code) } }