feat(pg): pre-beta no-silent-loss guards + DDL-drift test coverage (#555 #556 #559 #591) #512
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |