diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index eb28da6..7220b8d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -103,3 +103,38 @@ jobs: with: files: ./coverage.txt token: ${{ secrets.CODECOV_TOKEN }} + + test-mariadb: + name: MySQL suite on MariaDB + runs-on: ubuntu-latest + timeout-minutes: 10 + + services: + mariadb: + image: mariadb:11.4 + env: + MARIADB_ROOT_PASSWORD: root + options: >- + --health-cmd "healthcheck.sh --connect --innodb_initialized" + --health-interval 5s + --health-timeout 5s + --health-retries 12 + ports: + - 3306:3306 + + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 + + - name: Set up Go + uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e + with: + go-version: '1.27' + cache: true + + # The mysql package and the adapters' MySQL tests, against MariaDB on + # the default MySQL address. The Postgres-backed tests are the other + # job's business. + - name: Run the MySQL-backed tests against MariaDB + run: | + go test -race -count=1 ./mysql/... + go test -race -count=1 -run 'TestMySQL' ./migrate/goose/... ./migrate/golangmigrate/... diff --git a/Makefile b/Makefile index 2acd63e..752b517 100644 --- a/Makefile +++ b/Makefile @@ -61,6 +61,7 @@ POSTGRES_DB := testdb MYSQL_SERVICE := test-mysql MYSQL_USER := root MYSQL_PASSWORD := root +MARIADB_SERVICE := test-mariadb .PHONY: check_pgcli check_pgcli: @@ -103,6 +104,23 @@ define wait_for_mysql echo "✅ MySQL is ready!" endef +# The MariaDB image ships the mariadb client, not mysql. +define wait_for_mariadb + @echo "Waiting for MariaDB to be ready..." + @attempts=0; \ + while ! docker compose -f $(1) exec -T $(2) mariadb -u$(3) -p$(4) -e 'SELECT 1' >/dev/null 2>&1; do \ + attempts=$$((attempts+1)); \ + if [ $$attempts -gt 60 ]; then \ + echo "❌ MariaDB failed to become ready after 60 seconds"; \ + echo "Try 'make $(5)' for a fresh start"; \ + exit 1; \ + fi; \ + echo "⏳ Waiting... ($$attempts/60)"; \ + sleep 1; \ + done; \ + echo "✅ MariaDB is ready (port 3307; set TEST_MYSQL_URL to use it)" +endef + ## test/db/start: Start PostgreSQL test database .PHONY: test/db/start test/db/start: @@ -110,6 +128,7 @@ test/db/start: docker compose -f $(POSTGRES_COMPOSE_FILE) up -d $(call wait_for_postgres,$(POSTGRES_COMPOSE_FILE),$(POSTGRES_SERVICE),$(POSTGRES_USER),$(POSTGRES_DB),test/db/restart) $(call wait_for_mysql,$(POSTGRES_COMPOSE_FILE),$(MYSQL_SERVICE),$(MYSQL_USER),$(MYSQL_PASSWORD),test/db/restart) + $(call wait_for_mariadb,$(POSTGRES_COMPOSE_FILE),$(MARIADB_SERVICE),$(MYSQL_USER),$(MYSQL_PASSWORD),test/db/restart) ## test/db/stop: Stop PostgreSQL test database .PHONY: test/db/stop @@ -141,6 +160,7 @@ test/db/restart: test/db/clean docker compose -f $(POSTGRES_COMPOSE_FILE) up -d $(call wait_for_postgres,$(POSTGRES_COMPOSE_FILE),$(POSTGRES_SERVICE),$(POSTGRES_USER),$(POSTGRES_DB),test/db/restart) $(call wait_for_mysql,$(POSTGRES_COMPOSE_FILE),$(MYSQL_SERVICE),$(MYSQL_USER),$(MYSQL_PASSWORD),test/db/restart) + $(call wait_for_mariadb,$(POSTGRES_COMPOSE_FILE),$(MARIADB_SERVICE),$(MYSQL_USER),$(MYSQL_PASSWORD),test/db/restart) ## test/db/psql: Connect to Postgres database .PHONY: test/db/psql diff --git a/docker-compose.test.yml b/docker-compose.test.yml index a58d1ed..b4ad3ad 100644 --- a/docker-compose.test.yml +++ b/docker-compose.test.yml @@ -43,3 +43,22 @@ services: - "--skip-log-bin" - "--innodb-doublewrite=0" - "--innodb-flush-method=O_DIRECT_NO_FSYNC" + + # MariaDB on 3307: point TEST_MYSQL_URL at it to run the MySQL suite + # against MariaDB, as CI does. + test-mariadb: + image: mariadb:11.4 + environment: + MARIADB_ROOT_PASSWORD: root + ports: + - "3307:3306" + healthcheck: + test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"] + interval: 5s + timeout: 3s + retries: 12 + command: + - "--innodb-flush-log-at-trx-commit=0" + - "--skip-log-bin" + - "--innodb-doublewrite=0" + - "--innodb-flush-method=O_DIRECT_NO_FSYNC" diff --git a/mysql/template.go b/mysql/template.go index 5760afa..9e4e76b 100644 --- a/mysql/template.go +++ b/mysql/template.go @@ -553,9 +553,14 @@ func lockName(template string) string { return "testdb:template:" + hex.EncodeToString(sum[:12]) } +// buildLockTimeout bounds GET_LOCK when ctx has no deadline. MySQL treats a +// negative timeout as "forever" but MariaDB returns NULL for it, so a long +// finite wait is used on both; ctx still cancels the statement earlier. +const buildLockTimeout = 24 * time.Hour + // lockBuild takes the template's build lock, waiting as long as ctx allows. func lockBuild(ctx context.Context, conn *sql.Conn, template string) error { - timeout := -1 // forever, as far as MySQL is concerned; ctx still applies + timeout := int(buildLockTimeout.Seconds()) if deadline, ok := ctx.Deadline(); ok { timeout = max(int(time.Until(deadline).Seconds()), 1) }