ci: comprehensive CI workflow and infrastructure fixes

Workflow trigger chain fix:
- All 24 test workflows now trigger on CI-builds completion instead
  of CI-trigger completion, ensuring build artifacts are available
  before tests start

New test group workflows:
- Add CI-legacy-g1, CI-legacy-g3, CI-legacy-g5
- Add CI-mysql84-g1 through CI-mysql84-g5
- All use the generic ci-taptests-groups.yml reusable workflow

groups.json linter:
- Add lint_groups_json.py enforcing compact one-line-per-entry format
- Add CI-lint-groups-json.yml workflow triggered on groups.json changes
- Reformat groups.json to comply with documented format

Makefile fixes:
- Fix $(error) call with unescaped ')' that broke all docker-compose
  builds since Release 3.0.8
- Fix CURVER extraction regex to handle 'v' prefix from git describe

CI-taptests-pgsql-cluster rewrite:
- Replace old jenkins-build-scripts with Unified CI infrastructure
- Use ensure-infras.bash + run-tests-isolated.bash

Infrastructure improvements:
- Add --hostname test-runner to test-runner container for cross-container
  DNS resolution
- Pass TAP_PGSQL_SYNC_REPLICA_PORT through to test container
- Default TAP_PGSQL_SYNC_REPLICA_PORT=6042 when cluster nodes active
pull/5590/head
Rene Cannao 1 month ago
parent 21b3e238ee
commit 9671a414a3

@ -9,7 +9,7 @@ on:
default: main
type: string
workflow_run:
workflows: [ CI-trigger ]
workflows: [ CI-builds ]
types: [ completed ]
concurrency:

@ -9,7 +9,7 @@ on:
default: main
type: string
workflow_run:
workflows: [ CI-trigger ]
workflows: [ CI-builds ]
types: [ completed ]
concurrency:

@ -9,7 +9,7 @@ on:
default: main
type: string
workflow_run:
workflows: [ CI-trigger ]
workflows: [ CI-builds ]
types: [ completed ]
concurrency:

@ -9,7 +9,7 @@ on:
default: main
type: string
workflow_run:
workflows: [ CI-trigger ]
workflows: [ CI-builds ]
types: [ completed ]
concurrency:

@ -9,7 +9,7 @@ on:
default: main
type: string
workflow_run:
workflows: [ CI-trigger ]
workflows: [ CI-builds ]
types: [ completed ]
concurrency:

@ -9,7 +9,7 @@ on:
default: main
type: string
workflow_run:
workflows: [ CI-trigger ]
workflows: [ CI-builds ]
types: [ completed ]
concurrency:

@ -9,7 +9,7 @@ on:
default: main
type: string
workflow_run:
workflows: [ CI-trigger ]
workflows: [ CI-builds ]
types: [ completed ]
concurrency:

@ -9,7 +9,7 @@ on:
default: main
type: string
workflow_run:
workflows: [ CI-trigger ]
workflows: [ CI-builds ]
types: [ completed ]
concurrency:

@ -9,7 +9,7 @@ on:
default: main
type: string
workflow_run:
workflows: [ CI-trigger ]
workflows: [ CI-builds ]
types: [ completed ]
concurrency:

@ -9,7 +9,7 @@ on:
default: main
type: string
workflow_run:
workflows: [ CI-trigger ]
workflows: [ CI-builds ]
types: [ completed ]
concurrency:

@ -4,7 +4,7 @@ run-name: '${{ github.event.workflow_run && github.event.workflow_run.head_branc
on:
workflow_dispatch:
workflow_run:
workflows: [ CI-trigger ]
workflows: [ CI-builds ]
types: [ completed ]
concurrency:

@ -4,7 +4,7 @@ run-name: '${{ github.event.workflow_run && github.event.workflow_run.head_branc
on:
workflow_dispatch:
workflow_run:
workflows: [ CI-trigger ]
workflows: [ CI-builds ]
types: [ completed ]
concurrency:

