diff --git a/internal/testutil/testdb/db.go b/internal/testutil/testdb/db.go index 2f507690f..2903d19c9 100644 --- a/internal/testutil/testdb/db.go +++ b/internal/testutil/testdb/db.go @@ -99,8 +99,8 @@ func TestDBURL() string { const maxPostgresIdentifier = 63 // derivedDBSuffix derives the database-name suffix beneath the configured base: -// a per-WORKSPACE component plus a per-PACKAGE component, or "" when the process -// is not a test binary or sharing is forced. +// a per-WORKSPACE component plus a per-PACKAGE component, or "" when sharing +// is forced. // // Two dimensions, because per-package alone was not enough. It stops packages in // ONE run from truncating each other, but every checkout computed the same names, @@ -111,6 +111,13 @@ const maxPostgresIdentifier = 63 // makes the isolation structural: two checkouts cannot collide even when nobody // configures anything. // +// Derived for every binary, not only `go test` ones: a plain binary built from +// this module (cmd/e2a-contract-server is the one that calls into the test +// harness) previously fell through to the base URL verbatim, so two contract +// server processes on one machine shared and truncated each other's database. +// os.Args[0] for a non-test binary is just its own name (no ".test" suffix), +// which still yields a distinct, stable per-binary component. +// // Name length: _ws<8>_pkg_ runs ~40 chars for this repo's longest // package names, well inside Postgres's 63-byte identifier limit. A much longer // custom base could push past it, where Postgres truncates silently — keep bases @@ -121,9 +128,6 @@ func derivedDBSuffix() string { return "" } bin := filepath.Base(os.Args[0]) - if !strings.HasSuffix(bin, ".test") { - return "" - } name := strings.ToLower(strings.TrimSuffix(bin, ".test")) sanitized := make([]rune, 0, len(name)) for _, r := range name { diff --git a/internal/testutil/testdb/db_test.go b/internal/testutil/testdb/db_test.go index 4e46dafd4..761c03fb5 100644 --- a/internal/testutil/testdb/db_test.go +++ b/internal/testutil/testdb/db_test.go @@ -7,6 +7,7 @@ import ( "net/url" "os" "os/exec" + "path/filepath" "strings" "testing" "time" @@ -418,6 +419,53 @@ func TestTestDBURLDerivesPerPackageDatabase(t *testing.T) { } } +// TestTestDBURLDerivesForNonTestBinaries proves the derivation also covers +// cmd/e2a-contract-server (see #827): a symlink to this test binary under a +// non-test name reproduces a real non-".test" argv[0]. +func TestTestDBURLDerivesForNonTestBinaries(t *testing.T) { + if os.Getenv(testDBErrorChildEnv) == t.Name() { + u, err := url.Parse(TestDBURL()) + if err != nil { + t.Fatalf("parse TestDBURL: %v", err) + } + fmt.Printf("DBNAME=%s\n", strings.TrimPrefix(u.Path, "/")) + return + } + + self, err := filepath.Abs(os.Args[0]) + if err != nil { + t.Fatalf("resolve this test binary's path: %v", err) + } + renamed := filepath.Join(t.TempDir(), "e2a-contract-server") + if err := os.Symlink(self, renamed); err != nil { + t.Fatalf("symlink the test binary under a non-test name: %v", err) + } + + cmd := exec.Command(renamed, "-test.run=^"+t.Name()+"$") + cmd.Env = testDBChildEnv(t.Name(), "postgres://e2a:e2a@localhost:5433/e2a_test?sslmode=disable") + output, err := cmd.CombinedOutput() + if err != nil { + t.Fatalf("child failed: %v\n%s", err, output) + } + + var dbname string + for _, line := range strings.Split(string(output), "\n") { + if rest, ok := strings.CutPrefix(line, "DBNAME="); ok { + dbname = rest + break + } + } + if dbname == "" { + t.Fatalf("child did not report a dbname:\n%s", output) + } + if !strings.Contains(dbname, "_ws") { + t.Errorf("dbname = %q from a non-test binary, want a _ws workspace component", dbname) + } + if !strings.HasSuffix(dbname, "_pkg_e2a_contract_server") { + t.Errorf("dbname = %q, want the _pkg_e2a_contract_server suffix", dbname) + } +} + func TestOpenPreparedTestDBCreatesMissingDatabase(t *testing.T) { // First use of a package database must self-provision: connect failure // with SQLSTATE 3D000 creates the database from the base URL's server