Fix the try on 63980bf5: keep a test database's name within postgres's 63-byte limit so two calls in one long-named test never collide (issue 1517)

This commit is contained in:
jochen
2026-10-21 01:30:52 +02:00
parent 63980bf5ac
commit f89b73d936
2 changed files with 45 additions and 5 deletions
@@ -0,0 +1,41 @@
package main
import (
"crypto/sha256"
"fmt"
"testing"
)
// integrationDatabaseName builds a one-off postgres database name for an integration test: unique (n, a
// counter that only ever grows, sits at its very end, where nothing can truncate it away) and never over
// 63 bytes — postgres's own identifier limit (NAMEDATALEN - 1) — however long testName is. A short hash of
// testName keeps the name traceable to its test without spending the length budget a long test name would
// (issue 1517, fixed per the lead's try feedback on 63980bf5): the earlier name,
// "mesh_mail_reader_test_"+lower(testName)+"_"+n, ran past 63 bytes for
// TestPrepareOnADatabaseAtMigration0001AppliesWhatFollows, and postgres silently truncated both of that
// test's two calls (n=1, n=2) to the SAME 63-byte name — the second call's own `drop ... with (force)`
// then dropped the first call's database while its store was still connected to it, "terminating
// connection due to administrator command" (SQLSTATE 57P01). TestIntegrationDatabaseNameStaysUniqueAndWithinPostgresLimit
// proves both properties, with no database, including for that exact test's own long name.
func integrationDatabaseName(testName string, n int64) string {
sum := sha256.Sum256([]byte(testName))
return fmt.Sprintf("mmr_test_%x_%d", sum[:4], n)
}
// TestIntegrationDatabaseNameStaysUniqueAndWithinPostgresLimit (1517, fixed per the lead's try feedback on
// 63980bf5): for a very long test name — the exact one whose two calls collided in try 63980bf5 — two
// successive counters give two different names, and both stay at or under postgres's own 63-byte limit. A
// plain unit test (no `//go:build integration`, no database) so it runs on every `go test ./...`.
func TestIntegrationDatabaseNameStaysUniqueAndWithinPostgresLimit(t *testing.T) {
const long = "TestPrepareOnADatabaseAtMigration0001AppliesWhatFollowsAndThenSomeEvenLongerSuffixToBeSure"
a := integrationDatabaseName(long, 1)
b := integrationDatabaseName(long, 2)
if a == b {
t.Fatalf("two different counters gave the same name %q: the second drop would kill the first's connection (SQLSTATE 57P01)", a)
}
for _, name := range []string{a, b} {
if len(name) > 63 {
t.Fatalf("name %q is %d bytes, want at most 63 (postgres's own identifier limit)", name, len(name))
}
}
}
@@ -10,7 +10,6 @@ package main
import (
"context"
"fmt"
"net/url"
"os"
"reflect"
@@ -32,9 +31,9 @@ func testDatabaseURL(t *testing.T) string {
// freshDatabaseCounter makes freshIntegrationDatabase's own name unique across every call, even two calls
// within the SAME test (t.Name() alone is not: TestPrepareOnADatabaseAtMigration0001AppliesWhatFollows
// calls it twice, for "fresh" and "stepped", and two databases of the same name collided — the first
// call's own pool was still open on it when the second tried to drop and recreate it, "database ... is
// being accessed by other users", SQLSTATE 55006; the coordinator's own fix for issue 1517).
// calls it twice, for "fresh" and "stepped") — combined with integrationDatabaseName's own 63-byte limit
// (issue 1517, fixed per the lead's try feedback on 63980bf5: this test's own long name, truncated by
// postgres past that limit, once made both calls' names the same).
var freshDatabaseCounter atomic.Int64
// freshIntegrationDatabase makes a new, empty database on the server MESH_TEST_POSTGRES names, and returns
@@ -51,7 +50,7 @@ func freshIntegrationDatabase(t *testing.T) string {
}
defer admin.Close()
n := freshDatabaseCounter.Add(1)
name := fmt.Sprintf("mesh_mail_reader_test_%s_%d", strings.ToLower(strings.ReplaceAll(t.Name(), "/", "_")), n)
name := integrationDatabaseName(t.Name(), n)
if _, err := admin.Exec(ctx, "drop database if exists "+name+" with (force)"); err != nil {
t.Fatal(err)
}