@ -0,0 +1,21 @@
name: CI-legacy-g1
run-name: '${{ github.event.workflow_run && github.event.workflow_run.head_branch || github.ref_name }} ${{ github.workflow }} ${{ github.event.workflow_run && github.event.workflow_run.head_sha || github.sha }}'
on:
workflow_dispatch:
workflow_run:
workflows: [ CI-builds ]
types: [ completed ]
concurrency:
group: ${{ github.workflow }}-${{ github.event.workflow_run && github.event.workflow_run.head_branch || github.ref_name }}
cancel-in-progress: true
jobs:
run:
if: ${{ github.event.workflow_run && github.event.workflow_run.conclusion == 'success' || ! github.event.workflow_run }}
uses: sysown/proxysql/.github/workflows/ci-taptests-groups.yml@GH-Actions
secrets: inherit
with:
trigger: ${{ toJson(github) }}
testgroup: '["legacy-g1"]'

@ -4,7 +4,7 @@ run-name: '${{ github.event.workflow_run && github.event.workflow_run.head_branc
on:
workflow_dispatch:
workflow_run:
workflows: [ CI-trigger ]
workflows: [ CI-builds ]
types: [ completed ]
concurrency:

@ -4,7 +4,7 @@ run-name: '${{ github.event.workflow_run && github.event.workflow_run.head_branc
on:
workflow_dispatch:
workflow_run:
workflows: [ CI-trigger ]
workflows: [ CI-builds ]
types: [ completed ]
concurrency:

@ -0,0 +1,21 @@
name: CI-legacy-g3
run-name: '${{ github.event.workflow_run && github.event.workflow_run.head_branch || github.ref_name }} ${{ github.workflow }} ${{ github.event.workflow_run && github.event.workflow_run.head_sha || github.sha }}'
on:
workflow_dispatch:
workflow_run:
workflows: [ CI-builds ]
types: [ completed ]
concurrency:
group: ${{ github.workflow }}-${{ github.event.workflow_run && github.event.workflow_run.head_branch || github.ref_name }}
cancel-in-progress: true
jobs:
run:
if: ${{ github.event.workflow_run && github.event.workflow_run.conclusion == 'success' || ! github.event.workflow_run }}
uses: sysown/proxysql/.github/workflows/ci-taptests-groups.yml@GH-Actions
secrets: inherit
with:
trigger: ${{ toJson(github) }}
testgroup: '["legacy-g3"]'

@ -4,7 +4,7 @@ run-name: '${{ github.event.workflow_run && github.event.workflow_run.head_branc
on:
workflow_dispatch:
workflow_run:
workflows: [ CI-trigger ]
workflows: [ CI-builds ]
types: [ completed ]
concurrency:

@ -0,0 +1,21 @@
name: CI-legacy-g5
run-name: '${{ github.event.workflow_run && github.event.workflow_run.head_branch || github.ref_name }} ${{ github.workflow }} ${{ github.event.workflow_run && github.event.workflow_run.head_sha || github.sha }}'
on:
workflow_dispatch:
workflow_run:
workflows: [ CI-builds ]
types: [ completed ]
concurrency:
group: ${{ github.workflow }}-${{ github.event.workflow_run && github.event.workflow_run.head_branch || github.ref_name }}
cancel-in-progress: true
jobs:
run:
if: ${{ github.event.workflow_run && github.event.workflow_run.conclusion == 'success' || ! github.event.workflow_run }}
uses: sysown/proxysql/.github/workflows/ci-taptests-groups.yml@GH-Actions
secrets: inherit
with:
trigger: ${{ toJson(github) }}
testgroup: '["legacy-g5"]'

@ -0,0 +1,17 @@
name: CI-lint-groups-json
on:
pull_request:
paths:
- 'test/tap/groups/groups.json'
push:
paths:
- 'test/tap/groups/groups.json'
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Lint groups.json format
run: python3 test/tap/groups/lint_groups_json.py

@ -4,7 +4,7 @@ run-name: '${{ github.event.workflow_run && github.event.workflow_run.head_branc
on:
workflow_dispatch:
workflow_run:
workflows: [ CI-trigger ]
workflows: [ CI-builds ]
types: [ completed ]
concurrency:

@ -0,0 +1,21 @@
name: CI-mysql84-g1
run-name: '${{ github.event.workflow_run && github.event.workflow_run.head_branch || github.ref_name }} ${{ github.workflow }} ${{ github.event.workflow_run && github.event.workflow_run.head_sha || github.sha }}'
on:
workflow_dispatch:
workflow_run:
workflows: [ CI-builds ]
types: [ completed ]
concurrency:
group: ${{ github.workflow }}-${{ github.event.workflow_run && github.event.workflow_run.head_branch || github.ref_name }}
cancel-in-progress: true
jobs:
run:
if: ${{ github.event.workflow_run && github.event.workflow_run.conclusion == 'success' || ! github.event.workflow_run }}
uses: sysown/proxysql/.github/workflows/ci-taptests-groups.yml@GH-Actions
secrets: inherit
with:
trigger: ${{ toJson(github) }}
testgroup: '["mysql84-g1"]'

