Files
zeekayandHanzo Dev 9926a57bcc the same answers are asserted on both engines, and a release cannot land on a build
Base serves its data plane on SQLite and on a server, and only one of them was
ever run. So a corpus builds every fixture through Base's own API, carries no
engine in it, and runs on both: matching without regard to case, truth values,
numbers, numbers reached through a JSON document, paging, sorting, a collection
created down to its columns and declared index, a field added to a live
collection and queried at once, eight concurrent writers, settings that survive
a restart without being stored in the clear, and a backup that refuses where it
cannot hold the database.

A test app is handed a schema carved out of the named server, which is a
millisecond against eighty-five for a database, and the catalog is read through
the current schema, so the isolation is the same. The server is named to the
tests and to nothing else — where a Base lives is still the host's question,
answered in code. Unreachable is a failure rather than a fall back to a file,
because a gate that tests one engine twice reports the health of a gate.

Ordering by text is left out on purpose: bytes on one engine, a collation the
operator chose on the other, so it is not Base's answer to assert.

A release tag is refused at or below what is published, and the free number is
named. A version publishes verbatim and as a bare alias, while a build between
releases derives the next free patch — so the two climb the same series, and a
release below the mark would have republished a build under a name someone had
already pinned. The gate runs before the push, so the collision cannot happen
rather than being reported after it has.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-13 16:00:58 -07:00

84 lines
2.7 KiB
Go

package tests
import (
"database/sql"
"fmt"
"os"
"strings"
"sync/atomic"
_ "github.com/jackc/pgx/v5/stdlib"
)
// BASE_TEST_POSTGRES names a PostgreSQL server for the suite to run against.
// Unset — the ordinary case, and what `go test ./...` does — every test app
// opens the embedded SQLite file exactly as before.
//
// It is read HERE and nowhere else. Where a Base keeps its data is the host's
// question, answered by core.BaseAppConfig.DataDSN, and base's own startup path
// reads no environment to decide it. This is the test harness answering that
// question for itself, which is a different thing from Base answering it.
//
// The value is a DSN for a server, not for one database — the harness carves
// its own space out of it:
//
// BASE_TEST_POSTGRES='postgres://postgres:[email protected]:5432/postgres?sslmode=disable'
var postgres = os.Getenv("BASE_TEST_POSTGRES")
// Postgres reports whether the suite is running against a PostgreSQL server.
// A test that asserts a spelling only one engine has consults it; a test that
// asserts an ANSWER should not need to.
func Postgres() bool { return postgres != "" }
// schemas counts the schemas handed out by this process. Two processes test
// concurrently — `go test ./...` builds one binary per package and runs them in
// parallel — so the pid joins the count to keep the names apart.
var schemas atomic.Int64
// postgresSchema carves a fresh, empty schema out of the server and returns a
// DSN pointed at it, plus the release.
//
// A schema rather than a database because it is three orders of magnitude
// cheaper — measured at ~1ms against ~85ms for the fastest CREATE DATABASE, and
// the suite asks for hundreds. It isolates as completely for this purpose: the
// dialect reads the catalog through current_schema(), so an app sees its own
// tables and no others.
//
// A failure here is fatal rather than a fallback to SQLite. Being told to run
// on PostgreSQL and quietly running on a file is how a green gate comes to mean
// nothing.
func postgresSchema() (string, func()) {
if postgres == "" {
return "", nil
}
name := fmt.Sprintf("hz_test_%d_%d", os.Getpid(), schemas.Add(1))
if err := exec("CREATE SCHEMA " + name); err != nil {
panic(fmt.Errorf("tests: creating schema %s: %w", name, err))
}
sep := "?"
if strings.Contains(postgres, "?") {
sep = "&"
}
return postgres + sep + "search_path=" + name, func() {
// Best effort: the schema is gone with the server when CI ends, and a
// test that has already reported should not fail on the way out.
_ = exec("DROP SCHEMA " + name + " CASCADE")
}
}
func exec(query string) error {
db, err := sql.Open("pgx", postgres)
if err != nil {
return err
}
defer db.Close()
_, err = db.Exec(query)
return err
}