Skip to content

feat(pg): pre-beta no-silent-loss guards + DDL-drift test coverage (#555 #556 #559 #591) #512

feat(pg): pre-beta no-silent-loss guards + DDL-drift test coverage (#555 #556 #559 #591)

feat(pg): pre-beta no-silent-loss guards + DDL-drift test coverage (#555 #556 #559 #591) #512

Workflow file for this run

name: CI
on:
push:
branches: [main]
pull_request:
jobs:
unit:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v5
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
- name: go vet
run: go vet ./...
# Staleness guard for THIRD-PARTY-NOTICES (license compliance, #428).
# Keys off `go list -m all` (the module graph), NOT a go-licenses regen:
# regenerating in CI would mean a slow CGO build on every PR plus
# cross-platform nondeterminism (per-OS duckdb binding modules). If a
# dependency was added/removed/bumped without `make notices`, this fails
# with an actionable message.
- name: THIRD-PARTY-NOTICES staleness check
run: make check-notices
- name: gofmt (warning-only)
# The repo has pre-existing unformatted files; surface the list as a
# warning but do not fail CI on it. Tighten to a hard fail once the
# backlog is cleared.
run: gofmt -l . || true
continue-on-error: true
- name: Unit tests
run: go test ./... -race -count=1
integration:
runs-on: ubuntu-22.04
# Two supported cells: the BYO contract floor (8.0) and the bundled index
# engine (8.4, where mysql_native_password is disabled by default so every
# connection authenticates with caching_sha2_password over the plaintext
# network). Running the whole suite on 8.4 is what proves the native_password-
# OFF default doesn't break the up→stream→query→shim pipeline; the focused
# TestCachingSha2ColdAuthSeam additionally guards the cold full-auth path on
# both cells. fail-fast: false so a regression on one version still reports
# the other.
strategy:
fail-fast: false
matrix:
mysql: ["8.0", "8.4"]
env:
# Matches internal/testutil.DefaultDSN — set explicitly to mirror the
# SaaS repo's `agent/` job and make the wiring obvious in logs.
BINTRAIL_TEST_DSN: "root:testroot@tcp(127.0.0.1:13306)"
steps:
- uses: actions/checkout@v5
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
# Start MySQL as a manually-named container rather than a `services:`
# block. The integration tests `docker cp` raw binlog files out of a
# container named exactly `bintrail-test-mysql`, and they need ROW-format
# binlogs with a known basename — neither of which a `services:` block can
# provide (service containers get auto-generated names and can't take
# command-line args). This mirrors the local setup in CONTRIBUTING.md so
# CI and developer machines run against an identical container.
- name: Start MySQL (bintrail-test-mysql, ${{ matrix.mysql }})
run: |
docker run -d --name bintrail-test-mysql \
-e MYSQL_ROOT_PASSWORD=testroot \
-p 13306:3306 \
mysql:${{ matrix.mysql }} \
--binlog-format=ROW \
--binlog-row-image=FULL \
--log-bin=binlog \
--server-id=1
echo "Waiting for MySQL to accept connections..."
for i in $(seq 1 60); do
if docker exec bintrail-test-mysql mysqladmin ping -u root -ptestroot --silent 2>/dev/null; then
echo "MySQL is ready."
exit 0
fi
sleep 2
done
echo "MySQL did not become ready in time" >&2
docker logs bintrail-test-mysql >&2 || true
exit 1
# -p 1 serializes package execution. Integration tests share one MySQL
# instance with a single server-wide binlog; running packages in parallel
# lets one package's DDL/DML leak into another's binlog window (the binlog
# parser tests assert exact event counts). Serializing keeps each package
# the sole writer during its run.
- name: Integration tests (-tags=integration)
run: go test -tags=integration -race -p 1 -count=1 ./...
integration-mariadb-source:
# MariaDB-as-source alpha. A MariaDB-source test pairs a NEW MariaDB SOURCE
# container (13307) with the existing MySQL INDEX container (13306) — two
# containers a single matrix cell of the `integration` job cannot host, and
# bolting MariaDB into that job would also perturb the shared-binlog window
# the parser count-assertions depend on (#415). So it gets its own job, and
# the 8.0/8.4 matrix above stays byte-identical. Runs only the MariaDB-named
# tests (-run); the MySQL-only MariaDB tests (sqlmock/index-side) also run in
# the matrix job, where the live-replication test skips for lack of a source.
runs-on: ubuntu-22.04
env:
BINTRAIL_TEST_DSN: "root:testroot@tcp(127.0.0.1:13306)"
BINTRAIL_TEST_MARIADB_DSN: "root:testroot@tcp(127.0.0.1:13307)"
# A MariaDB source is guaranteed present in this job, so the MariaDB tests
# must FAIL (not silently skip) if they can't run — otherwise a handshake
# regression would pass as green-via-skip. The 8.0/8.4 matrix leaves this
# unset, so the same tests skip politely there.
BINTRAIL_REQUIRE_MARIADB: "1"
steps:
- uses: actions/checkout@v5
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
- name: Start MySQL index (bintrail-test-mysql, 8.4)
run: |
docker run -d --name bintrail-test-mysql \
-e MYSQL_ROOT_PASSWORD=testroot \
-p 13306:3306 \
mysql:8.4 \
--binlog-format=ROW --binlog-row-image=FULL --log-bin=binlog --server-id=1
for i in $(seq 1 60); do
if docker exec bintrail-test-mysql mysqladmin ping -u root -ptestroot --silent 2>/dev/null; then
echo "MySQL index is ready."; exit 0
fi
sleep 2
done
echo "MySQL did not become ready in time" >&2
docker logs bintrail-test-mysql >&2 || true
exit 1
- name: Start MariaDB source (bintrail-test-mariadb, 11.4)
run: |
docker run -d --name bintrail-test-mariadb \
-e MARIADB_ROOT_PASSWORD=testroot \
-p 13307:3306 \
mariadb:11.4 \
--log-bin=mariadb-bin --binlog-format=ROW --binlog-row-image=FULL \
--server-id=2 --gtid-strict-mode=ON
for i in $(seq 1 60); do
if docker exec bintrail-test-mariadb mariadb-admin ping -u root -ptestroot --silent 2>/dev/null; then
echo "MariaDB source is ready."; exit 0
fi
sleep 2
done
echo "MariaDB did not become ready in time" >&2
docker logs bintrail-test-mariadb >&2 || true
exit 1
# -run selects the MariaDB-named tests across all packages; -p 1 serializes
# so the shared binlog window stays single-writer (same rationale as the
# MySQL integration job).
- name: MariaDB-source integration tests
run: go test -tags=integration -race -p 1 -count=1 -run 'MariaDB|Mariadb|mariadb|Flavor|FourColumns' ./...
integration-postgres-source:
# PostgreSQL-as-source (#534). Like integration-mariadb-source, this pairs a
# NEW PostgreSQL SOURCE container with the existing MySQL INDEX container
# (13306): two containers a single `integration` matrix cell can't host, and
# the PG logical-replication tests need wal_level=logical + replication slots
# the MySQL index container can't provide. The end-to-end test
# (TestOne_EndToEnd_PostgresToIndexToRecovery) streams PG changes INTO the
# MySQL index and generates recovery SQL, so both servers must be present.
runs-on: ubuntu-22.04
# Spans the supported PostgreSQL range: 14 (declared minimum — PG13 is EOL)
# through 17. fail-fast: false so a regression on one version still reports
# the others, matching the MySQL matrix. (PG18-specific stream changes — e.g.
# STORED generated columns now in the WAL — are tracked with the beta work.)
strategy:
fail-fast: false
matrix:
postgres: ["14", "15", "16", "17"]
env:
BINTRAIL_TEST_DSN: "root:testroot@tcp(127.0.0.1:13306)"
# Host port 15432 (not 5432) avoids colliding with any PostgreSQL the
# runner image ships; the container-internal port stays 5432. sslmode is
# disabled — the stock postgres image serves plaintext. replDSN appends
# replication=database onto this for the WAL connection.
BINTRAIL_TEST_PG_DSN: "postgres://postgres:testpg@127.0.0.1:15432/pgtest?sslmode=disable"
# A PostgreSQL source is guaranteed present here, so the PG tests must FAIL
# (not silently skip) if the DSN is missing — guards against green-via-skip.
# The 8.0/8.4 matrix and console-e2e leave it unset, so PG tests skip there.
BINTRAIL_REQUIRE_POSTGRES: "1"
steps:
- uses: actions/checkout@v5
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
- name: Start MySQL index (bintrail-test-mysql, 8.4)
run: |
docker run -d --name bintrail-test-mysql \
-e MYSQL_ROOT_PASSWORD=testroot \
-p 13306:3306 \
mysql:8.4 \
--binlog-format=ROW --binlog-row-image=FULL --log-bin=binlog --server-id=1
for i in $(seq 1 60); do
if docker exec bintrail-test-mysql mysqladmin ping -u root -ptestroot --silent 2>/dev/null; then
echo "MySQL index is ready."; exit 0
fi
sleep 2
done
echo "MySQL did not become ready in time" >&2
docker logs bintrail-test-mysql >&2 || true
exit 1
- name: Start PostgreSQL source (bintrail-test-postgres, ${{ matrix.postgres }})
run: |
docker run -d --name bintrail-test-postgres \
-e POSTGRES_PASSWORD=testpg \
-e POSTGRES_DB=pgtest \
-p 15432:5432 \
postgres:${{ matrix.postgres }} \
-c wal_level=logical -c max_replication_slots=10 -c max_wal_senders=10
# Probe TCP from inside the container: during first-init the entrypoint
# runs a temporary unix-socket-only server, so a TCP (-h 127.0.0.1)
# pg_isready only succeeds once the REAL server is up — avoiding the
# false-ready race a socket probe would hit.
for i in $(seq 1 60); do
if docker exec bintrail-test-postgres pg_isready -h 127.0.0.1 -U postgres -d pgtest --quiet 2>/dev/null; then
echo "PostgreSQL source is ready."; exit 0
fi
sleep 2
done
echo "PostgreSQL did not become ready in time" >&2
docker logs bintrail-test-postgres >&2 || true
exit 1
# Scoped to the two PostgreSQL packages by path (not -run) so the selection
# is naming-independent — it won't accidentally catch a future TestOne_*-
# style test added elsewhere. -p 1 serializes (the tests share one server,
# creating/dropping their own slots/publications).
#
# Both packages ALSO hold non-tagged unit tests, so a build-tag or path
# drift that silently dropped the integration tests would still exit 0
# green with ZERO PG coverage (the BINTRAIL_REQUIRE_POSTGRES guard only
# fires when an integration test actually runs). Close that false-green by
# asserting the headline end-to-end test genuinely ran — not merely that
# `go test` exited 0. pipefail keeps the go-test exit code through the tee.
- name: PostgreSQL-source integration tests
run: |
set -o pipefail
go test -tags=integration -race -p 1 -count=1 -v \
./internal/pgcapture/ ./internal/pgstreamrun/ 2>&1 | tee /tmp/pg-it.out
grep -q -- '--- PASS: TestOne_EndToEnd_PostgresToIndexToRecovery' /tmp/pg-it.out \
|| { echo "FATAL: PG end-to-end integration test did not run — false-green tripwire" >&2; exit 1; }
console-e2e:
# Headless-Chrome regression guard for the console SPA: go test never
# renders assets/*, so CSS/DOM/presentation bugs (invisible buttons, a
# form that auto-expands, a raw error wall, the control plane vanishing on
# a broken server selection) are invisible to the Go suite. Each scenario
# pins a bug that shipped in 0.13.3.
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v5
- uses: actions/setup-go@v5
with:
go-version-file: go.mod
- uses: actions/setup-node@v4
with:
node-version: "20"
# Same manually-named container as the integration job (8.4 = the bundled
# index engine, caching_sha2_password default).
- name: Start MySQL (bintrail-test-mysql, 8.4)
run: |
docker run -d --name bintrail-test-mysql \
-e MYSQL_ROOT_PASSWORD=testroot \
-p 13306:3306 \
mysql:8.4 \
--binlog-format=ROW --binlog-row-image=FULL --log-bin=binlog --server-id=1
for i in $(seq 1 60); do
if docker exec bintrail-test-mysql mysqladmin ping -u root -ptestroot --silent 2>/dev/null; then
echo "MySQL is ready."; exit 0
fi
sleep 2
done
echo "MySQL did not become ready in time" >&2
docker logs bintrail-test-mysql >&2 || true
exit 1
- name: Install playwright chromium (+ system deps)
run: |
npm --prefix test/console-e2e install --no-audit --no-fund
npx --prefix test/console-e2e playwright install --with-deps chromium
# PW_CHANNEL unset → use the chromium installed above (not system Chrome).
- name: Console E2E (headless Chrome)
run: make console-e2e
- name: Upload failure screenshot
if: failure()
uses: actions/upload-artifact@v4
with:
name: console-e2e-failure
path: |
${{ runner.temp }}/console-e2e-failure.png
${{ runner.temp }}/console-e2e-daemon.log
if-no-files-found: ignore