@ -0,0 +1,21 @@
name: CI-mysql84-g2
run-name: '${{ github.event.workflow_run && github.event.workflow_run.head_branch || github.ref_name }} ${{ github.workflow }} ${{ github.event.workflow_run && github.event.workflow_run.head_sha || github.sha }}'
on:
workflow_dispatch:
workflow_run:
workflows: [ CI-builds ]
types: [ completed ]
concurrency:
group: ${{ github.workflow }}-${{ github.event.workflow_run && github.event.workflow_run.head_branch || github.ref_name }}
cancel-in-progress: true
jobs:
run:
if: ${{ github.event.workflow_run && github.event.workflow_run.conclusion == 'success' || ! github.event.workflow_run }}
uses: sysown/proxysql/.github/workflows/ci-taptests-groups.yml@GH-Actions
secrets: inherit
with:
trigger: ${{ toJson(github) }}
testgroup: '["mysql84-g2"]'

@ -0,0 +1,21 @@
name: CI-mysql84-g3
run-name: '${{ github.event.workflow_run && github.event.workflow_run.head_branch || github.ref_name }} ${{ github.workflow }} ${{ github.event.workflow_run && github.event.workflow_run.head_sha || github.sha }}'
on:
workflow_dispatch:
workflow_run:
workflows: [ CI-builds ]
types: [ completed ]
concurrency:
group: ${{ github.workflow }}-${{ github.event.workflow_run && github.event.workflow_run.head_branch || github.ref_name }}
cancel-in-progress: true
jobs:
run:
if: ${{ github.event.workflow_run && github.event.workflow_run.conclusion == 'success' || ! github.event.workflow_run }}
uses: sysown/proxysql/.github/workflows/ci-taptests-groups.yml@GH-Actions
secrets: inherit
with:
trigger: ${{ toJson(github) }}
testgroup: '["mysql84-g3"]'

@ -0,0 +1,21 @@
name: CI-mysql84-g4
run-name: '${{ github.event.workflow_run && github.event.workflow_run.head_branch || github.ref_name }} ${{ github.workflow }} ${{ github.event.workflow_run && github.event.workflow_run.head_sha || github.sha }}'
on:
workflow_dispatch:
workflow_run:
workflows: [ CI-builds ]
types: [ completed ]
concurrency:
group: ${{ github.workflow }}-${{ github.event.workflow_run && github.event.workflow_run.head_branch || github.ref_name }}
cancel-in-progress: true
jobs:
run:
if: ${{ github.event.workflow_run && github.event.workflow_run.conclusion == 'success' || ! github.event.workflow_run }}
uses: sysown/proxysql/.github/workflows/ci-taptests-groups.yml@GH-Actions
secrets: inherit
with:
trigger: ${{ toJson(github) }}
testgroup: '["mysql84-g4"]'

@ -0,0 +1,21 @@
name: CI-mysql84-g5
run-name: '${{ github.event.workflow_run && github.event.workflow_run.head_branch || github.ref_name }} ${{ github.workflow }} ${{ github.event.workflow_run && github.event.workflow_run.head_sha || github.sha }}'
on:
workflow_dispatch:
workflow_run:
workflows: [ CI-builds ]
types: [ completed ]
concurrency:
group: ${{ github.workflow }}-${{ github.event.workflow_run && github.event.workflow_run.head_branch || github.ref_name }}
cancel-in-progress: true
jobs:
run:
if: ${{ github.event.workflow_run && github.event.workflow_run.conclusion == 'success' || ! github.event.workflow_run }}
uses: sysown/proxysql/.github/workflows/ci-taptests-groups.yml@GH-Actions
secrets: inherit
with:
trigger: ${{ toJson(github) }}
testgroup: '["mysql84-g5"]'

@ -4,7 +4,7 @@ run-name: '${{ github.event.workflow_run && github.event.workflow_run.head_branc
on:
workflow_dispatch:
workflow_run:
workflows: [ CI-trigger ]
workflows: [ CI-builds ]
types: [ completed ]
concurrency:

@ -4,7 +4,7 @@ run-name: '${{ github.event.workflow_run && github.event.workflow_run.head_branc
on:
workflow_dispatch:
workflow_run:
workflows: [ CI-trigger ]
workflows: [ CI-builds ]
types: [ completed ]
concurrency:

@ -4,7 +4,7 @@ run-name: '${{ github.event.workflow_run && github.event.workflow_run.head_branc
on:
workflow_dispatch:
workflow_run:
workflows: [ CI-trigger ]
workflows: [ CI-builds ]
types: [ completed ]
concurrency:

@ -4,7 +4,7 @@ run-name: '${{ github.event.workflow_run && github.event.workflow_run.head_branc
on:
workflow_dispatch:
workflow_run:
workflows: [ CI-trigger ]
workflows: [ CI-builds ]
types: [ completed ]
concurrency:

@ -4,7 +4,7 @@ run-name: '${{ github.event.workflow_run && github.event.workflow_run.head_branc
on:
workflow_dispatch:
workflow_run:
workflows: [ CI-trigger ]
workflows: [ CI-builds ]
types: [ completed ]
concurrency:

@ -0,0 +1,93 @@
name: CI-taptests-pgsql-cluster
run-name: '${{ github.event.workflow_run && github.event.workflow_run.head_branch || github.ref_name }} ${{ github.workflow }} ${{ github.event.workflow_run && github.event.workflow_run.head_sha || github.sha }}'
on:
workflow_dispatch:
workflow_run:
workflows: [ CI-builds ]
types: [ completed ]
concurrency:
group: ${{ github.workflow }}-${{ github.event.workflow_run && github.event.workflow_run.head_branch || github.ref_name }}
cancel-in-progress: true
env:
SHA: ${{ github.event.workflow_run && github.event.workflow_run.head_sha || github.sha }}
jobs:
tests:
runs-on: ubuntu-22.04
if: ${{ github.event.workflow_run && github.event.workflow_run.conclusion == 'success' || ! github.event.workflow_run }}
strategy:
fail-fast: false
matrix:
testdist: [ 'ubuntu22-tap' ]
env:
TESTDIST: ${{ matrix.testdist }}
BLDCACHE: ${{ github.event.workflow_run && github.event.workflow_run.head_sha || github.sha }}_${{ matrix.testdist }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Cache restore src
id: cache-src
uses: actions/cache/restore@v4
with:
key: ${{ env.BLDCACHE }}_src
fail-on-cache-miss: true
path: |
src/
- name: Cache restore test
id: cache-test
uses: actions/cache/restore@v4
with:
key: ${{ env.BLDCACHE }}_test
fail-on-cache-miss: true
path: |
test/
- name: Build CI base image
run: |
cd test/infra/docker-base
docker build --network host -t proxysql-ci-base:latest .
- name: Start infrastructure
run: |
export WORKSPACE="${GITHUB_WORKSPACE}"
export INFRA_ID="pgsql-cluster-${GITHUB_RUN_ID}"
export TAP_GROUP="legacy-g3"
export TEST_PY_TAP_INCL="test_cluster_sync_pgsql-t"
export PROXYSQL_CLUSTER_NODES=2
source test/infra/common/env.sh
./test/infra/control/ensure-infras.bash
- name: Run test_cluster_sync_pgsql-t
run: |
export WORKSPACE="${GITHUB_WORKSPACE}"
export INFRA_ID="pgsql-cluster-${GITHUB_RUN_ID}"
export TAP_GROUP="legacy-g3"
export TEST_PY_TAP_INCL="test_cluster_sync_pgsql-t"
export PROXYSQL_CLUSTER_NODES=2
source test/infra/common/env.sh
./test/infra/control/run-tests-isolated.bash
- name: Cleanup
if: always()
run: |
export WORKSPACE="${GITHUB_WORKSPACE}"
export INFRA_ID="pgsql-cluster-${GITHUB_RUN_ID}"
export TAP_GROUP="legacy-g3"
source test/infra/common/env.sh
./test/infra/control/stop-proxysql-isolated.bash || true
./test/infra/control/destroy-infras.bash || true
- name: Archive logs
if: ${{ failure() && !cancelled() }}
uses: actions/upload-artifact@v4
with:
name: ${{ github.workflow }}-${{ env.SHA }}-logs-run${{ github.run_number }}
path: |
ci_infra_logs/

@ -4,7 +4,7 @@ run-name: '${{ github.event.workflow_run && github.event.workflow_run.head_branc
on:
workflow_dispatch:
workflow_run:
workflows: [ CI-trigger ]
workflows: [ CI-builds ]
types: [ completed ]
concurrency:

@ -4,7 +4,7 @@ run-name: '${{ github.event.workflow_run && github.event.workflow_run.head_branc
on:
workflow_dispatch:
workflow_run:
workflows: [ CI-trigger ]
workflows: [ CI-builds ]
types: [ completed ]
concurrency:

@ -72,13 +72,13 @@ endif
export GIT_VERSION
# Extract CURVER from GIT_VERSION (first 3 numbers, e.g., 3.0.6 from 3.0.6-388-ga94b7d6)
CURVER := $(shell echo "$(GIT_VERSION)" | sed -nE 's/^([0-9]+\.[0-9]+\.[0-9]+).*/\1/p' | head -1)
CURVER := $(shell echo "$(GIT_VERSION)" | sed -nE 's/^v?([0-9]+\.[0-9]+\.[0-9]+).*/\1/p' | head -1)
# Validate CURVER has 3 numbers separated by dots
CURVER_CHECK := $(shell echo "$(CURVER)" | grep -cE '^[0-9]+\.[0-9]+\.[0-9]+$$')
ifeq ($(CURVER_CHECK),0)
$(error CURVER "$(CURVER)" derived from GIT_VERSION "$(GIT_VERSION)" does not have 3 numbers separated by dots (expected format: X.Y.Z)
$(error CURVER "$(CURVER)" derived from GIT_VERSION "$(GIT_VERSION)" does not have 3 numbers separated by dots. Expected format: X.Y.Z)
endif
export CURVER

@ -108,6 +108,11 @@ export TEST_PY_TAP_DUMP_RUNTIME="${TEST_PY_TAP_DUMP_RUNTIME:-1}"
export TEST_PY_TAP_DUMP_STATS="${TEST_PY_TAP_DUMP_STATS:-1}"
export TEST_TAP_TIMEOUT="${TEST_TAP_TIMEOUT:-0}"
# Cluster sync test support — expose first cluster node admin port for replica validation
if [ "${NUM_CLUSTER_NODES}" -gt 0 ]; then
export TAP_PGSQL_SYNC_REPLICA_PORT="${TAP_PGSQL_SYNC_REPLICA_PORT:-6042}"
fi
# Noise injection for race condition testing
# When enabled, tests that support noise injection will introduce random delays
# and stress to help detect race conditions and deadlocks

@ -234,6 +234,7 @@ BINLOG_READER_BIN=$(find "${WORKSPACE}" -path "${WORKSPACE}/ci_infra_logs" -prun
# Execution: run the container
docker run \
--name "${TEST_CONTAINER}" \
--hostname "test-runner" \
--network "${NETWORK_NAME}" \
--cap-add=NET_ADMIN \
--cap-add=SYS_ADMIN \
@ -256,6 +257,7 @@ docker run \
-e MYSQL_BINLOG_BIN="${MYSQL_BINLOG_BIN}" \
-e BINLOG_READER_BIN="${BINLOG_READER_BIN}" \
-e TAP_USE_NOISE="${TAP_USE_NOISE:-0}" \
-e TAP_PGSQL_SYNC_REPLICA_PORT="${TAP_PGSQL_SYNC_REPLICA_PORT:-}" \
-e MULTI_GROUP="${MULTI_GROUP:-0}" \
-e GCOV_PREFIX="/gcov/tap" \
-e GCOV_PREFIX_STRIP="3" \

@ -188,14 +188,9 @@
"reg_test_3434-text_stmt_mix-t" : [ "legacy-g1","mysql84-g1","mysql-auto_increment_delay_multiplex=0-g1","mysql-multiplexing=false-g1","mysql-query_digests=0-g1","mysql-query_digests_keep_comment=1-g1" ],
"reg_test_3493-USE_with_comment-t" : [ "legacy-g1","mysql84-g1","mysql-auto_increment_delay_multiplex=0-g1","mysql-multiplexing=false-g1","mysql-query_digests=0-g1","mysql-query_digests_keep_comment=1-g1" ],
"reg_test_3504-change_user-t" : [ "legacy-g1","mysql84-g1","mysql-auto_increment_delay_multiplex=0-g1","mysql-multiplexing=false-g1","mysql-query_digests=0-g1","mysql-query_digests_keep_comment=1-g1" ],
"reg_test_handshake_response_unterminated_username-t" : [ "mysql84-g1" ],
"reg_test_com_change_user_malformed_packet-t" : [ "mysql84-g1" ],
"reg_test_3546-stmt_empty_params-t" : [ "legacy-g1","mysql84-g1","mysql-auto_increment_delay_multiplex=0-g1","mysql-multiplexing=false-g1","mysql-query_digests=0-g1","mysql-query_digests_keep_comment=1-g1" ],
"reg_test_3549-autocommit_tracking-t" : [ "legacy-g1","mysql84-g1","mysql-auto_increment_delay_multiplex=0-g1","mysql-multiplexing=false-g1","mysql-query_digests=0-g1","mysql-query_digests_keep_comment=1-g1" ],
"reg_test_proxy_protocol_oversized_address-t" : [ "mysql84-g1" ],
"reg_test_3585-stmt_metadata-t" : [ "legacy-g1","mysql84-g1","mysql-auto_increment_delay_multiplex=0-g1","mysql-multiplexing=false-g1","mysql-query_digests=0-g1","mysql-query_digests_keep_comment=1-g1" ],
"reg_test_stmt_send_long_data_short_packet-t" : [ "mysql84-g1" ],
"reg_test_stmt_close_short_packet-t" : [ "mysql84-g1" ],
"reg_test_3591-restapi_num_fds-t" : [ "legacy-g1","mysql84-g1","mysql-auto_increment_delay_multiplex=0-g1","mysql-multiplexing=false-g1","mysql-query_digests=0-g1","mysql-query_digests_keep_comment=1-g1" ],
"reg_test_3603-stmt_metadata-t" : [ "legacy-g1","mysql84-g1","mysql-auto_increment_delay_multiplex=0-g1","mysql-multiplexing=false-g1","mysql-query_digests=0-g1","mysql-query_digests_keep_comment=1-g1" ],
"reg_test_3606-mysql_warnings-t" : [ "legacy-g1","mysql84-g1","mysql-auto_increment_delay_multiplex=0-g1","mysql-multiplexing=false-g1","mysql-query_digests=0-g1","mysql-query_digests_keep_comment=1-g1" ],
@ -223,18 +218,23 @@
"reg_test_5306-show_warnings_with_comment-t" : [ "legacy-g2","mysql84-g2","mysql-auto_increment_delay_multiplex=0-g2","mysql-multiplexing=false-g2" ],
"reg_test_5389-flush_logs_no_drop-t" : [ "legacy-g4","mysql84-g4" ],
"reg_test__ssl_client_busy_wait-t" : [ "legacy-g2","mysql84-g2","mysql-auto_increment_delay_multiplex=0-g2","mysql-multiplexing=false-g2","mysql-query_digests=0-g2","mysql-query_digests_keep_comment=1-g2" ],
"reg_test_com_change_user_malformed_packet-t" : [ "mysql84-g1" ],
"reg_test_compression_split_packets-t" : [ "legacy-g2","mysql84-g2","mysql-auto_increment_delay_multiplex=0-g2","mysql-multiplexing=false-g2","mysql-query_digests=0-g2","mysql-query_digests_keep_comment=1-g2" ],
"reg_test_fast_forward_split_packet-t" : [ "legacy-g2","mysql84-g2","mysql-auto_increment_delay_multiplex=0-g2","mysql-multiplexing=false-g2","mysql-query_digests=0-g2","mysql-query_digests_keep_comment=1-g2" ],
"reg_test_handshake_response_unterminated_username-t" : [ "mysql84-g1" ],
"reg_test_mariadb_metadata_check-t" : [ "legacy-g2","mysql84-g2","mysql-auto_increment_delay_multiplex=0-g2","mysql-multiplexing=false-g2","mysql-query_digests=0-g2","mysql-query_digests_keep_comment=1-g2" ],
"reg_test_mariadb_stmt_store_result-t" : [ "legacy-g2","mysql84-g2","mysql-auto_increment_delay_multiplex=0-g2","mysql-multiplexing=false-g2","mysql-query_digests=0-g2","mysql-query_digests_keep_comment=1-g2" ],
"reg_test_mariadb_stmt_store_result_async-t" : [ "legacy-g2","mysql84-g2","mysql-auto_increment_delay_multiplex=0-g2","mysql-multiplexing=false-g2","mysql-query_digests=0-g2","mysql-query_digests_keep_comment=1-g2" ],
"reg_test_mariadb_stmt_store_result_libmysql-t" : [ "legacy-g2","mysql84-g2","mysql-auto_increment_delay_multiplex=0-g2","mysql-multiplexing=false-g2","mysql-query_digests=0-g2","mysql-query_digests_keep_comment=1-g2" ],
"reg_test_proclist_use_after_free-t" : [ "legacy-g1","mysql84-g1","mysql-auto_increment_delay_multiplex=0-g1","mysql-multiplexing=false-g1","mysql-query_digests=0-g1","mysql-query_digests_keep_comment=1-g1" ],
"reg_test_proxy_protocol_oversized_address-t" : [ "mysql84-g1" ],
"reg_test_sql_calc_found_rows-t" : [ "legacy-g2","mysql84-g2","mysql-auto_increment_delay_multiplex=0-g2","mysql-multiplexing=false-g2","mysql-query_digests=0-g2","mysql-query_digests_keep_comment=1-g2" ],
"reg_test_stmt_close_short_packet-t" : [ "mysql84-g1" ],
"reg_test_stmt_inv_param_offset-t" : [ "legacy-g2","mysql84-g2","mysql-auto_increment_delay_multiplex=0-g2","mysql-multiplexing=false-g2","mysql-query_digests=0-g2","mysql-query_digests_keep_comment=1-g2" ],
"reg_test_stmt_resultset_err_no_rows-t" : [ "legacy-g2","mysql84-g2","mysql-auto_increment_delay_multiplex=0-g2","mysql-multiplexing=false-g2","mysql-query_digests=0-g2","mysql-query_digests_keep_comment=1-g2" ],
"reg_test_stmt_resultset_err_no_rows_libmysql-t" : [ "legacy-g2","mysql84-g2","mysql-auto_increment_delay_multiplex=0-g2","mysql-multiplexing=false-g2","mysql-query_digests=0-g2","mysql-query_digests_keep_comment=1-g2" ],
"reg_test_stmt_resultset_err_no_rows_php-t" : [ "legacy-g2","mysql84-g2","mysql-auto_increment_delay_multiplex=0-g2","mysql-multiplexing=false-g2","mysql-query_digests=0-g2","mysql-query_digests_keep_comment=1-g2" ],
"reg_test_stmt_send_long_data_short_packet-t" : [ "mysql84-g1" ],
"reg_test_unexp_ping_pkt-t" : [ "legacy-g2","mysql84-g2","mysql-auto_increment_delay_multiplex=0-g2","mysql-multiplexing=false-g2","mysql-query_digests=0-g2","mysql-query_digests_keep_comment=1-g2" ],
"rule_matching_unit-t" : [ "unit-tests-g1" ],
"savepoint-3749-t" : [ "legacy-g2","mysql84-g2","mysql-auto_increment_delay_multiplex=0-g2","mysql-multiplexing=false-g2","mysql-query_digests=0-g2","mysql-query_digests_keep_comment=1-g2" ],
@ -270,6 +270,7 @@
"test_cluster1-t" : [ "legacy-g3","mysql-auto_increment_delay_multiplex=0-g3","mysql-multiplexing=false-g3","mysql-query_digests=0-g3","mysql-query_digests_keep_comment=1-g3" ],
"test_cluster_sync-t" : [ "legacy-g5","mysql-auto_increment_delay_multiplex=0-g3","mysql-multiplexing=false-g3","mysql-query_digests=0-g3","mysql-query_digests_keep_comment=1-g3" ],
"test_cluster_sync_mysql_servers-t" : [ "legacy-g5","mysql-auto_increment_delay_multiplex=0-g3","mysql-multiplexing=false-g3","mysql-query_digests=0-g3","mysql-query_digests_keep_comment=1-g3" ],
"test_cluster_sync_pgsql-t" : [ "legacy-g3","mysql-auto_increment_delay_multiplex=0-g3","mysql-multiplexing=false-g3","mysql-query_digests=0-g3","mysql-query_digests_keep_comment=1-g3" ],
"test_com_binlog_dump_enables_fast_forward-t" : [ "legacy-binlog-g1" ],
"test_com_register_slave_enables_fast_forward-t" : [ "legacy-g3","mysql84-g3","mysql-auto_increment_delay_multiplex=0-g3","mysql-multiplexing=false-g3","mysql-query_digests=0-g3","mysql-query_digests_keep_comment=1-g3" ],
"test_com_reset_connection_com_change_user-t" : [ "legacy-g3","mysql84-g3","mysql-auto_increment_delay_multiplex=0-g3","mysql-multiplexing=false-g3","mysql-query_digests=0-g3","mysql-query_digests_keep_comment=1-g3" ],

@ -0,0 +1,119 @@
#!/usr/bin/env python3
"""
Lint groups.json for format compliance.
Enforces the conventions documented in test/tap/groups/README.md:
- Valid JSON
- One entry per line, compact arrays (no multi-line)
- Format: ' "test-name-t" : [ "group1","group2" ],' (or no comma for last)
- Keys sorted alphabetically
- File starts with '{' and ends with '}'
Exit codes:
0 - Format is correct
1 - Format violations found
"""
import json
import os
import re
import sys
def main():
groups_path = os.path.join(os.path.dirname(__file__), "groups.json")
if len(sys.argv) > 1:
groups_path = sys.argv[1]
if not os.path.isfile(groups_path):
print(f"ERROR: {groups_path} not found", file=sys.stderr)
return 1
with open(groups_path, "r") as f:
raw = f.read()
lines = raw.split("\n")
errors = []
# Check 1: valid JSON
try:
data = json.loads(raw)
except json.JSONDecodeError as e:
errors.append(f"Invalid JSON: {e}")
# Can't do further checks
for e in errors:
print(f"ERROR: {e}", file=sys.stderr)
return 1
if not isinstance(data, dict):
errors.append("Top-level value must be a JSON object")
for e in errors:
print(f"ERROR: {e}", file=sys.stderr)
return 1
# Check 2: first and last lines
if not lines or lines[0].strip() != "{":
errors.append("Line 1: must be '{'")
if lines[-1].strip() == "":
# Allow trailing newline — check second-to-last
if len(lines) < 2 or lines[-2].strip() != "}":
errors.append(f"Last non-empty line: must be '}}'")
elif lines[-1].strip() != "}":
errors.append(f"Last line: must be '}}'")
# Check 3: each entry is one line with correct format
entry_pattern = re.compile(
r'^ "([^"]+)" : \[ .+ \](,?)$'
)
keys_in_order = []
entry_lines = lines[1:] # skip opening brace
for i, line in enumerate(entry_lines, start=2):
stripped = line.strip()
if stripped == "}" or stripped == "":
continue
m = entry_pattern.match(line)
if not m:
errors.append(
f"Line {i}: does not match expected format "
f"' \"test-name\" : [ \"group1\",\"group2\" ],' "
f"Got: {line!r}"
)
else:
keys_in_order.append(m.group(1))
# Check 4: keys are sorted
if keys_in_order != sorted(keys_in_order):
# Find first out-of-order key
for j in range(len(keys_in_order) - 1):
if keys_in_order[j] > keys_in_order[j + 1]:
errors.append(
f"Keys not sorted: '{keys_in_order[j + 1]}' "
f"should come before '{keys_in_order[j]}'"
)
break
# Check 5: last entry has no trailing comma, others do
entry_indices = []
for i, line in enumerate(lines):
if entry_pattern.match(line):
entry_indices.append(i)
if entry_indices:
for idx in entry_indices[:-1]:
if not lines[idx].rstrip().endswith(","):
errors.append(f"Line {idx + 1}: non-last entry must end with comma")
last_idx = entry_indices[-1]
if lines[last_idx].rstrip().endswith(","):
errors.append(f"Line {last_idx + 1}: last entry must not end with comma")
if errors:
print(f"groups.json format lint: {len(errors)} error(s) found:", file=sys.stderr)
for e in errors:
print(f" {e}", file=sys.stderr)
return 1
print(f"groups.json format lint: OK ({len(data)} entries, sorted, compact)")
return 0
if __name__ == "__main__":
sys.exit(main())
Loading…
Cancel
Save