From bccdd77d68e9d694def6fdf17f274da1eae1744e Mon Sep 17 00:00:00 2001 From: XuPeng-SH Date: Sun, 26 Jul 2026 07:39:37 +0800 Subject: [PATCH 01/25] ci: collect coverage from race UT and BVT jobs --- .github/workflows/entrypoint.yaml | 11 ++- etc/launch-tae-compose/compose.yaml | 6 ++ optools/images/Dockerfile | 10 +-- optools/run_ut.sh | 101 +++++++++++++++++++++++++--- 4 files changed, 112 insertions(+), 16 deletions(-) diff --git a/.github/workflows/entrypoint.yaml b/.github/workflows/entrypoint.yaml index 64deb6f9d0876..0177a7a25b82d 100644 --- a/.github/workflows/entrypoint.yaml +++ b/.github/workflows/entrypoint.yaml @@ -54,9 +54,14 @@ jobs: matrixone-utils-ci: name: Matrixone Utils CI - needs: check-pr-valid - if: ${{ needs.check-pr-valid.outputs.pr_valid == 'true' && github.base_ref != '3.0-dev' }} - uses: matrixorigin/CI/.github/workflows/utils.yaml@main + # The coverage gate only merges profiles produced by these existing test + # jobs. Waiting here keeps its report out of the critical test paths while + # ensuring it cannot race artifact publication. + needs: [check-pr-valid, matrixone-ci, matrixone-compose-ci, matrixone-standalone-ci] + if: ${{ always() && needs.check-pr-valid.outputs.pr_valid == 'true' && github.base_ref != '3.0-dev' }} + # Temporary integration reference for the linked CI PR. Restore @main + # after matrixorigin/CI#402 merges. + uses: matrixorigin/CI/.github/workflows/utils.yaml@xp/ci-race-coverage-artifacts secrets: inherit # ============ 3.0-dev 分支使用 @release/3.0-dev ============ diff --git a/etc/launch-tae-compose/compose.yaml b/etc/launch-tae-compose/compose.yaml index 68ad29147bd1b..d998c5b6fd0b2 100644 --- a/etc/launch-tae-compose/compose.yaml +++ b/etc/launch-tae-compose/compose.yaml @@ -10,11 +10,13 @@ x-mo-common: &mo-common # Typecheck: enable type checking for ToSliceNoTypeCheck and ToSliceNoTypeCheck2 # Default: 0 (disabled), set to 1 to enable TYPECHECK: ${TYPECHECK:-0} + GOBUILD_OPT: ${GOBUILD_OPT:-} image: matrixorigin/matrixone:latest volumes: - ../../etc/launch-tae-compose/config:/config - ../../test:/test - ../../docker-compose-log:/log + - ../../coverage:/coverage restart: on-failure tty: true @@ -37,6 +39,7 @@ services: - AWS_ACCESS_KEY_ID=minio - AWS_SECRET_ACCESS_KEY=minio123 - mo_reuse_enable_checker=true + - GOCOVERDIR=${GOCOVERDIR:-} networks: monet: aliases: @@ -59,6 +62,7 @@ services: - AWS_ACCESS_KEY_ID=minio - AWS_SECRET_ACCESS_KEY=minio123 - mo_reuse_enable_checker=true + - GOCOVERDIR=${GOCOVERDIR:-} networks: monet: aliases: @@ -80,6 +84,7 @@ services: - AWS_ACCESS_KEY_ID=minio - AWS_SECRET_ACCESS_KEY=minio123 - mo_reuse_enable_checker=true + - GOCOVERDIR=${GOCOVERDIR:-} networks: monet: aliases: @@ -103,6 +108,7 @@ services: - AWS_ACCESS_KEY_ID=minio - AWS_SECRET_ACCESS_KEY=minio123 - mo_reuse_enable_checker=true + - GOCOVERDIR=${GOCOVERDIR:-} networks: monet: aliases: diff --git a/optools/images/Dockerfile b/optools/images/Dockerfile index 87e179de75701..f787d4bcc7b98 100644 --- a/optools/images/Dockerfile +++ b/optools/images/Dockerfile @@ -8,6 +8,7 @@ RUN go env -w GOPROXY=${GOPROXY} # Typecheck: enable type checking for ToSliceNoTypeCheck and ToSliceNoTypeCheck2 # Default: 0 (disabled), set to 1 to enable ARG TYPECHECK=0 +ARG GOBUILD_OPT="" RUN mkdir -p /go/src/github.com/matrixorigin/matrixone @@ -28,9 +29,9 @@ RUN module_files="$(sha256sum go.mod go.sum)" && \ COPY . . RUN if [ "$TYPECHECK" = "1" ]; then \ - make clean && make build TYPECHECK=1; \ + make clean && make build TYPECHECK=1 GOBUILD_OPT="${GOBUILD_OPT}"; \ else \ - make clean && make build; \ + make clean && make build GOBUILD_OPT="${GOBUILD_OPT}"; \ fi FROM matrixorigin/ubuntu:22.04 @@ -42,8 +43,9 @@ COPY --from=builder /go/src/github.com/matrixorigin/matrixone/cgo/*.so /usr/loca COPY --from=builder /go/src/github.com/matrixorigin/matrixone/pkg/monlp/tokenizer/dict /usr/local/share/jieba ENV MO_JIEBA_DICT_DIR=/usr/local/share/jieba -# ldconfig and run mo-service to check if the shared library is found -RUN ldconfig && /mo-service -h +# ldconfig and run mo-service to check if the shared library is found. A +# coverage-instrumented binary requires a writable GOCOVERDIR even for -h. +RUN mkdir -p /tmp/coverage && ldconfig && GOCOVERDIR=/tmp/coverage /mo-service -h WORKDIR / diff --git a/optools/run_ut.sh b/optools/run_ut.sh index 369e8577e66f3..5b0437390baae 100755 --- a/optools/run_ut.sh +++ b/optools/run_ut.sh @@ -42,16 +42,17 @@ UT_TIMEOUT=${UT_TIMEOUT:-"15"} UT_PARALLEL=${UT_PARALLEL:-"1"} HEAVY_RACE_PARALLEL=${HEAVY_RACE_PARALLEL:-"3"} PLAN_RACE_SHARDS=${PLAN_RACE_SHARDS:-"8"} +UT_COVERAGE_DIR=${UT_COVERAGE_DIR:-""} SCA_REPORT="$G_WKSP/$G_TS-SCA-Report.out" UT_REPORT="$G_WKSP/$G_TS-UT-Report.out" UT_FILTER="$G_WKSP/$G_TS-UT-Filter.out" UT_COUNT="$G_WKSP/$G_TS-UT-Count.out" -CODE_COVERAGE="$G_WKSP/$G_TS-UT-Coverage.html" -RAW_COVERAGE="coverage.out" IS_BUILD_FAIL="" UT_TEST_STATUS=0 TAGS="matrixone_test" GO_MODULE_MODE="-mod=readonly" +COVERAGE_ENABLED=0 +declare -a COVERAGE_BASE_ARGS=() # CI runs the checked-out MatrixOne module, never a caller's Go workspace. export GOWORK=off @@ -80,6 +81,11 @@ if [[ -f $UT_REPORT ]]; then rm $UT_REPORT; fi if [[ -f $UT_FILTER ]]; then rm $UT_FILTER; fi if [[ -f $UT_COUNT ]]; then rm $UT_COUNT; fi +if [[ -n "${UT_COVERAGE_DIR}" && "${SKIP_TESTS}" == "race" ]]; then + echo "UT_COVERAGE_DIR requires the race-enabled UT path" + exit 2 +fi + function logger(){ local level=$1 @@ -88,6 +94,17 @@ function logger(){ logger_base "$level" "$msg" "$log" } +function verify_coverage_profile(){ + local profile_path=$1 + + if (( COVERAGE_ENABLED )) && [[ ! -s "${profile_path}" ]]; then + logger "ERR" "Missing or empty race coverage profile: ${profile_path}" + return 1 + fi + + return 0 +} + function run_vet(){ cd $BUILD_WKSP horiz_rule @@ -164,15 +181,26 @@ function run_plan_race_shards(){ fi shard_patterns[shard]+=')$' logger "INF" "Run ${plan_package} race shard $(( shard + 1 ))/${PLAN_RACE_SHARDS} (${shard_counts[shard]} tests)" + local -a shard_coverage_args=() + if (( COVERAGE_ENABLED )); then + shard_coverage_args=( + "${COVERAGE_BASE_ARGS[@]}" + "-coverprofile=${UT_COVERAGE_DIR}/ut-race-plan-${shard}.out" + ) + fi LD_LIBRARY_PATH="${LD_LIBRARY_PATH}" \ CGO_CFLAGS="${CGO_CFLAGS}" \ CGO_LDFLAGS="${CGO_LDFLAGS}" \ go test ${GO_MODULE_MODE} -short -v -json -tags "${TAGS}" \ -p 1 -count=1 -timeout "${UT_TIMEOUT}m" -race \ + "${shard_coverage_args[@]}" \ -run "${shard_patterns[shard]}" "${plan_package}" >> "${UT_REPORT}" if (( $? != 0 )); then shard_status=1 fi + if ! verify_coverage_profile "${UT_COVERAGE_DIR}/ut-race-plan-${shard}.out"; then + shard_status=1 + fi done return "${shard_status}" @@ -184,7 +212,7 @@ function run_tests(){ echo "# BUILD WORKSPACE: $BUILD_WKSP" echo "# SKIPPED TEST: $SKIP_TESTS" echo "# UT REPORT: $UT_REPORT" - echo "# COVERAGE REPORT: $CODE_COVERAGE" + echo "# UT COVERAGE DIR: ${UT_COVERAGE_DIR:-disabled}" echo "# UT TIMEOUT: $UT_TIMEOUT" echo "# UT PARALLEL: $UT_PARALLEL" echo "# HEAVY RACE UT: $HEAVY_RACE_PARALLEL" @@ -194,9 +222,33 @@ function run_tests(){ go clean -testcache local test_scope=$(go list ${GO_MODULE_MODE} ./... | grep -v 'driver/aoe' | grep -v 'engine/aoe' | grep -v 'pkg/catalog') - local leave_out=$(egrep -lr --include="*.go" 'Code generated by protoc-gen-gogo. DO NOT EDIT.' ./pkg/* | sort -u | xargs basename -a) - logger "INF" "Ingore code coverage $(echo ${leave_out[@]}|tr " " "|")" - local cover_profile='profile.raw' + if [[ -n "${UT_COVERAGE_DIR}" ]]; then + local coverage_scope + local coverage_pkgs + coverage_scope=$(go list ${GO_MODULE_MODE} ./... | grep -v 'driver\|engine/aoe\|engine/memEngine\|pkg/catalog') + coverage_pkgs=$(echo "${coverage_scope}" | paste -sd, -) + if [[ -z "${coverage_pkgs}" ]]; then + logger "ERR" "Race coverage package scope is empty" + UT_TEST_STATUS=1 + return 0 + fi + if ! mkdir -p "${UT_COVERAGE_DIR}"; then + logger "ERR" "Failed to create UT_COVERAGE_DIR: ${UT_COVERAGE_DIR}" + UT_TEST_STATUS=1 + return 0 + fi + if find "${UT_COVERAGE_DIR}" -maxdepth 1 -type f -name 'ut-race-*.out' -print -quit | grep -q .; then + logger "ERR" "UT_COVERAGE_DIR already contains ut-race coverage profiles: ${UT_COVERAGE_DIR}" + UT_TEST_STATUS=1 + return 0 + fi + COVERAGE_BASE_ARGS=( + "-covermode=atomic" + "-coverpkg=${coverage_pkgs}" + ) + COVERAGE_ENABLED=1 + logger "INF" "Race UT coverage enabled; profiles will be written to ${UT_COVERAGE_DIR}" + fi make cgo make thirdparties @@ -273,19 +325,49 @@ function run_tests(){ if [[ -n "${light_test_scope}" ]]; then logger "INF" "Run light race-test packages with parallelism ${UT_PARALLEL}" - LD_LIBRARY_PATH="${LD_LIBRARY_PATH}" CGO_CFLAGS="${CGO_CFLAGS}" CGO_LDFLAGS="${CGO_LDFLAGS}" go test ${GO_MODULE_MODE} -short -v -json -tags "${TAGS}" -p ${UT_PARALLEL} -timeout "${UT_TIMEOUT}m" -race $light_test_scope > $UT_REPORT + local -a light_coverage_args=() + if (( COVERAGE_ENABLED )); then + light_coverage_args=( + "${COVERAGE_BASE_ARGS[@]}" + "-coverprofile=${UT_COVERAGE_DIR}/ut-race-light.out" + ) + fi + LD_LIBRARY_PATH="${LD_LIBRARY_PATH}" CGO_CFLAGS="${CGO_CFLAGS}" CGO_LDFLAGS="${CGO_LDFLAGS}" go test ${GO_MODULE_MODE} -short -v -json -tags "${TAGS}" -p ${UT_PARALLEL} -timeout "${UT_TIMEOUT}m" -race "${light_coverage_args[@]}" $light_test_scope > $UT_REPORT light_status=$? + if ! verify_coverage_profile "${UT_COVERAGE_DIR}/ut-race-light.out"; then + light_status=1 + fi else : > "${UT_REPORT}" fi logger "INF" "Run fixed-port race-test packages serially" - LD_LIBRARY_PATH="${LD_LIBRARY_PATH}" CGO_CFLAGS="${CGO_CFLAGS}" CGO_LDFLAGS="${CGO_LDFLAGS}" go test ${GO_MODULE_MODE} -short -v -json -tags "${TAGS}" -p 1 -timeout "${UT_TIMEOUT}m" -race $fixed_port_test_scope >> $UT_REPORT + local -a fixed_port_coverage_args=() + if (( COVERAGE_ENABLED )); then + fixed_port_coverage_args=( + "${COVERAGE_BASE_ARGS[@]}" + "-coverprofile=${UT_COVERAGE_DIR}/ut-race-fixed-port.out" + ) + fi + LD_LIBRARY_PATH="${LD_LIBRARY_PATH}" CGO_CFLAGS="${CGO_CFLAGS}" CGO_LDFLAGS="${CGO_LDFLAGS}" go test ${GO_MODULE_MODE} -short -v -json -tags "${TAGS}" -p 1 -timeout "${UT_TIMEOUT}m" -race "${fixed_port_coverage_args[@]}" $fixed_port_test_scope >> $UT_REPORT fixed_port_status=$? + if ! verify_coverage_profile "${UT_COVERAGE_DIR}/ut-race-fixed-port.out"; then + fixed_port_status=1 + fi logger "INF" "Run heavy race-test packages with parallelism ${HEAVY_RACE_PARALLEL}" - LD_LIBRARY_PATH="${LD_LIBRARY_PATH}" CGO_CFLAGS="${CGO_CFLAGS}" CGO_LDFLAGS="${CGO_LDFLAGS}" go test ${GO_MODULE_MODE} -short -v -json -tags "${TAGS}" -p ${HEAVY_RACE_PARALLEL} -timeout "${UT_TIMEOUT}m" -race $heavy_test_scope >> $UT_REPORT + local -a heavy_coverage_args=() + if (( COVERAGE_ENABLED )); then + heavy_coverage_args=( + "${COVERAGE_BASE_ARGS[@]}" + "-coverprofile=${UT_COVERAGE_DIR}/ut-race-heavy.out" + ) + fi + LD_LIBRARY_PATH="${LD_LIBRARY_PATH}" CGO_CFLAGS="${CGO_CFLAGS}" CGO_LDFLAGS="${CGO_LDFLAGS}" go test ${GO_MODULE_MODE} -short -v -json -tags "${TAGS}" -p ${HEAVY_RACE_PARALLEL} -timeout "${UT_TIMEOUT}m" -race "${heavy_coverage_args[@]}" $heavy_test_scope >> $UT_REPORT heavy_status=$? + if ! verify_coverage_profile "${UT_COVERAGE_DIR}/ut-race-heavy.out"; then + heavy_status=1 + fi run_plan_race_shards "${plan_package}" plan_status=$? @@ -293,6 +375,7 @@ function run_tests(){ if (( light_status != 0 || fixed_port_status != 0 || heavy_status != 0 || plan_status != 0 )); then UT_TEST_STATUS=1 fi + fi # run_ut.sh intentionally does not use errexit because post-processing must From 30c389b3bcb0cd3dc988e807f86fe08c11adad55 Mon Sep 17 00:00:00 2001 From: XuPeng-SH Date: Sun, 26 Jul 2026 07:44:38 +0800 Subject: [PATCH 02/25] ci: add coverage integration validation workflow --- .github/workflows/coverage-integration.yaml | 38 +++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 .github/workflows/coverage-integration.yaml diff --git a/.github/workflows/coverage-integration.yaml b/.github/workflows/coverage-integration.yaml new file mode 100644 index 0000000000000..5a960a24c8bf6 --- /dev/null +++ b/.github/workflows/coverage-integration.yaml @@ -0,0 +1,38 @@ +name: MatrixOne Coverage Integration + +on: + pull_request: + branches: + - main + +jobs: + matrixone-ci: + uses: matrixorigin/CI/.github/workflows/ci.yaml@xp/ci-race-coverage-artifacts + with: + repository: ${{ github.event.pull_request.head.repo.full_name }} + ref: ${{ github.event.pull_request.head.ref }} + secrets: inherit + + matrixone-compose-ci: + uses: matrixorigin/CI/.github/workflows/e2e-compose.yaml@xp/ci-race-coverage-artifacts + with: + repository: ${{ github.event.pull_request.head.repo.full_name }} + ref: ${{ github.event.pull_request.head.ref }} + secrets: inherit + + matrixone-standalone-ci: + uses: matrixorigin/CI/.github/workflows/e2e-standalone.yaml@xp/ci-race-coverage-artifacts + with: + repository: ${{ github.event.pull_request.head.repo.full_name }} + ref: ${{ github.event.pull_request.head.ref }} + secrets: inherit + + matrixone-utils-ci: + needs: [matrixone-ci, matrixone-compose-ci, matrixone-standalone-ci] + if: ${{ always() }} + uses: matrixorigin/CI/.github/workflows/utils.yaml@xp/ci-race-coverage-artifacts + with: + repository: ${{ github.event.pull_request.head.repo.full_name }} + ref: ${{ github.event.pull_request.head.ref }} + pr_number: ${{ github.event.pull_request.number }} + secrets: inherit From 52c37d5072ac49f8424b69650fbd3c7a14b138c3 Mon Sep 17 00:00:00 2001 From: XuPeng-SH Date: Sun, 26 Jul 2026 09:23:45 +0800 Subject: [PATCH 03/25] ci: route compose BVT through proxy --- etc/launch-tae-compose/README.md | 13 +-- etc/launch-tae-compose/compose.yaml | 34 ++++++-- etc/launch-tae-compose/config/cn-0.toml | 1 + etc/launch-tae-compose/config/cn-1.toml | 1 + etc/launch-tae-compose/config/proxy.toml | 43 ++++++++++ optools/run_bvt_group.sh | 55 ++++++++++++ optools/run_ut.sh | 101 ++--------------------- 7 files changed, 144 insertions(+), 104 deletions(-) create mode 100644 etc/launch-tae-compose/config/proxy.toml create mode 100644 optools/run_bvt_group.sh diff --git a/etc/launch-tae-compose/README.md b/etc/launch-tae-compose/README.md index 0283622461dbc..033281e22c2df 100644 --- a/etc/launch-tae-compose/README.md +++ b/etc/launch-tae-compose/README.md @@ -33,6 +33,9 @@ docker compose -f etc/launch-tae-compose/compose.yaml --profile launch-multi-cn # cn-1 docker compose -f etc/launch-tae-compose/compose.yaml --profile launch-multi-cn logs cn-1 +# proxy +docker compose -f etc/launch-tae-compose/compose.yaml --profile launch-multi-cn logs proxy + # tn docker compose -f etc/launch-tae-compose/compose.yaml --profile launch-multi-cn logs tn @@ -47,17 +50,15 @@ docker compose -f etc/launch-tae-compose/compose.yaml --profile launch-multi-cn username: minio password: minio123 -## connect service +## connect service through proxy -cn-0 ```shell mysql -h 127.0.0.1 -P 6001 -udump -p111 ``` -cn-1: -```shell -mysql -h 127.0.0.1 -P 7001 -udump -p111 -``` +Port `6001` is the Proxy entrypoint. CN SQL ports are intentionally not +published to the host; clients should enter through Proxy so that both CNs are +eligible backends. ## down diff --git a/etc/launch-tae-compose/compose.yaml b/etc/launch-tae-compose/compose.yaml index d998c5b6fd0b2..f7c84b2922f27 100644 --- a/etc/launch-tae-compose/compose.yaml +++ b/etc/launch-tae-compose/compose.yaml @@ -32,14 +32,13 @@ services: depends_on: - tn ports: - - "6001:6001" - "6002" - "12345:12345" environment: - AWS_ACCESS_KEY_ID=minio - AWS_SECRET_ACCESS_KEY=minio123 - mo_reuse_enable_checker=true - - GOCOVERDIR=${GOCOVERDIR:-} + - GOCOVERDIR=/coverage networks: monet: aliases: @@ -52,7 +51,6 @@ services: depends_on: - tn ports: - - "7001:6001" - "6002" - "22345:12345" profiles: @@ -62,12 +60,36 @@ services: - AWS_ACCESS_KEY_ID=minio - AWS_SECRET_ACCESS_KEY=minio123 - mo_reuse_enable_checker=true - - GOCOVERDIR=${GOCOVERDIR:-} + - GOCOVERDIR=/coverage networks: monet: aliases: - "cn1" + proxy: + container_name: proxy + <<: *mo-common + entrypoint: ["/bin/bash","-c","set -euo pipefail; /mo-service -debug-http :12345 -cfg /config/proxy.toml | tee -a /log/proxy.log"] + profiles: + - launch + - launch-multi-cn + depends_on: + - logservice + - cn-0 + - cn-1 + ports: + - "6001:6009" + - "32346:12345" + environment: + - AWS_ACCESS_KEY_ID=minio + - AWS_SECRET_ACCESS_KEY=minio123 + - mo_reuse_enable_checker=true + - GOCOVERDIR=/coverage + networks: + monet: + aliases: + - "proxy" + tn: container_name: tn <<: *mo-common @@ -84,7 +106,7 @@ services: - AWS_ACCESS_KEY_ID=minio - AWS_SECRET_ACCESS_KEY=minio123 - mo_reuse_enable_checker=true - - GOCOVERDIR=${GOCOVERDIR:-} + - GOCOVERDIR=/coverage networks: monet: aliases: @@ -108,7 +130,7 @@ services: - AWS_ACCESS_KEY_ID=minio - AWS_SECRET_ACCESS_KEY=minio123 - mo_reuse_enable_checker=true - - GOCOVERDIR=${GOCOVERDIR:-} + - GOCOVERDIR=/coverage networks: monet: aliases: diff --git a/etc/launch-tae-compose/config/cn-0.toml b/etc/launch-tae-compose/config/cn-0.toml index 582e86d0bf512..14990afe9902d 100644 --- a/etc/launch-tae-compose/config/cn-0.toml +++ b/etc/launch-tae-compose/config/cn-0.toml @@ -54,6 +54,7 @@ type = "distributed-tae" [cn.frontend] port = 6001 +proxy-enabled = true [cn.frontend.iceberg] enable = true diff --git a/etc/launch-tae-compose/config/cn-1.toml b/etc/launch-tae-compose/config/cn-1.toml index 134f1decdea0c..aa7823f65fe75 100644 --- a/etc/launch-tae-compose/config/cn-1.toml +++ b/etc/launch-tae-compose/config/cn-1.toml @@ -54,6 +54,7 @@ type = "distributed-tae" [cn.frontend] port = 6001 +proxy-enabled = true [cn.frontend.iceberg] enable = true diff --git a/etc/launch-tae-compose/config/proxy.toml b/etc/launch-tae-compose/config/proxy.toml new file mode 100644 index 0000000000000..92aa38a590527 --- /dev/null +++ b/etc/launch-tae-compose/config/proxy.toml @@ -0,0 +1,43 @@ +service-type = "PROXY" +data-dir = "/var/lib/matrixone/data" + +[log] +level = "info" +format = "console" +max-size = 512 + +[hakeeper-client] +service-addresses = ["logservice:32001"] + +[[fileservice]] +backend = "DISK-V2" +data-dir = "/var/lib/matrixone/data" +name = "LOCAL" + +[[fileservice]] +backend = "MINIO" +name = "SHARED" + +[fileservice.s3] +bucket = "mo-test" +endpoint = "http://minio:9000" +key-prefix = "server/data" +cert-files = ["/etc/ssl/cert.pem"] + +[fileservice.cache] +memory-capacity = "512MB" +disk-capacity = "1GB" +disk-path = "mo-data/file-service-cache" + +[[fileservice]] +backend = "MINIO" +name = "ETL" + +[fileservice.s3] +bucket = "mo-test" +endpoint = "http://minio:9000" +key-prefix = "server/etl" + +[proxy] +uuid = "rd1dccb4-4d3c-41f8-b482-5251dc7a41bf" +listen-address = "0.0.0.0:6009" diff --git a/optools/run_bvt_group.sh b/optools/run_bvt_group.sh new file mode 100644 index 0000000000000..ae725d9fa2289 --- /dev/null +++ b/optools/run_bvt_group.sh @@ -0,0 +1,55 @@ +#!/usr/bin/env bash + +# Run one of the three time-balanced BVT groups. The groups were derived from +# successful CI reports: pessimistic_transaction is the long group; snapshot, +# optimizer, fulltext and git4data form the second; the remaining cases form +# the third. New top-level directories are assigned deterministically. +set -euo pipefail + +if [[ $# -ne 3 ]]; then + echo "Usage: $0 " >&2 + exit 2 +fi + +tester_dir=$(cd "$1" && pwd) +case_root=$(cd "$2" && pwd) +group_id=$3 + +if ! [[ "${group_id}" =~ ^[0-2]$ ]]; then + echo "BVT group must be 0, 1, or 2; got '${group_id}'" >&2 + exit 2 +fi + +declare -a included_scripts=() +while IFS= read -r script_path; do + relative_path=${script_path#"${case_root}/"} + [[ "${relative_path}" == *optimistic* ]] && continue + top_level=${relative_path%%/*} + + case "${top_level}" in + pessimistic_transaction) assigned_group=0 ;; + snapshot|optimizer|fulltext|git4data) assigned_group=1 ;; + *) + # Group 2 is the complete complement of the two explicitly + # measured heavy groups. A new directory is covered immediately + # here and becomes a signal to refresh the timing balance. + assigned_group=2 + ;; + esac + + if (( assigned_group == group_id )); then + included_scripts+=("${script_path}") + fi +done < <(find "${case_root}" -type f \( -name '*.sql' -o -name '*.test' \) -print | LC_ALL=C sort) + +if (( ${#included_scripts[@]} == 0 )); then + echo "BVT group ${group_id} has no test scripts" >&2 + exit 1 +fi + +include_list=$(IFS=,; echo "${included_scripts[*]}") +echo "Run BVT group ${group_id}: ${#included_scripts[@]} scripts" +printf '%s\n' "${included_scripts[@]}" + +cd "${tester_dir}" +./run.sh -n -g -o -p "${case_root}" -i "${include_list}" diff --git a/optools/run_ut.sh b/optools/run_ut.sh index 5b0437390baae..369e8577e66f3 100755 --- a/optools/run_ut.sh +++ b/optools/run_ut.sh @@ -42,17 +42,16 @@ UT_TIMEOUT=${UT_TIMEOUT:-"15"} UT_PARALLEL=${UT_PARALLEL:-"1"} HEAVY_RACE_PARALLEL=${HEAVY_RACE_PARALLEL:-"3"} PLAN_RACE_SHARDS=${PLAN_RACE_SHARDS:-"8"} -UT_COVERAGE_DIR=${UT_COVERAGE_DIR:-""} SCA_REPORT="$G_WKSP/$G_TS-SCA-Report.out" UT_REPORT="$G_WKSP/$G_TS-UT-Report.out" UT_FILTER="$G_WKSP/$G_TS-UT-Filter.out" UT_COUNT="$G_WKSP/$G_TS-UT-Count.out" +CODE_COVERAGE="$G_WKSP/$G_TS-UT-Coverage.html" +RAW_COVERAGE="coverage.out" IS_BUILD_FAIL="" UT_TEST_STATUS=0 TAGS="matrixone_test" GO_MODULE_MODE="-mod=readonly" -COVERAGE_ENABLED=0 -declare -a COVERAGE_BASE_ARGS=() # CI runs the checked-out MatrixOne module, never a caller's Go workspace. export GOWORK=off @@ -81,11 +80,6 @@ if [[ -f $UT_REPORT ]]; then rm $UT_REPORT; fi if [[ -f $UT_FILTER ]]; then rm $UT_FILTER; fi if [[ -f $UT_COUNT ]]; then rm $UT_COUNT; fi -if [[ -n "${UT_COVERAGE_DIR}" && "${SKIP_TESTS}" == "race" ]]; then - echo "UT_COVERAGE_DIR requires the race-enabled UT path" - exit 2 -fi - function logger(){ local level=$1 @@ -94,17 +88,6 @@ function logger(){ logger_base "$level" "$msg" "$log" } -function verify_coverage_profile(){ - local profile_path=$1 - - if (( COVERAGE_ENABLED )) && [[ ! -s "${profile_path}" ]]; then - logger "ERR" "Missing or empty race coverage profile: ${profile_path}" - return 1 - fi - - return 0 -} - function run_vet(){ cd $BUILD_WKSP horiz_rule @@ -181,26 +164,15 @@ function run_plan_race_shards(){ fi shard_patterns[shard]+=')$' logger "INF" "Run ${plan_package} race shard $(( shard + 1 ))/${PLAN_RACE_SHARDS} (${shard_counts[shard]} tests)" - local -a shard_coverage_args=() - if (( COVERAGE_ENABLED )); then - shard_coverage_args=( - "${COVERAGE_BASE_ARGS[@]}" - "-coverprofile=${UT_COVERAGE_DIR}/ut-race-plan-${shard}.out" - ) - fi LD_LIBRARY_PATH="${LD_LIBRARY_PATH}" \ CGO_CFLAGS="${CGO_CFLAGS}" \ CGO_LDFLAGS="${CGO_LDFLAGS}" \ go test ${GO_MODULE_MODE} -short -v -json -tags "${TAGS}" \ -p 1 -count=1 -timeout "${UT_TIMEOUT}m" -race \ - "${shard_coverage_args[@]}" \ -run "${shard_patterns[shard]}" "${plan_package}" >> "${UT_REPORT}" if (( $? != 0 )); then shard_status=1 fi - if ! verify_coverage_profile "${UT_COVERAGE_DIR}/ut-race-plan-${shard}.out"; then - shard_status=1 - fi done return "${shard_status}" @@ -212,7 +184,7 @@ function run_tests(){ echo "# BUILD WORKSPACE: $BUILD_WKSP" echo "# SKIPPED TEST: $SKIP_TESTS" echo "# UT REPORT: $UT_REPORT" - echo "# UT COVERAGE DIR: ${UT_COVERAGE_DIR:-disabled}" + echo "# COVERAGE REPORT: $CODE_COVERAGE" echo "# UT TIMEOUT: $UT_TIMEOUT" echo "# UT PARALLEL: $UT_PARALLEL" echo "# HEAVY RACE UT: $HEAVY_RACE_PARALLEL" @@ -222,33 +194,9 @@ function run_tests(){ go clean -testcache local test_scope=$(go list ${GO_MODULE_MODE} ./... | grep -v 'driver/aoe' | grep -v 'engine/aoe' | grep -v 'pkg/catalog') - if [[ -n "${UT_COVERAGE_DIR}" ]]; then - local coverage_scope - local coverage_pkgs - coverage_scope=$(go list ${GO_MODULE_MODE} ./... | grep -v 'driver\|engine/aoe\|engine/memEngine\|pkg/catalog') - coverage_pkgs=$(echo "${coverage_scope}" | paste -sd, -) - if [[ -z "${coverage_pkgs}" ]]; then - logger "ERR" "Race coverage package scope is empty" - UT_TEST_STATUS=1 - return 0 - fi - if ! mkdir -p "${UT_COVERAGE_DIR}"; then - logger "ERR" "Failed to create UT_COVERAGE_DIR: ${UT_COVERAGE_DIR}" - UT_TEST_STATUS=1 - return 0 - fi - if find "${UT_COVERAGE_DIR}" -maxdepth 1 -type f -name 'ut-race-*.out' -print -quit | grep -q .; then - logger "ERR" "UT_COVERAGE_DIR already contains ut-race coverage profiles: ${UT_COVERAGE_DIR}" - UT_TEST_STATUS=1 - return 0 - fi - COVERAGE_BASE_ARGS=( - "-covermode=atomic" - "-coverpkg=${coverage_pkgs}" - ) - COVERAGE_ENABLED=1 - logger "INF" "Race UT coverage enabled; profiles will be written to ${UT_COVERAGE_DIR}" - fi + local leave_out=$(egrep -lr --include="*.go" 'Code generated by protoc-gen-gogo. DO NOT EDIT.' ./pkg/* | sort -u | xargs basename -a) + logger "INF" "Ingore code coverage $(echo ${leave_out[@]}|tr " " "|")" + local cover_profile='profile.raw' make cgo make thirdparties @@ -325,49 +273,19 @@ function run_tests(){ if [[ -n "${light_test_scope}" ]]; then logger "INF" "Run light race-test packages with parallelism ${UT_PARALLEL}" - local -a light_coverage_args=() - if (( COVERAGE_ENABLED )); then - light_coverage_args=( - "${COVERAGE_BASE_ARGS[@]}" - "-coverprofile=${UT_COVERAGE_DIR}/ut-race-light.out" - ) - fi - LD_LIBRARY_PATH="${LD_LIBRARY_PATH}" CGO_CFLAGS="${CGO_CFLAGS}" CGO_LDFLAGS="${CGO_LDFLAGS}" go test ${GO_MODULE_MODE} -short -v -json -tags "${TAGS}" -p ${UT_PARALLEL} -timeout "${UT_TIMEOUT}m" -race "${light_coverage_args[@]}" $light_test_scope > $UT_REPORT + LD_LIBRARY_PATH="${LD_LIBRARY_PATH}" CGO_CFLAGS="${CGO_CFLAGS}" CGO_LDFLAGS="${CGO_LDFLAGS}" go test ${GO_MODULE_MODE} -short -v -json -tags "${TAGS}" -p ${UT_PARALLEL} -timeout "${UT_TIMEOUT}m" -race $light_test_scope > $UT_REPORT light_status=$? - if ! verify_coverage_profile "${UT_COVERAGE_DIR}/ut-race-light.out"; then - light_status=1 - fi else : > "${UT_REPORT}" fi logger "INF" "Run fixed-port race-test packages serially" - local -a fixed_port_coverage_args=() - if (( COVERAGE_ENABLED )); then - fixed_port_coverage_args=( - "${COVERAGE_BASE_ARGS[@]}" - "-coverprofile=${UT_COVERAGE_DIR}/ut-race-fixed-port.out" - ) - fi - LD_LIBRARY_PATH="${LD_LIBRARY_PATH}" CGO_CFLAGS="${CGO_CFLAGS}" CGO_LDFLAGS="${CGO_LDFLAGS}" go test ${GO_MODULE_MODE} -short -v -json -tags "${TAGS}" -p 1 -timeout "${UT_TIMEOUT}m" -race "${fixed_port_coverage_args[@]}" $fixed_port_test_scope >> $UT_REPORT + LD_LIBRARY_PATH="${LD_LIBRARY_PATH}" CGO_CFLAGS="${CGO_CFLAGS}" CGO_LDFLAGS="${CGO_LDFLAGS}" go test ${GO_MODULE_MODE} -short -v -json -tags "${TAGS}" -p 1 -timeout "${UT_TIMEOUT}m" -race $fixed_port_test_scope >> $UT_REPORT fixed_port_status=$? - if ! verify_coverage_profile "${UT_COVERAGE_DIR}/ut-race-fixed-port.out"; then - fixed_port_status=1 - fi logger "INF" "Run heavy race-test packages with parallelism ${HEAVY_RACE_PARALLEL}" - local -a heavy_coverage_args=() - if (( COVERAGE_ENABLED )); then - heavy_coverage_args=( - "${COVERAGE_BASE_ARGS[@]}" - "-coverprofile=${UT_COVERAGE_DIR}/ut-race-heavy.out" - ) - fi - LD_LIBRARY_PATH="${LD_LIBRARY_PATH}" CGO_CFLAGS="${CGO_CFLAGS}" CGO_LDFLAGS="${CGO_LDFLAGS}" go test ${GO_MODULE_MODE} -short -v -json -tags "${TAGS}" -p ${HEAVY_RACE_PARALLEL} -timeout "${UT_TIMEOUT}m" -race "${heavy_coverage_args[@]}" $heavy_test_scope >> $UT_REPORT + LD_LIBRARY_PATH="${LD_LIBRARY_PATH}" CGO_CFLAGS="${CGO_CFLAGS}" CGO_LDFLAGS="${CGO_LDFLAGS}" go test ${GO_MODULE_MODE} -short -v -json -tags "${TAGS}" -p ${HEAVY_RACE_PARALLEL} -timeout "${UT_TIMEOUT}m" -race $heavy_test_scope >> $UT_REPORT heavy_status=$? - if ! verify_coverage_profile "${UT_COVERAGE_DIR}/ut-race-heavy.out"; then - heavy_status=1 - fi run_plan_race_shards "${plan_package}" plan_status=$? @@ -375,7 +293,6 @@ function run_tests(){ if (( light_status != 0 || fixed_port_status != 0 || heavy_status != 0 || plan_status != 0 )); then UT_TEST_STATUS=1 fi - fi # run_ut.sh intentionally does not use errexit because post-processing must From fb16dd3ab45defcd878b35993b2a8179286307b3 Mon Sep 17 00:00:00 2001 From: XuPeng-SH Date: Sun, 26 Jul 2026 19:52:37 +0800 Subject: [PATCH 04/25] ci: split BVT coverage into two groups --- .github/workflows/coverage-integration.yaml | 38 --------------------- .github/workflows/entrypoint.yaml | 35 ++++++++++++------- etc/launch-tae-compose/compose.yaml | 12 ++++--- optools/run_bvt_group.sh | 27 ++++++++------- 4 files changed, 45 insertions(+), 67 deletions(-) delete mode 100644 .github/workflows/coverage-integration.yaml diff --git a/.github/workflows/coverage-integration.yaml b/.github/workflows/coverage-integration.yaml deleted file mode 100644 index 5a960a24c8bf6..0000000000000 --- a/.github/workflows/coverage-integration.yaml +++ /dev/null @@ -1,38 +0,0 @@ -name: MatrixOne Coverage Integration - -on: - pull_request: - branches: - - main - -jobs: - matrixone-ci: - uses: matrixorigin/CI/.github/workflows/ci.yaml@xp/ci-race-coverage-artifacts - with: - repository: ${{ github.event.pull_request.head.repo.full_name }} - ref: ${{ github.event.pull_request.head.ref }} - secrets: inherit - - matrixone-compose-ci: - uses: matrixorigin/CI/.github/workflows/e2e-compose.yaml@xp/ci-race-coverage-artifacts - with: - repository: ${{ github.event.pull_request.head.repo.full_name }} - ref: ${{ github.event.pull_request.head.ref }} - secrets: inherit - - matrixone-standalone-ci: - uses: matrixorigin/CI/.github/workflows/e2e-standalone.yaml@xp/ci-race-coverage-artifacts - with: - repository: ${{ github.event.pull_request.head.repo.full_name }} - ref: ${{ github.event.pull_request.head.ref }} - secrets: inherit - - matrixone-utils-ci: - needs: [matrixone-ci, matrixone-compose-ci, matrixone-standalone-ci] - if: ${{ always() }} - uses: matrixorigin/CI/.github/workflows/utils.yaml@xp/ci-race-coverage-artifacts - with: - repository: ${{ github.event.pull_request.head.repo.full_name }} - ref: ${{ github.event.pull_request.head.ref }} - pr_number: ${{ github.event.pull_request.number }} - secrets: inherit diff --git a/.github/workflows/entrypoint.yaml b/.github/workflows/entrypoint.yaml index 0177a7a25b82d..a136b0cf7a961 100644 --- a/.github/workflows/entrypoint.yaml +++ b/.github/workflows/entrypoint.yaml @@ -23,12 +23,19 @@ jobs: title_for_find_issue: "Which issue(s) this PR fixes:" title_for_find_content: "What this PR does / why we need it:" - # ============ 非 3.0-dev 分支使用 @main ============ + # ============ 非 3.0-dev 分支使用 coverage integration ref ============ matrixone-ci: name: Matrixone CI needs: check-pr-valid if: ${{ needs.check-pr-valid.outputs.pr_valid == 'true' && github.base_ref != '3.0-dev' }} - uses: matrixorigin/CI/.github/workflows/ci.yaml@main + uses: matrixorigin/CI/.github/workflows/ci.yaml@xp/ci-two-bvt-coverage + secrets: inherit + + matrixone-ut-coverage: + name: Matrixone UT Coverage + needs: check-pr-valid + if: ${{ needs.check-pr-valid.outputs.pr_valid == 'true' && github.base_ref != '3.0-dev' }} + uses: matrixorigin/CI/.github/workflows/coverage-ut.yaml@xp/ci-two-bvt-coverage secrets: inherit matrixone-upgrade-ci: @@ -42,26 +49,30 @@ jobs: name: Matrixone Compose CI needs: check-pr-valid if: ${{ needs.check-pr-valid.outputs.pr_valid == 'true' && github.base_ref != '3.0-dev' }} - uses: matrixorigin/CI/.github/workflows/e2e-compose.yaml@main + uses: matrixorigin/CI/.github/workflows/e2e-compose.yaml@xp/ci-two-bvt-coverage secrets: inherit matrixone-standalone-ci: name: Matrixone Standlone CI needs: check-pr-valid if: ${{ needs.check-pr-valid.outputs.pr_valid == 'true' && github.base_ref != '3.0-dev' }} - uses: matrixorigin/CI/.github/workflows/e2e-standalone.yaml@main + uses: matrixorigin/CI/.github/workflows/e2e-standalone.yaml@xp/ci-two-bvt-coverage secrets: inherit matrixone-utils-ci: name: Matrixone Utils CI - # The coverage gate only merges profiles produced by these existing test - # jobs. Waiting here keeps its report out of the critical test paths while - # ensuring it cannot race artifact publication. - needs: [check-pr-valid, matrixone-ci, matrixone-compose-ci, matrixone-standalone-ci] - if: ${{ always() && needs.check-pr-valid.outputs.pr_valid == 'true' && github.base_ref != '3.0-dev' }} - # Temporary integration reference for the linked CI PR. Restore @main - # after matrixorigin/CI#402 merges. - uses: matrixorigin/CI/.github/workflows/utils.yaml@xp/ci-race-coverage-artifacts + needs: check-pr-valid + if: ${{ needs.check-pr-valid.outputs.pr_valid == 'true' && github.base_ref != '3.0-dev' }} + uses: matrixorigin/CI/.github/workflows/utils.yaml@xp/ci-two-bvt-coverage + secrets: inherit + + matrixone-coverage-merge: + name: Matrixone Coverage Merge + # Race UT intentionally is not a dependency: it produces no coverage + # profile, and must not delay the profile merge. + needs: [check-pr-valid, matrixone-ut-coverage, matrixone-compose-ci, matrixone-standalone-ci] + if: ${{ always() && needs.check-pr-valid.outputs.pr_valid == 'true' && github.base_ref != '3.0-dev' && !github.event.pull_request.draft && needs.matrixone-ut-coverage.result == 'success' && needs.matrixone-ut-coverage.outputs.coverage_ready == 'true' && needs.matrixone-compose-ci.result == 'success' && needs.matrixone-standalone-ci.result == 'success' }} + uses: matrixorigin/CI/.github/workflows/coverage-merge.yaml@xp/ci-two-bvt-coverage secrets: inherit # ============ 3.0-dev 分支使用 @release/3.0-dev ============ diff --git a/etc/launch-tae-compose/compose.yaml b/etc/launch-tae-compose/compose.yaml index f7c84b2922f27..135f5095d295c 100644 --- a/etc/launch-tae-compose/compose.yaml +++ b/etc/launch-tae-compose/compose.yaml @@ -25,7 +25,9 @@ services: cn-0: container_name: cn-0 <<: *mo-common - entrypoint: ["/bin/bash","-c","set -euo pipefail; /mo-service -debug-http :12345 -cfg /config/cn-0.toml | tee -a /log/cn-0.log"] + # exec makes mo-service PID 1, so docker compose down delivers SIGTERM to + # the instrumented process and lets it flush GOCOVERDIR before shutdown. + entrypoint: ["/bin/bash","-c","set -euo pipefail; exec /mo-service -debug-http :12345 -cfg /config/cn-0.toml >> /log/cn-0.log 2>&1"] profiles: - launch - launch-multi-cn @@ -47,7 +49,7 @@ services: cn-1: container_name: cn-1 <<: *mo-common - entrypoint: ["/bin/bash","-c","set -euo pipefail; /mo-service -debug-http :12345 -cfg /config/cn-1.toml | tee -a /log/cn-1.log"] + entrypoint: ["/bin/bash","-c","set -euo pipefail; exec /mo-service -debug-http :12345 -cfg /config/cn-1.toml >> /log/cn-1.log 2>&1"] depends_on: - tn ports: @@ -69,7 +71,7 @@ services: proxy: container_name: proxy <<: *mo-common - entrypoint: ["/bin/bash","-c","set -euo pipefail; /mo-service -debug-http :12345 -cfg /config/proxy.toml | tee -a /log/proxy.log"] + entrypoint: ["/bin/bash","-c","set -euo pipefail; exec /mo-service -debug-http :12345 -cfg /config/proxy.toml >> /log/proxy.log 2>&1"] profiles: - launch - launch-multi-cn @@ -93,7 +95,7 @@ services: tn: container_name: tn <<: *mo-common - entrypoint: ["/bin/bash","-c","set -euo pipefail; /mo-service -debug-http :12345 -cfg /config/tn.toml | tee -a /log/tn.log"] + entrypoint: ["/bin/bash","-c","set -euo pipefail; exec /mo-service -debug-http :12345 -cfg /config/tn.toml >> /log/tn.log 2>&1"] profiles: - launch - launch-multi-cn @@ -118,7 +120,7 @@ services: profiles: - launch - launch-multi-cn - entrypoint: ["/bin/bash","-c","set -euo pipefail; /mo-service -debug-http :12345 -cfg /config/log.toml | tee -a /log/logservice.log"] + entrypoint: ["/bin/bash","-c","set -euo pipefail; exec /mo-service -debug-http :12345 -cfg /config/log.toml >> /log/logservice.log 2>&1"] depends_on: - createbuckets ports: diff --git a/optools/run_bvt_group.sh b/optools/run_bvt_group.sh index ae725d9fa2289..58003ca598d49 100644 --- a/optools/run_bvt_group.sh +++ b/optools/run_bvt_group.sh @@ -1,9 +1,9 @@ #!/usr/bin/env bash -# Run one of the three time-balanced BVT groups. The groups were derived from -# successful CI reports: pessimistic_transaction is the long group; snapshot, -# optimizer, fulltext and git4data form the second; the remaining cases form -# the third. New top-level directories are assigned deterministically. +# Run one of the two time-balanced BVT groups. Group 0 carries the especially +# expensive pessimistic_transaction suite and is otherwise balanced by case +# count; Group 1 contains the complement. New top-level directories are +# assigned deterministically so every case is exercised immediately. set -euo pipefail if [[ $# -ne 3 ]]; then @@ -15,8 +15,8 @@ tester_dir=$(cd "$1" && pwd) case_root=$(cd "$2" && pwd) group_id=$3 -if ! [[ "${group_id}" =~ ^[0-2]$ ]]; then - echo "BVT group must be 0, 1, or 2; got '${group_id}'" >&2 +if ! [[ "${group_id}" =~ ^[01]$ ]]; then + echo "BVT group must be 0 or 1; got '${group_id}'" >&2 exit 2 fi @@ -27,13 +27,16 @@ while IFS= read -r script_path; do top_level=${relative_path%%/*} case "${top_level}" in - pessimistic_transaction) assigned_group=0 ;; - snapshot|optimizer|fulltext|git4data) assigned_group=1 ;; + array|auto_increment|benchmark|dataXtest|ddl|disttae|fake_pk|git4data|hint|join|keyword|load_data|log|mo_cloud|optimizer|pessimistic_transaction|pg_cast|plugin|prepare|procedure|query_result|sample|save_query_result|sequence|sql_inject|stage|system|system_variable|temporary|tenant|tenxcloud_xx|time_window|union|util|vector|view) + assigned_group=0 + ;; + analyze|charset_collation|comment|cte|database|distinct|dml|dtype|expression|feature_limit|foreign_key|fulltext|function|geo|iceberg|metadata|operator|pitr|plan_cache|publication_subscription|qexec|recursive_cte|replace_statement|result_count|security|set|snapshot|sql_source_type|statement_query_type|subquery|table|task|udf|window|zz_accesscontrol|zz_statement_query_type) + assigned_group=1 + ;; *) - # Group 2 is the complete complement of the two explicitly - # measured heavy groups. A new directory is covered immediately - # here and becomes a signal to refresh the timing balance. - assigned_group=2 + # Do not defer newly added suites: deterministically place them in + # one group and rebalance the explicit list from collected timing. + assigned_group=$(( $(printf '%s' "${top_level}" | cksum | awk '{print $1}') % 2 )) ;; esac From 8a7f71e9cbff3afc3164b018ce64fefd42b36ad2 Mon Sep 17 00:00:00 2001 From: XuPeng-SH Date: Sun, 26 Jul 2026 20:03:56 +0800 Subject: [PATCH 05/25] ci: add scoped coverage integration harness --- .github/workflows/coverage-integration.yaml | 46 +++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 .github/workflows/coverage-integration.yaml diff --git a/.github/workflows/coverage-integration.yaml b/.github/workflows/coverage-integration.yaml new file mode 100644 index 0000000000000..82fb391d38b9c --- /dev/null +++ b/.github/workflows/coverage-integration.yaml @@ -0,0 +1,46 @@ +name: MatrixOne Coverage Integration + +on: + pull_request: + types: [opened, synchronize, reopened] + branches: [main] + +concurrency: + group: ${{ github.event.pull_request.head.repo.full_name }}/${{ github.event.pull_request.head.ref }}/${{ github.workflow }} + cancel-in-progress: true + +# pull_request_target executes the base branch workflow by design, so it cannot +# validate reusable-workflow changes made in this PR. This short-lived harness +# runs only for this trusted same-repository integration branch and disappears +# from normal PR execution after the branch is merged or deleted. +jobs: + matrixone-ci: + if: ${{ github.event.pull_request.head.repo.full_name == github.repository && github.event.pull_request.head.ref == 'xp/ci-race-coverage-artifacts' }} + uses: matrixorigin/CI/.github/workflows/ci.yaml@xp/ci-two-bvt-coverage + secrets: inherit + + matrixone-ut-coverage: + if: ${{ github.event.pull_request.head.repo.full_name == github.repository && github.event.pull_request.head.ref == 'xp/ci-race-coverage-artifacts' }} + uses: matrixorigin/CI/.github/workflows/coverage-ut.yaml@xp/ci-two-bvt-coverage + secrets: inherit + + matrixone-compose-ci: + if: ${{ github.event.pull_request.head.repo.full_name == github.repository && github.event.pull_request.head.ref == 'xp/ci-race-coverage-artifacts' }} + uses: matrixorigin/CI/.github/workflows/e2e-compose.yaml@xp/ci-two-bvt-coverage + secrets: inherit + + matrixone-standalone-ci: + if: ${{ github.event.pull_request.head.repo.full_name == github.repository && github.event.pull_request.head.ref == 'xp/ci-race-coverage-artifacts' }} + uses: matrixorigin/CI/.github/workflows/e2e-standalone.yaml@xp/ci-two-bvt-coverage + secrets: inherit + + matrixone-utils-ci: + if: ${{ github.event.pull_request.head.repo.full_name == github.repository && github.event.pull_request.head.ref == 'xp/ci-race-coverage-artifacts' }} + uses: matrixorigin/CI/.github/workflows/utils.yaml@xp/ci-two-bvt-coverage + secrets: inherit + + matrixone-coverage-merge: + needs: [matrixone-ut-coverage, matrixone-compose-ci, matrixone-standalone-ci] + if: ${{ always() && github.event.pull_request.head.repo.full_name == github.repository && github.event.pull_request.head.ref == 'xp/ci-race-coverage-artifacts' && !github.event.pull_request.draft && needs.matrixone-ut-coverage.result == 'success' && needs.matrixone-ut-coverage.outputs.coverage_ready == 'true' && needs.matrixone-compose-ci.result == 'success' && needs.matrixone-standalone-ci.result == 'success' }} + uses: matrixorigin/CI/.github/workflows/coverage-merge.yaml@xp/ci-two-bvt-coverage + secrets: inherit From b33655cf46c2cd33797fb4b563ab6d1771f0a533 Mon Sep 17 00:00:00 2001 From: XuPeng-SH Date: Sun, 26 Jul 2026 20:43:21 +0800 Subject: [PATCH 06/25] ci: rebalance BVT groups by observed timing --- optools/run_bvt_group.sh | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/optools/run_bvt_group.sh b/optools/run_bvt_group.sh index 58003ca598d49..6b6f7540dc8ff 100644 --- a/optools/run_bvt_group.sh +++ b/optools/run_bvt_group.sh @@ -1,9 +1,14 @@ #!/usr/bin/env bash -# Run one of the two time-balanced BVT groups. Group 0 carries the especially -# expensive pessimistic_transaction suite and is otherwise balanced by case -# count; Group 1 contains the complement. New top-level directories are -# assigned deterministically so every case is exercised immediately. +# Run one of the two time-balanced BVT groups. The assignment is based on the +# latest successful full Compose BVT timing: group 0 is 1285s and group 1 is +# 1268s in that baseline. Keep the statement-info producers and verifiers in +# one group: mo-tester runs directories in lexical order, so log, +# result_count, sql_source_type, and statement_query_type run before +# zz_statement_query_type. +# +# New top-level directories are assigned deterministically so every case is +# exercised immediately; rebalance the explicit list from collected timing. set -euo pipefail if [[ $# -ne 3 ]]; then @@ -27,10 +32,10 @@ while IFS= read -r script_path; do top_level=${relative_path%%/*} case "${top_level}" in - array|auto_increment|benchmark|dataXtest|ddl|disttae|fake_pk|git4data|hint|join|keyword|load_data|log|mo_cloud|optimizer|pessimistic_transaction|pg_cast|plugin|prepare|procedure|query_result|sample|save_query_result|sequence|sql_inject|stage|system|system_variable|temporary|tenant|tenxcloud_xx|time_window|union|util|vector|view) + array|auto_increment|benchmark|dataXtest|ddl|disttae|fake_pk|fulltext|function|git4data|hint|join|keyword|load_data|mo_cloud|optimizer|pg_cast|plugin|prepare|procedure|query_result|sample|save_query_result|sequence|snapshot|sql_inject|stage|system|system_variable|temporary|tenant|tenxcloud_xx|time_window|union|util|vector|view) assigned_group=0 ;; - analyze|charset_collation|comment|cte|database|distinct|dml|dtype|expression|feature_limit|foreign_key|fulltext|function|geo|iceberg|metadata|operator|pitr|plan_cache|publication_subscription|qexec|recursive_cte|replace_statement|result_count|security|set|snapshot|sql_source_type|statement_query_type|subquery|table|task|udf|window|zz_accesscontrol|zz_statement_query_type) + analyze|charset_collation|comment|cte|database|distinct|dml|dtype|expression|feature_limit|foreign_key|geo|iceberg|log|metadata|operator|pessimistic_transaction|pitr|plan_cache|publication_subscription|qexec|recursive_cte|replace_statement|result_count|security|set|sql_source_type|statement_query_type|subquery|table|task|udf|window|zz_accesscontrol|zz_statement_query_type) assigned_group=1 ;; *) From f1cd1309e68c6e8def84676dbf1eb420dd3a9f31 Mon Sep 17 00:00:00 2001 From: XuPeng-SH Date: Sun, 26 Jul 2026 21:35:53 +0800 Subject: [PATCH 07/25] fix: respect context in qcloud retries --- pkg/fileservice/file_service_test.go | 10 +++++++--- pkg/fileservice/qcloud_sdk.go | 26 +++++++++++++++----------- pkg/fileservice/qcloud_sdk_test.go | 20 ++++++++++++++++++++ 3 files changed, 42 insertions(+), 14 deletions(-) diff --git a/pkg/fileservice/file_service_test.go b/pkg/fileservice/file_service_test.go index 38f8f2d6306b7..25b2bed079154 100644 --- a/pkg/fileservice/file_service_test.go +++ b/pkg/fileservice/file_service_test.go @@ -886,7 +886,11 @@ func testFileService( }) t.Run("streaming write", func(t *testing.T) { - ctx := context.Background() + // An object-storage request must not be able to consume the package's + // entire Go test timeout. This still leaves enough time for the SDK's + // bounded retry policy to recover from a transient remote failure. + ctx, cancel := context.WithTimeout(context.Background(), 3*time.Minute) + defer cancel() fs := newFS(fsName) defer fs.Close(ctx) @@ -981,11 +985,11 @@ func testFileService( }, Policy: policy, } - ctx, cancel := context.WithCancel(context.Background()) + cancelCtx, cancel := context.WithCancel(context.Background()) cancel() errCh := make(chan error) go func() { - err := fs.Write(ctx, vec) + err := fs.Write(cancelCtx, vec) errCh <- err }() select { diff --git a/pkg/fileservice/qcloud_sdk.go b/pkg/fileservice/qcloud_sdk.go index e44664b980e07..74a313eb2efc1 100644 --- a/pkg/fileservice/qcloud_sdk.go +++ b/pkg/fileservice/qcloud_sdk.go @@ -122,7 +122,7 @@ func NewQCloudSDK( if !args.NoBucketValidation { // validate bucket - _, err := DoWithRetry("cos bucket head", func() (*cos.Response, error) { + _, err := DoWithRetryContext(ctx, "cos bucket head", func() (*cos.Response, error) { return client.Bucket.Head(ctx, &cos.BucketHeadOptions{}) }, maxRetryAttemps, IsRetryableError) if err != nil { @@ -285,7 +285,7 @@ func (a *QCloudSDK) Write( if err != nil { return err } - _, err = DoWithRetry("write", func() (int, error) { + _, err = DoWithRetryContext(ctx, "write", func() (int, error) { return 0, a.putObject( ctx, key, @@ -315,7 +315,7 @@ func (a *QCloudSDK) Write( if err != nil { return err } - _, err = DoWithRetry("write", func() (int, error) { + _, err = DoWithRetryContext(ctx, "write", func() (int, error) { if _, err := seeker.Seek(offset, io.SeekStart); err != nil { return 0, err } @@ -428,7 +428,7 @@ func (a *QCloudSDK) WriteMultipartParallel( Expires: expiresHeader, }, } - output, createErr := DoWithRetry("cos initiate multipart upload", func() (*cos.InitiateMultipartUploadResult, error) { + output, createErr := DoWithRetryContext(ctx, "cos initiate multipart upload", func() (*cos.InitiateMultipartUploadResult, error) { res, _, e := a.client.Object.InitiateMultipartUpload(ctx, key, initOpt) return res, e }, maxRetryAttemps, IsRetryableError) @@ -439,7 +439,7 @@ func (a *QCloudSDK) WriteMultipartParallel( defer func() { if err != nil { - _, _ = DoWithRetry("cos abort multipart upload", func() (*cos.Response, error) { + _, _ = DoWithRetryContext(context.WithoutCancel(parentCtx), "cos abort multipart upload", func() (*cos.Response, error) { return a.client.Object.AbortMultipartUpload( context.WithoutCancel(parentCtx), key, output.UploadID) }, maxRetryAttemps, IsRetryableError) @@ -501,7 +501,7 @@ func (a *QCloudSDK) WriteMultipartParallel( uploadOpt := &cos.ObjectUploadPartOptions{ ContentLength: int64(job.part.n), } - resp, uploadErr := DoWithRetry("cos upload part", func() (*cos.Response, error) { + resp, uploadErr := DoWithRetryContext(ctx, "cos upload part", func() (*cos.Response, error) { recordS3PutRequest(ctx, a.perfCounterSets...) return a.client.Object.UploadPart(ctx, key, output.UploadID, int(job.num), bytes.NewReader(job.part.buf[:job.part.n]), uploadOpt) }, maxRetryAttemps, IsRetryableError) @@ -584,7 +584,7 @@ func (a *QCloudSDK) WriteMultipartParallel( completeOpt := &cos.CompleteMultipartUploadOptions{ Parts: parts, } - _, err = DoWithRetry("cos complete multipart upload", func() (*cos.CompleteMultipartUploadResult, error) { + _, err = DoWithRetryContext(ctx, "cos complete multipart upload", func() (*cos.CompleteMultipartUploadResult, error) { res, _, e := a.client.Object.CompleteMultipartUpload(ctx, key, output.UploadID, completeOpt) return res, e }, maxRetryAttemps, IsRetryableError) @@ -703,7 +703,8 @@ func (a *QCloudSDK) listObjects(ctx context.Context, prefix string, marker strin opts.MaxKeys = a.listMaxKeys } - return DoWithRetry( + return DoWithRetryContext( + ctx, "s3 list objects", func() (*cos.BucketGetResult, error) { perfcounter.Update(ctx, func(counter *perfcounter.CounterSet) { @@ -791,7 +792,8 @@ func (a *QCloudSDK) getObject(ctx context.Context, key string, min *int64, max * Range: rang, } - return DoWithRetry( + return DoWithRetryContext( + ctx, "s3 get object", func() (io.ReadCloser, error) { perfcounter.Update(ctx, func(counter *perfcounter.CounterSet) { @@ -823,7 +825,8 @@ func (a *QCloudSDK) getObject(ctx context.Context, key string, min *int64, max * func (a *QCloudSDK) deleteObject(ctx context.Context, key string) (bool, error) { ctx, task := gotrace.NewTask(ctx, "QCloudSDK.deleteObject") defer task.End() - return DoWithRetry( + return DoWithRetryContext( + ctx, "s3 delete object", func() (bool, error) { perfcounter.Update(ctx, func(counter *perfcounter.CounterSet) { @@ -842,7 +845,8 @@ func (a *QCloudSDK) deleteObject(ctx context.Context, key string) (bool, error) func (a *QCloudSDK) deleteObjects(ctx context.Context, keys ...string) (bool, error) { ctx, task := gotrace.NewTask(ctx, "QCloudSDK.deleteObjects") defer task.End() - return DoWithRetry( + return DoWithRetryContext( + ctx, "s3 delete objects", func() (bool, error) { objects := make([]cos.Object, 0, len(keys)) diff --git a/pkg/fileservice/qcloud_sdk_test.go b/pkg/fileservice/qcloud_sdk_test.go index 6a5d32a298eba..792cb6d291562 100644 --- a/pkg/fileservice/qcloud_sdk_test.go +++ b/pkg/fileservice/qcloud_sdk_test.go @@ -26,6 +26,7 @@ import ( "net/url" "strings" "testing" + "time" "github.com/matrixorigin/matrixone/pkg/common/moerr" "github.com/stretchr/testify/require" @@ -398,6 +399,25 @@ func TestQCloudSDKBasicObjectOperations(t *testing.T) { } } +func TestQCloudSDKListStopsRetryingAtContextDeadline(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusBadGateway) + })) + defer server.Close() + + sdk := newTestCOSClient(t, server) + ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond) + defer cancel() + + for _, err := range sdk.List(ctx, "") { + if !errors.Is(err, context.DeadlineExceeded) { + t.Fatalf("expected context deadline exceeded, got %v", err) + } + return + } + t.Fatal("expected list to return a deadline error") +} + type timeoutError struct{} func (timeoutError) Error() string { From 40e358da048bd0fb5041776bd90b9bab48262c20 Mon Sep 17 00:00:00 2001 From: XuPeng-SH Date: Sun, 26 Jul 2026 22:08:31 +0800 Subject: [PATCH 08/25] fix: bound object storage retries by context --- pkg/fileservice/aliyun_sdk.go | 14 ++++++++---- pkg/fileservice/aliyun_sdk_test.go | 34 ++++++++++++++++++++++++++++ pkg/fileservice/aws_sdk_v2.go | 26 ++++++++++++--------- pkg/fileservice/file_service_test.go | 31 +++++++++++++------------ pkg/fileservice/hdfs.go | 24 ++++++++++---------- pkg/fileservice/minio_sdk.go | 14 ++++++++---- 6 files changed, 95 insertions(+), 48 deletions(-) diff --git a/pkg/fileservice/aliyun_sdk.go b/pkg/fileservice/aliyun_sdk.go index 4765e0afe79bf..d0d4d4b954523 100644 --- a/pkg/fileservice/aliyun_sdk.go +++ b/pkg/fileservice/aliyun_sdk.go @@ -277,7 +277,7 @@ func (a *AliyunSDK) Write( if err != nil { return err } - _, err = DoWithRetry("write", func() (int, error) { + _, err = DoWithRetryContext(ctx, "write", func() (int, error) { return 0, a.putObject( ctx, key, @@ -413,7 +413,8 @@ func (a *AliyunSDK) listObjects(ctx context.Context, prefix string, cont string) if a.listMaxKeys > 0 { opts = append(opts, oss.MaxKeys(a.listMaxKeys)) } - return DoWithRetry( + return DoWithRetryContext( + ctx, "s3 list objects", func() (oss.ListObjectsResultV2, error) { perfcounter.Update(ctx, func(counter *perfcounter.CounterSet) { @@ -510,7 +511,8 @@ func (a *AliyunSDK) getObject(ctx context.Context, key string, min *int64, max * } opts = append(opts, oss.NormalizedRange(rang)) opts = append(opts, oss.RangeBehavior("standard")) - r, err := DoWithRetry( + r, err := DoWithRetryContext( + ctx, "s3 get object", func() (io.ReadCloser, error) { perfcounter.Update(ctx, func(counter *perfcounter.CounterSet) { @@ -548,7 +550,8 @@ func (a *AliyunSDK) getObject(ctx context.Context, key string, min *int64, max * func (a *AliyunSDK) deleteObject(ctx context.Context, key string) (bool, error) { ctx, task := gotrace.NewTask(ctx, "AliyunSDK.deleteObject") defer task.End() - return DoWithRetry( + return DoWithRetryContext( + ctx, "s3 delete object", func() (bool, error) { perfcounter.Update(ctx, func(counter *perfcounter.CounterSet) { @@ -570,7 +573,8 @@ func (a *AliyunSDK) deleteObject(ctx context.Context, key string) (bool, error) func (a *AliyunSDK) deleteObjects(ctx context.Context, keys ...string) (bool, error) { ctx, task := gotrace.NewTask(ctx, "AliyunSDK.deleteObjects") defer task.End() - return DoWithRetry( + return DoWithRetryContext( + ctx, "s3 delete objects", func() (bool, error) { perfcounter.Update(ctx, func(counter *perfcounter.CounterSet) { diff --git a/pkg/fileservice/aliyun_sdk_test.go b/pkg/fileservice/aliyun_sdk_test.go index 43ea229e08284..aad59baf2a40c 100644 --- a/pkg/fileservice/aliyun_sdk_test.go +++ b/pkg/fileservice/aliyun_sdk_test.go @@ -88,6 +88,40 @@ func TestAliyunSDKCopyObjectPropagatesCancellation(t *testing.T) { } } +func TestAliyunSDKListStopsRetryingAtContextDeadline(t *testing.T) { + client, err := oss.New( + "http://oss.test", + "id", + "secret", + oss.ForcePathStyle(true), + oss.HTTPClient(&http.Client{Transport: aliyunRetryTimeoutTransport{}}), + ) + require.NoError(t, err) + bucket, err := client.Bucket("bucket") + require.NoError(t, err) + sdk := &AliyunSDK{bucket: bucket} + + ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond) + defer cancel() + for _, err := range sdk.List(ctx, "") { + require.ErrorIs(t, err, context.DeadlineExceeded) + return + } + t.Fatal("expected list to return a deadline error") +} + +type aliyunRetryTimeoutTransport struct{} + +func (aliyunRetryTimeoutTransport) RoundTrip(*http.Request) (*http.Response, error) { + return nil, aliyunRetryTimeoutError{} +} + +type aliyunRetryTimeoutError struct{} + +func (aliyunRetryTimeoutError) Error() string { return "timeout" } +func (aliyunRetryTimeoutError) Timeout() bool { return true } +func (aliyunRetryTimeoutError) Temporary() bool { return true } + func TestAliyunPutObjectPhysicalAccounting(t *testing.T) { var fail bool server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { diff --git a/pkg/fileservice/aws_sdk_v2.go b/pkg/fileservice/aws_sdk_v2.go index 0195522cbab77..9695924067a09 100644 --- a/pkg/fileservice/aws_sdk_v2.go +++ b/pkg/fileservice/aws_sdk_v2.go @@ -365,7 +365,7 @@ func (a *AwsSDKv2) Write( if sizeHint == nil { // multipart - output, err := DoWithRetry("create multipart upload", func() (*s3.CreateMultipartUploadOutput, error) { + output, err := DoWithRetryContext(ctx, "create multipart upload", func() (*s3.CreateMultipartUploadOutput, error) { return a.client.CreateMultipartUpload(ctx, &s3.CreateMultipartUploadInput{ Bucket: ptrTo(a.bucket), Key: ptrTo(key), @@ -400,7 +400,7 @@ func (a *AwsSDKv2) Write( if len(content) == 0 { break } - uploadOutput, err := DoWithRetry("upload part", func() (*s3.UploadPartOutput, error) { + uploadOutput, err := DoWithRetryContext(ctx, "upload part", func() (*s3.UploadPartOutput, error) { recordS3PutRequest(ctx, a.perfCounterSets...) return a.client.UploadPart(ctx, &s3.UploadPartInput{ Bucket: ptrTo(a.bucket), @@ -426,7 +426,7 @@ func (a *AwsSDKv2) Write( } // complete - _, err = DoWithRetry("complete multipart upload", func() (*s3.CompleteMultipartUploadOutput, error) { + _, err = DoWithRetryContext(ctx, "complete multipart upload", func() (*s3.CompleteMultipartUploadOutput, error) { return a.client.CompleteMultipartUpload(ctx, &s3.CompleteMultipartUploadInput{ Bucket: ptrTo(a.bucket), Key: ptrTo(key), @@ -443,7 +443,7 @@ func (a *AwsSDKv2) Write( if err != nil { return err } - _, err = DoWithRetry("write", func() (*s3.PutObjectOutput, error) { + _, err = DoWithRetryContext(ctx, "write", func() (*s3.PutObjectOutput, error) { return a.putObject( ctx, &s3.PutObjectInput{ @@ -578,7 +578,7 @@ func (a *AwsSDKv2) WriteMultipartParallel( return a.Write(ctx, key, bytes.NewReader(data), &size, options.Expire) } - output, createErr := DoWithRetry("create multipart upload", func() (*s3.CreateMultipartUploadOutput, error) { + output, createErr := DoWithRetryContext(ctx, "create multipart upload", func() (*s3.CreateMultipartUploadOutput, error) { return a.client.CreateMultipartUpload(ctx, &s3.CreateMultipartUploadInput{ Bucket: ptrTo(a.bucket), Key: ptrTo(key), @@ -653,7 +653,7 @@ func (a *AwsSDKv2) WriteMultipartParallel( releasePartBuffer(job.part) return } - uploadOutput, uploadErr := DoWithRetry("upload part", func() (*s3.UploadPartOutput, error) { + uploadOutput, uploadErr := DoWithRetryContext(ctx, "upload part", func() (*s3.UploadPartOutput, error) { recordS3PutRequest(ctx, a.perfCounterSets...) return a.client.UploadPart(ctx, &s3.UploadPartInput{ Bucket: ptrTo(a.bucket), @@ -735,7 +735,7 @@ func (a *AwsSDKv2) WriteMultipartParallel( return cmp.Compare(*a.PartNumber, *b.PartNumber) }) - _, err = DoWithRetry("complete multipart upload", func() (*s3.CompleteMultipartUploadOutput, error) { + _, err = DoWithRetryContext(ctx, "complete multipart upload", func() (*s3.CompleteMultipartUploadOutput, error) { return a.client.CompleteMultipartUpload(ctx, &s3.CompleteMultipartUploadInput{ Bucket: ptrTo(a.bucket), Key: ptrTo(key), @@ -919,7 +919,8 @@ func (a *AwsSDKv2) deleteMultiObjOneByOne(ctx context.Context, objs []types.Obje func (a *AwsSDKv2) listObjects(ctx context.Context, params *s3.ListObjectsInput, optFns ...func(*s3.Options)) (*s3.ListObjectsOutput, error) { ctx, task := gotrace.NewTask(ctx, "AwsSDKv2.listObjects") defer task.End() - return DoWithRetry( + return DoWithRetryContext( + ctx, "s3 list objects", func() (*s3.ListObjectsOutput, error) { perfcounter.Update(ctx, func(counter *perfcounter.CounterSet) { @@ -978,7 +979,8 @@ func (a *AwsSDKv2) getObject(ctx context.Context, min *int64, max *int64, params rang = fmt.Sprintf("bytes=%d-", offset) } params.Range = &rang - output, err := DoWithRetry( + output, err := DoWithRetryContext( + ctx, "s3 get object", func() (*s3.GetObjectOutput, error) { LogEvent(ctx, str_awssdkv2_get_object_begin) @@ -1015,7 +1017,8 @@ func (a *AwsSDKv2) getObject(ctx context.Context, min *int64, max *int64, params func (a *AwsSDKv2) deleteObject(ctx context.Context, params *s3.DeleteObjectInput, optFns ...func(*s3.Options)) (*s3.DeleteObjectOutput, error) { ctx, task := gotrace.NewTask(ctx, "AwsSDKv2.deleteObject") defer task.End() - return DoWithRetry( + return DoWithRetryContext( + ctx, "s3 delete object", func() (*s3.DeleteObjectOutput, error) { perfcounter.Update(ctx, func(counter *perfcounter.CounterSet) { @@ -1031,7 +1034,8 @@ func (a *AwsSDKv2) deleteObject(ctx context.Context, params *s3.DeleteObjectInpu func (a *AwsSDKv2) deleteObjects(ctx context.Context, params *s3.DeleteObjectsInput, optFns ...func(*s3.Options)) (*s3.DeleteObjectsOutput, error) { ctx, task := gotrace.NewTask(ctx, "AwsSDKv2.deleteObjects") defer task.End() - return DoWithRetry( + return DoWithRetryContext( + ctx, "s3 delete objects", func() (*s3.DeleteObjectsOutput, error) { perfcounter.Update(ctx, func(counter *perfcounter.CounterSet) { diff --git a/pkg/fileservice/file_service_test.go b/pkg/fileservice/file_service_test.go index 25b2bed079154..04c35837c2c04 100644 --- a/pkg/fileservice/file_service_test.go +++ b/pkg/fileservice/file_service_test.go @@ -47,9 +47,14 @@ func testFileService( ) { fsName := time.Now().Format("fs-2006-01-02-15-04-05") + // Real object-storage specs are part of this shared test suite. Bound the + // whole suite, rather than only one subtest, so a remote outage cannot use + // the package-level Go test timeout. + testCtx, cancel := context.WithTimeout(context.Background(), 3*time.Minute) + defer cancel() t.Run("basic", func(t *testing.T) { - ctx := context.Background() + ctx := testCtx fs := newFS(fsName) defer fs.Close(ctx) @@ -182,7 +187,7 @@ func testFileService( t.Run("WriterForRead", func(t *testing.T) { fs := newFS(fsName) - ctx := context.Background() + ctx := testCtx defer fs.Close(ctx) err := fs.Write(ctx, IOVector{ @@ -233,7 +238,7 @@ func testFileService( }) t.Run("ReadCloserForRead", func(t *testing.T) { - ctx := context.Background() + ctx := testCtx fs := newFS(fsName) defer fs.Close(ctx) @@ -313,7 +318,7 @@ func testFileService( t.Run("random", func(t *testing.T) { fs := newFS(fsName) - ctx := context.Background() + ctx := testCtx defer fs.Close(ctx) for i := 0; i < 8; i++ { @@ -443,7 +448,7 @@ func testFileService( t.Run("tree", func(t *testing.T) { fs := newFS(fsName) - ctx := context.Background() + ctx := testCtx defer fs.Close(ctx) for _, dir := range []string{ @@ -574,7 +579,7 @@ func testFileService( t.Run("errors", func(t *testing.T) { fs := newFS(fsName) - ctx := context.Background() + ctx := testCtx defer fs.Close(ctx) err := fs.Read(ctx, &IOVector{ @@ -678,7 +683,7 @@ func testFileService( }) t.Run("cache data", func(t *testing.T) { - ctx := context.Background() + ctx := testCtx fs := newFS(fsName) defer fs.Close(ctx) var counterSet perfcounter.CounterSet @@ -755,7 +760,7 @@ func testFileService( }) t.Run("ignore", func(t *testing.T) { - ctx := context.Background() + ctx := testCtx fs := newFS(fsName) defer fs.Close(ctx) @@ -793,7 +798,7 @@ func testFileService( }) t.Run("named path", func(t *testing.T) { - ctx := context.Background() + ctx := testCtx fs := newFS(fsName) defer fs.Close(ctx) @@ -863,7 +868,7 @@ func testFileService( }) t.Run("issue6110", func(t *testing.T) { - ctx := context.Background() + ctx := testCtx fs := newFS(fsName) defer fs.Close(ctx) @@ -886,11 +891,7 @@ func testFileService( }) t.Run("streaming write", func(t *testing.T) { - // An object-storage request must not be able to consume the package's - // entire Go test timeout. This still leaves enough time for the SDK's - // bounded retry policy to recover from a transient remote failure. - ctx, cancel := context.WithTimeout(context.Background(), 3*time.Minute) - defer cancel() + ctx := testCtx fs := newFS(fsName) defer fs.Close(ctx) diff --git a/pkg/fileservice/hdfs.go b/pkg/fileservice/hdfs.go index 42f943989c3d2..4a99376b902e6 100644 --- a/pkg/fileservice/hdfs.go +++ b/pkg/fileservice/hdfs.go @@ -133,7 +133,7 @@ func (h *HDFS) Delete(ctx context.Context, keys ...string) (err error) { }, h.perfCounterSets...) for _, key := range keys { - _, err := DoWithRetry("HDFS: delete", func() (bool, error) { + _, err := DoWithRetryContext(ctx, "HDFS: delete", func() (bool, error) { return true, h.client.Remove(h.keyToPath(key)) }, maxRetryAttemps, IsRetryableError) if err != nil { @@ -153,21 +153,21 @@ func (h *HDFS) keyToPath(key string) string { return ret } -func (h *HDFS) createTemp() (*hdfs.FileWriter, string, error) { +func (h *HDFS) createTemp(ctx context.Context) (*hdfs.FileWriter, string, error) { for { randomBytes := make([]byte, 26) rand.Read(randomBytes) name := h.keyToPath( base32.HexEncoding.WithPadding(base32.NoPadding).EncodeToString(randomBytes), ) - _, err := DoWithRetry("HDFS: stat", func() (os.FileInfo, error) { + _, err := DoWithRetryContext(ctx, "HDFS: stat", func() (os.FileInfo, error) { return h.client.Stat(name) }, maxRetryAttemps, IsRetryableError) if err == nil { // existed continue } - file, err := DoWithRetry("HDFS: create", func() (*hdfs.FileWriter, error) { + file, err := DoWithRetryContext(ctx, "HDFS: create", func() (*hdfs.FileWriter, error) { return h.client.Create(name) }, maxRetryAttemps, IsRetryableError) if err != nil { @@ -186,7 +186,7 @@ func (h *HDFS) Exists(ctx context.Context, key string) (bool, error) { counter.FileService.S3.Head.Add(1) }, h.perfCounterSets...) - _, err := DoWithRetry("HDFS: stat", func() (os.FileInfo, error) { + _, err := DoWithRetryContext(ctx, "HDFS: stat", func() (os.FileInfo, error) { return h.client.Stat(h.keyToPath(key)) }, maxRetryAttemps, IsRetryableError) if err != nil { @@ -212,7 +212,7 @@ func (h *HDFS) List(ctx context.Context, prefix string) iter.Seq2[*DirEntry, err dir, prefix := path.Split(prefix) - f, err := DoWithRetry("HDFS: open", func() (*hdfs.FileReader, error) { + f, err := DoWithRetryContext(ctx, "HDFS: open", func() (*hdfs.FileReader, error) { return h.client.Open(h.keyToPath(dir)) }, maxRetryAttemps, IsRetryableError) if err != nil { @@ -271,7 +271,7 @@ func (h *HDFS) Read(ctx context.Context, key string, min *int64, max *int64) (r counter.FileService.S3.Get.Add(1) }, h.perfCounterSets...) - f, err := DoWithRetry("HDFS: open", func() (*hdfs.FileReader, error) { + f, err := DoWithRetryContext(ctx, "HDFS: open", func() (*hdfs.FileReader, error) { return h.client.Open(h.keyToPath(key)) }, maxRetryAttemps, IsRetryableError) if err != nil { @@ -321,7 +321,7 @@ func (h *HDFS) Stat(ctx context.Context, key string) (size int64, err error) { counter.FileService.S3.Head.Add(1) }, h.perfCounterSets...) - stat, err := DoWithRetry("HDFS: stat", func() (os.FileInfo, error) { + stat, err := DoWithRetryContext(ctx, "HDFS: stat", func() (os.FileInfo, error) { return h.client.Stat(h.keyToPath(key)) }, maxRetryAttemps, IsRetryableError) if err != nil { @@ -345,7 +345,7 @@ func (h *HDFS) Write(ctx context.Context, key string, r io.Reader, sizeHint *int counter.FileService.S3.Put.Add(1) }, h.perfCounterSets...) - tempFile, tempFilePath, err := h.createTemp() + tempFile, tempFilePath, err := h.createTemp(ctx) if err != nil { return err } @@ -368,7 +368,7 @@ func (h *HDFS) Write(ctx context.Context, key string, r io.Reader, sizeHint *int return moerr.NewSizeNotMatchNoCtx(key) } - _, err = DoWithRetry("HDFS: FileWriter.Close", func() (bool, error) { + _, err = DoWithRetryContext(ctx, "HDFS: FileWriter.Close", func() (bool, error) { // may retrun 'replication in progress' error, retry until OK return true, tempFile.Close() }, maxRetryAttemps, IsRetryableError) @@ -377,7 +377,7 @@ func (h *HDFS) Write(ctx context.Context, key string, r io.Reader, sizeHint *int } filePath := h.keyToPath(key) - _, err = DoWithRetry("HDFS: MkdirAll", func() (bool, error) { + _, err = DoWithRetryContext(ctx, "HDFS: MkdirAll", func() (bool, error) { return true, h.client.MkdirAll(path.Dir(filePath), 0755) }, maxRetryAttemps, IsRetryableError) if err != nil { @@ -386,7 +386,7 @@ func (h *HDFS) Write(ctx context.Context, key string, r io.Reader, sizeHint *int } } - _, err = DoWithRetry("HDFS: Rename", func() (bool, error) { + _, err = DoWithRetryContext(ctx, "HDFS: Rename", func() (bool, error) { return true, h.client.Rename(tempFilePath, filePath) }, maxRetryAttemps, IsRetryableError) if err != nil { diff --git a/pkg/fileservice/minio_sdk.go b/pkg/fileservice/minio_sdk.go index ebd4e7e58ab12..653cf5bfc411d 100644 --- a/pkg/fileservice/minio_sdk.go +++ b/pkg/fileservice/minio_sdk.go @@ -369,7 +369,7 @@ func (a *MinioSDK) Write( if err != nil { return err } - _, err = DoWithRetry("write", func() (minio.UploadInfo, error) { + _, err = DoWithRetryContext(ctx, "write", func() (minio.UploadInfo, error) { return a.putObject( ctx, key, @@ -500,7 +500,8 @@ func (a *MinioSDK) deleteSingle(ctx context.Context, key string) error { func (a *MinioSDK) listObjects(ctx context.Context, prefix string, marker string) (minio.ListBucketResult, error) { ctx, task := gotrace.NewTask(ctx, "MinioSDK.listObjects") defer task.End() - return DoWithRetry( + return DoWithRetryContext( + ctx, "s3 list objects", func() (minio.ListBucketResult, error) { perfcounter.Update(ctx, func(counter *perfcounter.CounterSet) { @@ -579,7 +580,8 @@ func (a *MinioSDK) getObject(ctx context.Context, key string, min *int64, max *i } r, err := newRetryableReader( func(offset int64) (io.ReadCloser, error) { - obj, err := DoWithRetry( + obj, err := DoWithRetryContext( + ctx, "s3 get object", func() (obj *minio.Object, err error) { perfcounter.Update(ctx, func(counter *perfcounter.CounterSet) { @@ -619,7 +621,8 @@ func (a *MinioSDK) getObject(ctx context.Context, key string, min *int64, max *i func (a *MinioSDK) deleteObject(ctx context.Context, key string) (any, error) { ctx, task := gotrace.NewTask(ctx, "MinioSDK.deleteObject") defer task.End() - return DoWithRetry( + return DoWithRetryContext( + ctx, "s3 delete object", func() (any, error) { perfcounter.Update(ctx, func(counter *perfcounter.CounterSet) { @@ -638,7 +641,8 @@ func (a *MinioSDK) deleteObject(ctx context.Context, key string) (any, error) { func (a *MinioSDK) deleteObjects(ctx context.Context, keys ...string) (any, error) { ctx, task := gotrace.NewTask(ctx, "MinioSDK.deleteObjects") defer task.End() - return DoWithRetry( + return DoWithRetryContext( + ctx, "s3 delete objects", func() (any, error) { perfcounter.Update(ctx, func(counter *perfcounter.CounterSet) { From 01ca2334ea536bb1acf4fe25f64c6a2174ef830e Mon Sep 17 00:00:00 2001 From: XuPeng-SH Date: Sun, 26 Jul 2026 22:17:23 +0800 Subject: [PATCH 09/25] ci: validate right-sized coverage runner From dd5849b3f60eb6bb6198383c0535bf316ba2234d Mon Sep 17 00:00:00 2001 From: XuPeng-SH Date: Sun, 26 Jul 2026 23:12:41 +0800 Subject: [PATCH 10/25] ci: rerun coverage runner validation From 84f1cafc557e39c1faf68202f9a416e6d5cd1418 Mon Sep 17 00:00:00 2001 From: XuPeng-SH Date: Sun, 26 Jul 2026 23:55:57 +0800 Subject: [PATCH 11/25] ci: rebalance BVT case groups --- optools/run_bvt_group.sh | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/optools/run_bvt_group.sh b/optools/run_bvt_group.sh index 6b6f7540dc8ff..df5430f72e6fa 100644 --- a/optools/run_bvt_group.sh +++ b/optools/run_bvt_group.sh @@ -1,9 +1,8 @@ #!/usr/bin/env bash -# Run one of the two time-balanced BVT groups. The assignment is based on the -# latest successful full Compose BVT timing: group 0 is 1285s and group 1 is -# 1268s in that baseline. Keep the statement-info producers and verifiers in -# one group: mo-tester runs directories in lexical order, so log, +# Run one of the two time-balanced BVT groups. The latest BVT timing assigns +# about 1501s to group 0 and 1479s to group 1. Keep the statement-info +# producers and verifiers in one group: mo-tester runs directories in lexical order, so log, # result_count, sql_source_type, and statement_query_type run before # zz_statement_query_type. # @@ -32,10 +31,10 @@ while IFS= read -r script_path; do top_level=${relative_path%%/*} case "${top_level}" in - array|auto_increment|benchmark|dataXtest|ddl|disttae|fake_pk|fulltext|function|git4data|hint|join|keyword|load_data|mo_cloud|optimizer|pg_cast|plugin|prepare|procedure|query_result|sample|save_query_result|sequence|snapshot|sql_inject|stage|system|system_variable|temporary|tenant|tenxcloud_xx|time_window|union|util|vector|view) + array|auto_increment|benchmark|dataXtest|ddl|disttae|fake_pk|function|git4data|hint|join|keyword|load_data|mo_cloud|optimizer|pg_cast|plugin|prepare|procedure|query_result|sample|save_query_result|sequence|snapshot|sql_inject|stage|system|system_variable|temporary|tenant|tenxcloud_xx|time_window|union|util|vector|view) assigned_group=0 ;; - analyze|charset_collation|comment|cte|database|distinct|dml|dtype|expression|feature_limit|foreign_key|geo|iceberg|log|metadata|operator|pessimistic_transaction|pitr|plan_cache|publication_subscription|qexec|recursive_cte|replace_statement|result_count|security|set|sql_source_type|statement_query_type|subquery|table|task|udf|window|zz_accesscontrol|zz_statement_query_type) + analyze|charset_collation|comment|cte|database|distinct|dml|dtype|expression|feature_limit|foreign_key|fulltext|geo|iceberg|log|metadata|operator|pessimistic_transaction|pitr|plan_cache|publication_subscription|qexec|recursive_cte|replace_statement|result_count|security|set|sql_source_type|statement_query_type|subquery|table|task|udf|window|zz_accesscontrol|zz_statement_query_type) assigned_group=1 ;; *) From 6f2429ac84d4ed0e02367f575ca04380c17f8b12 Mon Sep 17 00:00:00 2001 From: XuPeng-SH Date: Mon, 27 Jul 2026 00:06:02 +0800 Subject: [PATCH 12/25] ci: validate coverage observability From cc77cd76c99beb9f5980e32f64211ff3d9562ce1 Mon Sep 17 00:00:00 2001 From: XuPeng-SH Date: Mon, 27 Jul 2026 00:33:02 +0800 Subject: [PATCH 13/25] ci: rerun UT timing summary validation From d1b49439eb33cb240d8a39ca92ba881da619c8d5 Mon Sep 17 00:00:00 2001 From: XuPeng-SH Date: Mon, 27 Jul 2026 00:41:47 +0800 Subject: [PATCH 14/25] ci: preserve BVT resources and stable entrypoint refs --- .github/workflows/entrypoint.yaml | 26 +++++--------------------- optools/run_bvt_group.sh | 16 +++++++++++++--- 2 files changed, 18 insertions(+), 24 deletions(-) diff --git a/.github/workflows/entrypoint.yaml b/.github/workflows/entrypoint.yaml index a136b0cf7a961..64deb6f9d0876 100644 --- a/.github/workflows/entrypoint.yaml +++ b/.github/workflows/entrypoint.yaml @@ -23,19 +23,12 @@ jobs: title_for_find_issue: "Which issue(s) this PR fixes:" title_for_find_content: "What this PR does / why we need it:" - # ============ 非 3.0-dev 分支使用 coverage integration ref ============ + # ============ 非 3.0-dev 分支使用 @main ============ matrixone-ci: name: Matrixone CI needs: check-pr-valid if: ${{ needs.check-pr-valid.outputs.pr_valid == 'true' && github.base_ref != '3.0-dev' }} - uses: matrixorigin/CI/.github/workflows/ci.yaml@xp/ci-two-bvt-coverage - secrets: inherit - - matrixone-ut-coverage: - name: Matrixone UT Coverage - needs: check-pr-valid - if: ${{ needs.check-pr-valid.outputs.pr_valid == 'true' && github.base_ref != '3.0-dev' }} - uses: matrixorigin/CI/.github/workflows/coverage-ut.yaml@xp/ci-two-bvt-coverage + uses: matrixorigin/CI/.github/workflows/ci.yaml@main secrets: inherit matrixone-upgrade-ci: @@ -49,30 +42,21 @@ jobs: name: Matrixone Compose CI needs: check-pr-valid if: ${{ needs.check-pr-valid.outputs.pr_valid == 'true' && github.base_ref != '3.0-dev' }} - uses: matrixorigin/CI/.github/workflows/e2e-compose.yaml@xp/ci-two-bvt-coverage + uses: matrixorigin/CI/.github/workflows/e2e-compose.yaml@main secrets: inherit matrixone-standalone-ci: name: Matrixone Standlone CI needs: check-pr-valid if: ${{ needs.check-pr-valid.outputs.pr_valid == 'true' && github.base_ref != '3.0-dev' }} - uses: matrixorigin/CI/.github/workflows/e2e-standalone.yaml@xp/ci-two-bvt-coverage + uses: matrixorigin/CI/.github/workflows/e2e-standalone.yaml@main secrets: inherit matrixone-utils-ci: name: Matrixone Utils CI needs: check-pr-valid if: ${{ needs.check-pr-valid.outputs.pr_valid == 'true' && github.base_ref != '3.0-dev' }} - uses: matrixorigin/CI/.github/workflows/utils.yaml@xp/ci-two-bvt-coverage - secrets: inherit - - matrixone-coverage-merge: - name: Matrixone Coverage Merge - # Race UT intentionally is not a dependency: it produces no coverage - # profile, and must not delay the profile merge. - needs: [check-pr-valid, matrixone-ut-coverage, matrixone-compose-ci, matrixone-standalone-ci] - if: ${{ always() && needs.check-pr-valid.outputs.pr_valid == 'true' && github.base_ref != '3.0-dev' && !github.event.pull_request.draft && needs.matrixone-ut-coverage.result == 'success' && needs.matrixone-ut-coverage.outputs.coverage_ready == 'true' && needs.matrixone-compose-ci.result == 'success' && needs.matrixone-standalone-ci.result == 'success' }} - uses: matrixorigin/CI/.github/workflows/coverage-merge.yaml@xp/ci-two-bvt-coverage + uses: matrixorigin/CI/.github/workflows/utils.yaml@main secrets: inherit # ============ 3.0-dev 分支使用 @release/3.0-dev ============ diff --git a/optools/run_bvt_group.sh b/optools/run_bvt_group.sh index df5430f72e6fa..1984aac8bfb6b 100644 --- a/optools/run_bvt_group.sh +++ b/optools/run_bvt_group.sh @@ -10,14 +10,15 @@ # exercised immediately; rebalance the explicit list from collected timing. set -euo pipefail -if [[ $# -ne 3 ]]; then - echo "Usage: $0 " >&2 +if [[ $# -lt 3 || $# -gt 4 ]]; then + echo "Usage: $0 [resource-dir]" >&2 exit 2 fi tester_dir=$(cd "$1" && pwd) case_root=$(cd "$2" && pwd) group_id=$3 +resource_dir=${4:-} if ! [[ "${group_id}" =~ ^[01]$ ]]; then echo "BVT group must be 0 or 1; got '${group_id}'" >&2 @@ -25,6 +26,12 @@ if ! [[ "${group_id}" =~ ^[01]$ ]]; then fi declare -a included_scripts=() +declare -a resource_args=() +if [[ -n "${resource_dir}" ]]; then + resource_dir=$(cd "${resource_dir}" && pwd) + resource_args=(-s "${resource_dir}") +fi + while IFS= read -r script_path; do relative_path=${script_path#"${case_root}/"} [[ "${relative_path}" == *optimistic* ]] && continue @@ -56,7 +63,10 @@ fi include_list=$(IFS=,; echo "${included_scripts[*]}") echo "Run BVT group ${group_id}: ${#included_scripts[@]} scripts" +if [[ -n "${resource_dir}" ]]; then + echo "BVT resource directory: ${resource_dir}" +fi printf '%s\n' "${included_scripts[@]}" cd "${tester_dir}" -./run.sh -n -g -o -p "${case_root}" -i "${include_list}" +./run.sh -n -g -o -p "${case_root}" -i "${include_list}" "${resource_args[@]}" From 3c05fb865d34aaa4fed169d6816e4f639e0649f4 Mon Sep 17 00:00:00 2001 From: XuPeng-SH Date: Mon, 27 Jul 2026 01:36:25 +0800 Subject: [PATCH 15/25] ci: validate compacted coverage artifacts From 78942a8b2e37b9f8d539a85f15bd8d902388d954 Mon Sep 17 00:00:00 2001 From: XuPeng-SH Date: Mon, 27 Jul 2026 02:04:38 +0800 Subject: [PATCH 16/25] fix: bound object storage cleanup and test deadlines --- pkg/fileservice/file_service_test.go | 43 ++++++++------ pkg/fileservice/object_storage_test.go | 82 ++++++++++++++++---------- pkg/fileservice/qcloud_sdk.go | 31 ++++++++-- pkg/fileservice/qcloud_sdk_test.go | 28 +++++++-- pkg/fileservice/s3_fs_test.go | 4 +- 5 files changed, 131 insertions(+), 57 deletions(-) diff --git a/pkg/fileservice/file_service_test.go b/pkg/fileservice/file_service_test.go index 04c35837c2c04..ae2f816cc108b 100644 --- a/pkg/fileservice/file_service_test.go +++ b/pkg/fileservice/file_service_test.go @@ -45,17 +45,26 @@ func testFileService( policy Policy, newFS func(name string) FileService, ) { + testFileServiceWithContext(t, policy, func(_ context.Context, name string) FileService { + return newFS(name) + }) +} +func testFileServiceWithContext( + t *testing.T, + policy Policy, + newFS func(context.Context, string) FileService, +) { fsName := time.Now().Format("fs-2006-01-02-15-04-05") // Real object-storage specs are part of this shared test suite. Bound the - // whole suite, rather than only one subtest, so a remote outage cannot use - // the package-level Go test timeout. - testCtx, cancel := context.WithTimeout(context.Background(), 3*time.Minute) + // constructor and the whole suite so a remote outage cannot consume the + // package-level Go test timeout. + testCtx, cancel := context.WithTimeout(t.Context(), 3*time.Minute) defer cancel() t.Run("basic", func(t *testing.T) { ctx := testCtx - fs := newFS(fsName) + fs := newFS(ctx, fsName) defer fs.Close(ctx) assert.True(t, strings.Contains(fs.Name(), fsName)) @@ -186,8 +195,8 @@ func testFileService( }) t.Run("WriterForRead", func(t *testing.T) { - fs := newFS(fsName) ctx := testCtx + fs := newFS(ctx, fsName) defer fs.Close(ctx) err := fs.Write(ctx, IOVector{ @@ -239,7 +248,7 @@ func testFileService( t.Run("ReadCloserForRead", func(t *testing.T) { ctx := testCtx - fs := newFS(fsName) + fs := newFS(ctx, fsName) defer fs.Close(ctx) err := fs.Write(ctx, IOVector{ @@ -317,8 +326,8 @@ func testFileService( }) t.Run("random", func(t *testing.T) { - fs := newFS(fsName) ctx := testCtx + fs := newFS(ctx, fsName) defer fs.Close(ctx) for i := 0; i < 8; i++ { @@ -447,8 +456,8 @@ func testFileService( }) t.Run("tree", func(t *testing.T) { - fs := newFS(fsName) ctx := testCtx + fs := newFS(ctx, fsName) defer fs.Close(ctx) for _, dir := range []string{ @@ -578,8 +587,8 @@ func testFileService( }) t.Run("errors", func(t *testing.T) { - fs := newFS(fsName) ctx := testCtx + fs := newFS(ctx, fsName) defer fs.Close(ctx) err := fs.Read(ctx, &IOVector{ @@ -684,7 +693,7 @@ func testFileService( t.Run("cache data", func(t *testing.T) { ctx := testCtx - fs := newFS(fsName) + fs := newFS(ctx, fsName) defer fs.Close(ctx) var counterSet perfcounter.CounterSet ctx = perfcounter.WithCounterSet(ctx, &counterSet) @@ -761,7 +770,7 @@ func testFileService( t.Run("ignore", func(t *testing.T) { ctx := testCtx - fs := newFS(fsName) + fs := newFS(ctx, fsName) defer fs.Close(ctx) data := []byte("foo") @@ -799,7 +808,7 @@ func testFileService( t.Run("named path", func(t *testing.T) { ctx := testCtx - fs := newFS(fsName) + fs := newFS(ctx, fsName) defer fs.Close(ctx) // write @@ -869,7 +878,7 @@ func testFileService( t.Run("issue6110", func(t *testing.T) { ctx := testCtx - fs := newFS(fsName) + fs := newFS(ctx, fsName) defer fs.Close(ctx) err := fs.Write(ctx, IOVector{ @@ -892,7 +901,7 @@ func testFileService( t.Run("streaming write", func(t *testing.T) { ctx := testCtx - fs := newFS(fsName) + fs := newFS(ctx, fsName) defer fs.Close(ctx) reader, writer := io.Pipe() @@ -1005,7 +1014,7 @@ func testFileService( t.Run("context cancel", func(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) cancel() - fs := newFS(fsName) + fs := newFS(testCtx, fsName) defer fs.Close(ctx) err := fs.Write(ctx, IOVector{ @@ -1028,7 +1037,7 @@ func testFileService( t.Run("NewReader and NewWriter", func(t *testing.T) { ctx, cancel := context.WithCancel(t.Context()) defer cancel() - fs := newFS(fsName) + fs := newFS(testCtx, fsName) defer fs.Close(ctx) rwFS, ok := fs.(ReaderWriterFileService) @@ -1052,7 +1061,7 @@ func testFileService( }) t.Run("NewReader and NewWriter error", func(t *testing.T) { - fs := newFS(fsName) + fs := newFS(testCtx, fsName) defer fs.Close(t.Context()) rwFS, ok := fs.(ReaderWriterFileService) diff --git a/pkg/fileservice/object_storage_test.go b/pkg/fileservice/object_storage_test.go index 01a9eac3140b8..f0df570162558 100644 --- a/pkg/fileservice/object_storage_test.go +++ b/pkg/fileservice/object_storage_test.go @@ -32,12 +32,32 @@ func testObjectStorage[T ObjectStorage]( sdkName string, newStorage func(t *testing.T) T, ) { + testObjectStorageWithContext(t, sdkName, func(t *testing.T, _ context.Context) T { + return newStorage(t) + }) +} + +func testObjectStorageWithContext[T ObjectStorage]( + t *testing.T, + sdkName string, + newStorage func(*testing.T, context.Context) T, +) { + testObjectStorageWithParentContext(t, sdkName, t.Context(), newStorage) +} +func testObjectStorageWithParentContext[T ObjectStorage]( + t *testing.T, + sdkName string, + parentCtx context.Context, + newStorage func(*testing.T, context.Context) T, +) { t.Run(sdkName, func(t *testing.T) { + testCtx, cancel := context.WithTimeout(parentCtx, 3*time.Minute) + defer cancel() t.Run("basic", func(t *testing.T) { - storage := newStorage(t) - ctx := context.Background() + storage := newStorage(t, testCtx) + ctx := testCtx prefix := time.Now().Format("2006-01-02-15-04-05.000000") name := path.Join(prefix, "foo") @@ -159,8 +179,8 @@ func testObjectStorage[T ObjectStorage]( }) t.Run("invalid write length", func(t *testing.T) { - storage := newStorage(t) - ctx := context.Background() + storage := newStorage(t, testCtx) + ctx := testCtx prefix := time.Now().Format("2006-01-02-15-04-05.000000") name := path.Join(prefix, "foo") @@ -183,8 +203,8 @@ func testObjectStorage[T ObjectStorage]( }) t.Run("write empty", func(t *testing.T) { - storage := newStorage(t) - ctx := context.Background() + storage := newStorage(t, testCtx) + ctx := testCtx prefix := time.Now().Format("2006-01-02-15-04-05.000000") name := path.Join(prefix, "foo") @@ -207,13 +227,15 @@ func TestObjectStorages(t *testing.T) { for _, args := range objectStorageArgumentsForTest("test", t) { t.Run(args.Name, func(t *testing.T) { + specCtx, cancel := context.WithTimeout(t.Context(), 3*time.Minute) + defer cancel() switch { case strings.HasPrefix(strings.ToLower(args.Endpoint), "hdfs"): // HDFS - testObjectStorage(t, "hdfs", func(t *testing.T) *HDFS { - storage, err := NewHDFS(context.Background(), args, nil) + testObjectStorageWithParentContext(t, "hdfs", specCtx, func(t *testing.T, ctx context.Context) *HDFS { + storage, err := NewHDFS(ctx, args, nil) if err != nil { t.Fatal(err) } @@ -222,8 +244,8 @@ func TestObjectStorages(t *testing.T) { case args.Endpoint == "disk": // disk - testObjectStorage(t, "disk", func(t *testing.T) *diskObjectStorage { - storage, err := newDiskObjectStorage(context.Background(), args, nil) + testObjectStorageWithParentContext(t, "disk", specCtx, func(t *testing.T, ctx context.Context) *diskObjectStorage { + storage, err := newDiskObjectStorage(ctx, args, nil) if err != nil { t.Fatal(err) } @@ -232,23 +254,23 @@ func TestObjectStorages(t *testing.T) { case strings.Contains(args.Endpoint, "aliyun"): // aliyun - testObjectStorage(t, "aliyun", func(t *testing.T) *AliyunSDK { - storage, err := NewAliyunSDK(context.Background(), args, nil) + testObjectStorageWithParentContext(t, "aliyun", specCtx, func(t *testing.T, ctx context.Context) *AliyunSDK { + storage, err := NewAliyunSDK(ctx, args, nil) if err != nil { t.Fatal(err) } return storage }) if args.RoleARN == "" { - testObjectStorage(t, "minio", func(t *testing.T) *MinioSDK { - storage, err := NewMinioSDK(context.Background(), args, nil) + testObjectStorageWithParentContext(t, "minio", specCtx, func(t *testing.T, ctx context.Context) *MinioSDK { + storage, err := NewMinioSDK(ctx, args, nil) if err != nil { t.Fatal(err) } return storage }) - testObjectStorage(t, "aws", func(t *testing.T) *AwsSDKv2 { - storage, err := NewAwsSDKv2(context.Background(), args, nil) + testObjectStorageWithParentContext(t, "aws", specCtx, func(t *testing.T, ctx context.Context) *AwsSDKv2 { + storage, err := NewAwsSDKv2(ctx, args, nil) if err != nil { t.Fatal(err) } @@ -258,22 +280,22 @@ func TestObjectStorages(t *testing.T) { case strings.Contains(args.Endpoint, "qcloud"): // qcloud - testObjectStorage(t, "aws", func(t *testing.T) *AwsSDKv2 { - storage, err := NewAwsSDKv2(context.Background(), args, nil) + testObjectStorageWithParentContext(t, "aws", specCtx, func(t *testing.T, ctx context.Context) *AwsSDKv2 { + storage, err := NewAwsSDKv2(ctx, args, nil) if err != nil { t.Fatal(err) } return storage }) - testObjectStorage(t, "minio", func(t *testing.T) *MinioSDK { - storage, err := NewMinioSDK(context.Background(), args, nil) + testObjectStorageWithParentContext(t, "minio", specCtx, func(t *testing.T, ctx context.Context) *MinioSDK { + storage, err := NewMinioSDK(ctx, args, nil) if err != nil { t.Fatal(err) } return storage }) - testObjectStorage(t, "qcloud", func(t *testing.T) *QCloudSDK { - storage, err := NewQCloudSDK(context.Background(), args, nil) + testObjectStorageWithParentContext(t, "qcloud", specCtx, func(t *testing.T, ctx context.Context) *QCloudSDK { + storage, err := NewQCloudSDK(ctx, args, nil) if err != nil { t.Fatal(err) } @@ -282,15 +304,15 @@ func TestObjectStorages(t *testing.T) { case strings.Contains(args.Endpoint, "aws"): // AWS - testObjectStorage(t, "aws", func(t *testing.T) *AwsSDKv2 { - storage, err := NewAwsSDKv2(context.Background(), args, nil) + testObjectStorageWithParentContext(t, "aws", specCtx, func(t *testing.T, ctx context.Context) *AwsSDKv2 { + storage, err := NewAwsSDKv2(ctx, args, nil) if err != nil { t.Fatal(err) } return storage }) - testObjectStorage(t, "minio", func(t *testing.T) *MinioSDK { - storage, err := NewMinioSDK(context.Background(), args, nil) + testObjectStorageWithParentContext(t, "minio", specCtx, func(t *testing.T, ctx context.Context) *MinioSDK { + storage, err := NewMinioSDK(ctx, args, nil) if err != nil { t.Fatal(err) } @@ -299,15 +321,15 @@ func TestObjectStorages(t *testing.T) { case strings.Contains(args.Endpoint, "qiniu"): // qiniu - testObjectStorage(t, "minio", func(t *testing.T) *MinioSDK { - storage, err := NewMinioSDK(context.Background(), args, nil) + testObjectStorageWithParentContext(t, "minio", specCtx, func(t *testing.T, ctx context.Context) *MinioSDK { + storage, err := NewMinioSDK(ctx, args, nil) if err != nil { t.Fatal(err) } return storage }) - testObjectStorage(t, "aws", func(t *testing.T) *AwsSDKv2 { - storage, err := NewAwsSDKv2(context.Background(), args, nil) + testObjectStorageWithParentContext(t, "aws", specCtx, func(t *testing.T, ctx context.Context) *AwsSDKv2 { + storage, err := NewAwsSDKv2(ctx, args, nil) if err != nil { t.Fatal(err) } diff --git a/pkg/fileservice/qcloud_sdk.go b/pkg/fileservice/qcloud_sdk.go index 74a313eb2efc1..226a564f25aba 100644 --- a/pkg/fileservice/qcloud_sdk.go +++ b/pkg/fileservice/qcloud_sdk.go @@ -49,6 +49,8 @@ type QCloudSDK struct { listMaxKeys int } +const qcloudMultipartAbortTimeout = 30 * time.Second + func NewQCloudSDK( ctx context.Context, args ObjectStorageArguments, @@ -439,10 +441,20 @@ func (a *QCloudSDK) WriteMultipartParallel( defer func() { if err != nil { - _, _ = DoWithRetryContext(context.WithoutCancel(parentCtx), "cos abort multipart upload", func() (*cos.Response, error) { - return a.client.Object.AbortMultipartUpload( - context.WithoutCancel(parentCtx), key, output.UploadID) - }, maxRetryAttemps, IsRetryableError) + // The upload context is normally canceled on the first part + // failure, but abort still needs a live context to remove the + // server-side multipart upload. Bound that detached cleanup so a + // broken COS endpoint cannot delay the original Write forever. + abortCtx, abortCancel := context.WithTimeoutCause( + context.WithoutCancel(parentCtx), + qcloudMultipartAbortTimeout, + context.DeadlineExceeded, + ) + defer abortCancel() + if abortErr := a.abortMultipartUpload(abortCtx, key, output.UploadID); abortErr != nil { + logutil.Warn("failed to abort cos multipart upload", + zap.Error(abortErr)) + } } }() @@ -595,6 +607,17 @@ func (a *QCloudSDK) WriteMultipartParallel( return nil } +func (a *QCloudSDK) abortMultipartUpload( + ctx context.Context, + key string, + uploadID string, +) error { + _, err := DoWithRetryContext(ctx, "cos abort multipart upload", func() (*cos.Response, error) { + return a.client.Object.AbortMultipartUpload(ctx, key, uploadID) + }, maxRetryAttemps, IsRetryableError) + return err +} + func (a *QCloudSDK) Read( ctx context.Context, key string, diff --git a/pkg/fileservice/qcloud_sdk_test.go b/pkg/fileservice/qcloud_sdk_test.go index 792cb6d291562..618b8c3c1879b 100644 --- a/pkg/fileservice/qcloud_sdk_test.go +++ b/pkg/fileservice/qcloud_sdk_test.go @@ -25,6 +25,7 @@ import ( "net/http/httptest" "net/url" "strings" + "sync/atomic" "testing" "time" @@ -43,10 +44,10 @@ func TestQCloudSDK(t *testing.T) { t.Run(fmt.Sprintf("%s %s", args.Name, args.Bucket), func(t *testing.T) { - testObjectStorage(t, "qcloud", func(t *testing.T) *QCloudSDK { + testObjectStorageWithContext(t, "qcloud", func(t *testing.T, ctx context.Context) *QCloudSDK { args.KeyPrefix = fmt.Sprintf("%v", rand.Int64()) ret, err := NewQCloudSDK( - context.Background(), + ctx, args, nil, ) @@ -69,11 +70,11 @@ func TestQCloudSDK(t *testing.T) { t.Run(fmt.Sprintf("%s %s", args.Name, args.Bucket), func(t *testing.T) { t.Run("file service", func(t *testing.T) { - testFileService(t, 0, func(name string) FileService { + testFileServiceWithContext(t, 0, func(ctx context.Context, name string) FileService { args.Name = name args.KeyPrefix = fmt.Sprintf("%v", rand.Int64()) ret, err := NewS3FS( - context.Background(), + ctx, args, DisabledCacheConfig, nil, @@ -418,6 +419,25 @@ func TestQCloudSDKListStopsRetryingAtContextDeadline(t *testing.T) { t.Fatal("expected list to return a deadline error") } +func TestQCloudSDKAbortMultipartStopsAtContextDeadline(t *testing.T) { + var requests atomic.Int64 + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + requests.Add(1) + w.WriteHeader(http.StatusBadGateway) + })) + defer server.Close() + + sdk := newTestCOSClient(t, server) + ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond) + defer cancel() + + start := time.Now() + err := sdk.abortMultipartUpload(ctx, "object", "upload-id") + require.ErrorIs(t, err, context.DeadlineExceeded) + require.Less(t, time.Since(start), time.Second) + require.Positive(t, requests.Load()) +} + type timeoutError struct{} func (timeoutError) Error() string { diff --git a/pkg/fileservice/s3_fs_test.go b/pkg/fileservice/s3_fs_test.go index d0325519ea730..36d45422369d4 100644 --- a/pkg/fileservice/s3_fs_test.go +++ b/pkg/fileservice/s3_fs_test.go @@ -1753,12 +1753,12 @@ func TestS3FSFromSpecs(t *testing.T) { t.Run(args.Name, func(t *testing.T) { - testFileService(t, 0, func(name string) FileService { + testFileServiceWithContext(t, 0, func(ctx context.Context, name string) FileService { args.Name = name args.KeyPrefix = time.Now().Format("2006-01-02.15:04:05.000000") fs, err := NewS3FS( - context.Background(), + ctx, args, DisabledCacheConfig, nil, From f6bf6745b03930def1473ad9ba0bd895d244dc29 Mon Sep 17 00:00:00 2001 From: XuPeng-SH Date: Mon, 27 Jul 2026 02:04:38 +0800 Subject: [PATCH 17/25] ci: complete parallel coverage rollout --- .github/workflows/coverage-integration.yaml | 21 ++++++++++++--------- .github/workflows/entrypoint.yaml | 16 ++++++++++++++++ 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/.github/workflows/coverage-integration.yaml b/.github/workflows/coverage-integration.yaml index 82fb391d38b9c..d95344ad792d9 100644 --- a/.github/workflows/coverage-integration.yaml +++ b/.github/workflows/coverage-integration.yaml @@ -10,37 +10,40 @@ concurrency: cancel-in-progress: true # pull_request_target executes the base branch workflow by design, so it cannot -# validate reusable-workflow changes made in this PR. This short-lived harness -# runs only for this trusted same-repository integration branch and disappears -# from normal PR execution after the branch is merged or deleted. +# validate reusable-workflow changes made in this PR. This validation-only +# harness runs only for this trusted same-repository branch and pins the exact +# reviewed CI commit. Remove it when the production entrypoint is switched to +# the merged reusable workflows. jobs: matrixone-ci: if: ${{ github.event.pull_request.head.repo.full_name == github.repository && github.event.pull_request.head.ref == 'xp/ci-race-coverage-artifacts' }} - uses: matrixorigin/CI/.github/workflows/ci.yaml@xp/ci-two-bvt-coverage + uses: matrixorigin/CI/.github/workflows/ci.yaml@6beb8d8bb8a93d62f6ff02591c3eafe287d536ce secrets: inherit matrixone-ut-coverage: if: ${{ github.event.pull_request.head.repo.full_name == github.repository && github.event.pull_request.head.ref == 'xp/ci-race-coverage-artifacts' }} - uses: matrixorigin/CI/.github/workflows/coverage-ut.yaml@xp/ci-two-bvt-coverage + uses: matrixorigin/CI/.github/workflows/coverage-ut.yaml@6beb8d8bb8a93d62f6ff02591c3eafe287d536ce secrets: inherit matrixone-compose-ci: if: ${{ github.event.pull_request.head.repo.full_name == github.repository && github.event.pull_request.head.ref == 'xp/ci-race-coverage-artifacts' }} - uses: matrixorigin/CI/.github/workflows/e2e-compose.yaml@xp/ci-two-bvt-coverage + uses: matrixorigin/CI/.github/workflows/e2e-compose.yaml@6beb8d8bb8a93d62f6ff02591c3eafe287d536ce secrets: inherit matrixone-standalone-ci: if: ${{ github.event.pull_request.head.repo.full_name == github.repository && github.event.pull_request.head.ref == 'xp/ci-race-coverage-artifacts' }} - uses: matrixorigin/CI/.github/workflows/e2e-standalone.yaml@xp/ci-two-bvt-coverage + uses: matrixorigin/CI/.github/workflows/e2e-standalone.yaml@6beb8d8bb8a93d62f6ff02591c3eafe287d536ce secrets: inherit matrixone-utils-ci: if: ${{ github.event.pull_request.head.repo.full_name == github.repository && github.event.pull_request.head.ref == 'xp/ci-race-coverage-artifacts' }} - uses: matrixorigin/CI/.github/workflows/utils.yaml@xp/ci-two-bvt-coverage + uses: matrixorigin/CI/.github/workflows/utils.yaml@6beb8d8bb8a93d62f6ff02591c3eafe287d536ce + with: + enable_legacy_coverage: false secrets: inherit matrixone-coverage-merge: needs: [matrixone-ut-coverage, matrixone-compose-ci, matrixone-standalone-ci] if: ${{ always() && github.event.pull_request.head.repo.full_name == github.repository && github.event.pull_request.head.ref == 'xp/ci-race-coverage-artifacts' && !github.event.pull_request.draft && needs.matrixone-ut-coverage.result == 'success' && needs.matrixone-ut-coverage.outputs.coverage_ready == 'true' && needs.matrixone-compose-ci.result == 'success' && needs.matrixone-standalone-ci.result == 'success' }} - uses: matrixorigin/CI/.github/workflows/coverage-merge.yaml@xp/ci-two-bvt-coverage + uses: matrixorigin/CI/.github/workflows/coverage-merge.yaml@6beb8d8bb8a93d62f6ff02591c3eafe287d536ce secrets: inherit diff --git a/.github/workflows/entrypoint.yaml b/.github/workflows/entrypoint.yaml index 64deb6f9d0876..e96907c8a2472 100644 --- a/.github/workflows/entrypoint.yaml +++ b/.github/workflows/entrypoint.yaml @@ -31,6 +31,13 @@ jobs: uses: matrixorigin/CI/.github/workflows/ci.yaml@main secrets: inherit + matrixone-ut-coverage: + name: Matrixone UT Coverage + needs: check-pr-valid + if: ${{ needs.check-pr-valid.outputs.pr_valid == 'true' && github.base_ref != '3.0-dev' && !github.event.pull_request.draft }} + uses: matrixorigin/CI/.github/workflows/coverage-ut.yaml@main + secrets: inherit + matrixone-upgrade-ci: name: Matrixone Upgrade CI needs: check-pr-valid @@ -57,6 +64,15 @@ jobs: needs: check-pr-valid if: ${{ needs.check-pr-valid.outputs.pr_valid == 'true' && github.base_ref != '3.0-dev' }} uses: matrixorigin/CI/.github/workflows/utils.yaml@main + with: + enable_legacy_coverage: false + secrets: inherit + + matrixone-coverage-merge: + name: Matrixone Coverage Merge + needs: [check-pr-valid, matrixone-ut-coverage, matrixone-compose-ci, matrixone-standalone-ci] + if: ${{ always() && needs.check-pr-valid.outputs.pr_valid == 'true' && github.base_ref != '3.0-dev' && !github.event.pull_request.draft && needs.matrixone-ut-coverage.result == 'success' && needs.matrixone-ut-coverage.outputs.coverage_ready == 'true' && needs.matrixone-compose-ci.result == 'success' && needs.matrixone-standalone-ci.result == 'success' }} + uses: matrixorigin/CI/.github/workflows/coverage-merge.yaml@main secrets: inherit # ============ 3.0-dev 分支使用 @release/3.0-dev ============ From cce04020003a6661f30e3260d5d5e526667a8db8 Mon Sep 17 00:00:00 2001 From: XuPeng-SH Date: Mon, 27 Jul 2026 06:34:42 +0800 Subject: [PATCH 18/25] ci: remove coverage validation harness --- .github/workflows/coverage-integration.yaml | 49 --------------------- 1 file changed, 49 deletions(-) delete mode 100644 .github/workflows/coverage-integration.yaml diff --git a/.github/workflows/coverage-integration.yaml b/.github/workflows/coverage-integration.yaml deleted file mode 100644 index d95344ad792d9..0000000000000 --- a/.github/workflows/coverage-integration.yaml +++ /dev/null @@ -1,49 +0,0 @@ -name: MatrixOne Coverage Integration - -on: - pull_request: - types: [opened, synchronize, reopened] - branches: [main] - -concurrency: - group: ${{ github.event.pull_request.head.repo.full_name }}/${{ github.event.pull_request.head.ref }}/${{ github.workflow }} - cancel-in-progress: true - -# pull_request_target executes the base branch workflow by design, so it cannot -# validate reusable-workflow changes made in this PR. This validation-only -# harness runs only for this trusted same-repository branch and pins the exact -# reviewed CI commit. Remove it when the production entrypoint is switched to -# the merged reusable workflows. -jobs: - matrixone-ci: - if: ${{ github.event.pull_request.head.repo.full_name == github.repository && github.event.pull_request.head.ref == 'xp/ci-race-coverage-artifacts' }} - uses: matrixorigin/CI/.github/workflows/ci.yaml@6beb8d8bb8a93d62f6ff02591c3eafe287d536ce - secrets: inherit - - matrixone-ut-coverage: - if: ${{ github.event.pull_request.head.repo.full_name == github.repository && github.event.pull_request.head.ref == 'xp/ci-race-coverage-artifacts' }} - uses: matrixorigin/CI/.github/workflows/coverage-ut.yaml@6beb8d8bb8a93d62f6ff02591c3eafe287d536ce - secrets: inherit - - matrixone-compose-ci: - if: ${{ github.event.pull_request.head.repo.full_name == github.repository && github.event.pull_request.head.ref == 'xp/ci-race-coverage-artifacts' }} - uses: matrixorigin/CI/.github/workflows/e2e-compose.yaml@6beb8d8bb8a93d62f6ff02591c3eafe287d536ce - secrets: inherit - - matrixone-standalone-ci: - if: ${{ github.event.pull_request.head.repo.full_name == github.repository && github.event.pull_request.head.ref == 'xp/ci-race-coverage-artifacts' }} - uses: matrixorigin/CI/.github/workflows/e2e-standalone.yaml@6beb8d8bb8a93d62f6ff02591c3eafe287d536ce - secrets: inherit - - matrixone-utils-ci: - if: ${{ github.event.pull_request.head.repo.full_name == github.repository && github.event.pull_request.head.ref == 'xp/ci-race-coverage-artifacts' }} - uses: matrixorigin/CI/.github/workflows/utils.yaml@6beb8d8bb8a93d62f6ff02591c3eafe287d536ce - with: - enable_legacy_coverage: false - secrets: inherit - - matrixone-coverage-merge: - needs: [matrixone-ut-coverage, matrixone-compose-ci, matrixone-standalone-ci] - if: ${{ always() && github.event.pull_request.head.repo.full_name == github.repository && github.event.pull_request.head.ref == 'xp/ci-race-coverage-artifacts' && !github.event.pull_request.draft && needs.matrixone-ut-coverage.result == 'success' && needs.matrixone-ut-coverage.outputs.coverage_ready == 'true' && needs.matrixone-compose-ci.result == 'success' && needs.matrixone-standalone-ci.result == 'success' }} - uses: matrixorigin/CI/.github/workflows/coverage-merge.yaml@6beb8d8bb8a93d62f6ff02591c3eafe287d536ce - secrets: inherit From 8834b71f0821419bca478f8e70949fe15f5e8b05 Mon Sep 17 00:00:00 2001 From: XuPeng-SH Date: Mon, 27 Jul 2026 07:29:20 +0800 Subject: [PATCH 19/25] ci: make parallel coverage rerun-safe --- .github/workflows/entrypoint.yaml | 58 +++++++++++++++++++++---- .github/workflows/iceberg-connector.yml | 2 + etc/launch-tae-compose/compose.yaml | 10 ++--- 3 files changed, 56 insertions(+), 14 deletions(-) diff --git a/.github/workflows/entrypoint.yaml b/.github/workflows/entrypoint.yaml index e96907c8a2472..c340aed950507 100644 --- a/.github/workflows/entrypoint.yaml +++ b/.github/workflows/entrypoint.yaml @@ -2,8 +2,8 @@ name: MatrixOne ALL CI on: pull_request_target: - types: [ opened, synchronize, reopened ] - branches: [ main,'[0-9]+.[0-9]+*' ] + types: [ opened, synchronize, reopened, ready_for_review ] + branches: [ main, 4.2-dev, '[0-9]+.[0-9]+*' ] concurrency: group: ${{ github.event.pull_request.head.repo.full_name}}/${{ github.event.pull_request.head.ref }}/${{ github.workflow }} @@ -23,6 +23,35 @@ jobs: title_for_find_issue: "Which issue(s) this PR fixes:" title_for_find_content: "What this PR does / why we need it:" + bvt-group-plan: + name: Plan complementary BVT groups + needs: check-pr-valid + if: ${{ needs.check-pr-valid.outputs.pr_valid == 'true' && github.base_ref != '3.0-dev' }} + runs-on: ubuntu-latest + outputs: + compose_group: ${{ steps.plan.outputs.compose_group }} + launch_group: ${{ steps.plan.outputs.launch_group }} + generation: ${{ steps.plan.outputs.generation }} + steps: + - id: plan + name: Assign both BVT groups + run: | + set -euo pipefail + compose_group=$(( (${{ github.run_id }} + ${{ github.run_attempt }}) % 2 )) + launch_group=$((1 - compose_group)) + generation='${{ github.run_id }}-${{ github.run_attempt }}' + { + echo "compose_group=${compose_group}" + echo "launch_group=${launch_group}" + echo "generation=${generation}" + } >> "$GITHUB_OUTPUT" + { + echo '### BVT group plan' + echo "- Compose + Proxy: group ${compose_group}" + echo "- Launch + Pessimistic: group ${launch_group}" + echo "- Generation: ${generation}" + } >> "$GITHUB_STEP_SUMMARY" + # ============ 非 3.0-dev 分支使用 @main ============ matrixone-ci: name: Matrixone CI @@ -33,9 +62,11 @@ jobs: matrixone-ut-coverage: name: Matrixone UT Coverage - needs: check-pr-valid - if: ${{ needs.check-pr-valid.outputs.pr_valid == 'true' && github.base_ref != '3.0-dev' && !github.event.pull_request.draft }} + needs: [check-pr-valid, bvt-group-plan] + if: ${{ needs.check-pr-valid.outputs.pr_valid == 'true' && github.base_ref != '3.0-dev' }} uses: matrixorigin/CI/.github/workflows/coverage-ut.yaml@main + with: + bvt_generation: ${{ needs.bvt-group-plan.outputs.generation }} secrets: inherit matrixone-upgrade-ci: @@ -47,16 +78,22 @@ jobs: matrixone-compose-ci: name: Matrixone Compose CI - needs: check-pr-valid + needs: [check-pr-valid, bvt-group-plan] if: ${{ needs.check-pr-valid.outputs.pr_valid == 'true' && github.base_ref != '3.0-dev' }} uses: matrixorigin/CI/.github/workflows/e2e-compose.yaml@main + with: + bvt_group: ${{ needs.bvt-group-plan.outputs.compose_group }} + bvt_generation: ${{ needs.bvt-group-plan.outputs.generation }} secrets: inherit matrixone-standalone-ci: name: Matrixone Standlone CI - needs: check-pr-valid + needs: [check-pr-valid, bvt-group-plan] if: ${{ needs.check-pr-valid.outputs.pr_valid == 'true' && github.base_ref != '3.0-dev' }} uses: matrixorigin/CI/.github/workflows/e2e-standalone.yaml@main + with: + bvt_group: ${{ needs.bvt-group-plan.outputs.launch_group }} + bvt_generation: ${{ needs.bvt-group-plan.outputs.generation }} secrets: inherit matrixone-utils-ci: @@ -69,10 +106,13 @@ jobs: secrets: inherit matrixone-coverage-merge: - name: Matrixone Coverage Merge - needs: [check-pr-valid, matrixone-ut-coverage, matrixone-compose-ci, matrixone-standalone-ci] - if: ${{ always() && needs.check-pr-valid.outputs.pr_valid == 'true' && github.base_ref != '3.0-dev' && !github.event.pull_request.draft && needs.matrixone-ut-coverage.result == 'success' && needs.matrixone-ut-coverage.outputs.coverage_ready == 'true' && needs.matrixone-compose-ci.result == 'success' && needs.matrixone-standalone-ci.result == 'success' }} + name: Matrixone Utils CI + needs: [check-pr-valid, bvt-group-plan, matrixone-ut-coverage, matrixone-compose-ci, matrixone-standalone-ci] + if: ${{ always() && needs.check-pr-valid.outputs.pr_valid == 'true' && github.base_ref != '3.0-dev' }} uses: matrixorigin/CI/.github/workflows/coverage-merge.yaml@main + with: + prerequisites_ready: ${{ needs.bvt-group-plan.result == 'success' && needs.matrixone-ut-coverage.result == 'success' && needs.matrixone-ut-coverage.outputs.coverage_ready == 'true' && needs.matrixone-compose-ci.result == 'success' && needs.matrixone-standalone-ci.result == 'success' }} + expected_bvt_generation: ${{ needs.bvt-group-plan.outputs.generation }} secrets: inherit # ============ 3.0-dev 分支使用 @release/3.0-dev ============ diff --git a/.github/workflows/iceberg-connector.yml b/.github/workflows/iceberg-connector.yml index 9e16921c76b3f..03c49da8ae4b9 100644 --- a/.github/workflows/iceberg-connector.yml +++ b/.github/workflows/iceberg-connector.yml @@ -2,7 +2,9 @@ name: Iceberg Connector E2E on: pull_request: + types: [opened, synchronize, reopened, ready_for_review] paths: + - '.github/workflows/entrypoint.yaml' - '.github/workflows/iceberg-connector.yml' - 'Makefile' - 'optools/iceberg_ci.bash' diff --git a/etc/launch-tae-compose/compose.yaml b/etc/launch-tae-compose/compose.yaml index 135f5095d295c..fdd6188201bbd 100644 --- a/etc/launch-tae-compose/compose.yaml +++ b/etc/launch-tae-compose/compose.yaml @@ -27,7 +27,7 @@ services: <<: *mo-common # exec makes mo-service PID 1, so docker compose down delivers SIGTERM to # the instrumented process and lets it flush GOCOVERDIR before shutdown. - entrypoint: ["/bin/bash","-c","set -euo pipefail; exec /mo-service -debug-http :12345 -cfg /config/cn-0.toml >> /log/cn-0.log 2>&1"] + entrypoint: ["/bin/bash","-c","set -euo pipefail; exec /mo-service -debug-http :12345 -cfg /config/cn-0.toml > >(tee -a /log/cn-0.log) 2>&1"] profiles: - launch - launch-multi-cn @@ -49,7 +49,7 @@ services: cn-1: container_name: cn-1 <<: *mo-common - entrypoint: ["/bin/bash","-c","set -euo pipefail; exec /mo-service -debug-http :12345 -cfg /config/cn-1.toml >> /log/cn-1.log 2>&1"] + entrypoint: ["/bin/bash","-c","set -euo pipefail; exec /mo-service -debug-http :12345 -cfg /config/cn-1.toml > >(tee -a /log/cn-1.log) 2>&1"] depends_on: - tn ports: @@ -71,7 +71,7 @@ services: proxy: container_name: proxy <<: *mo-common - entrypoint: ["/bin/bash","-c","set -euo pipefail; exec /mo-service -debug-http :12345 -cfg /config/proxy.toml >> /log/proxy.log 2>&1"] + entrypoint: ["/bin/bash","-c","set -euo pipefail; exec /mo-service -debug-http :12345 -cfg /config/proxy.toml > >(tee -a /log/proxy.log) 2>&1"] profiles: - launch - launch-multi-cn @@ -95,7 +95,7 @@ services: tn: container_name: tn <<: *mo-common - entrypoint: ["/bin/bash","-c","set -euo pipefail; exec /mo-service -debug-http :12345 -cfg /config/tn.toml >> /log/tn.log 2>&1"] + entrypoint: ["/bin/bash","-c","set -euo pipefail; exec /mo-service -debug-http :12345 -cfg /config/tn.toml > >(tee -a /log/tn.log) 2>&1"] profiles: - launch - launch-multi-cn @@ -120,7 +120,7 @@ services: profiles: - launch - launch-multi-cn - entrypoint: ["/bin/bash","-c","set -euo pipefail; exec /mo-service -debug-http :12345 -cfg /config/log.toml >> /log/logservice.log 2>&1"] + entrypoint: ["/bin/bash","-c","set -euo pipefail; exec /mo-service -debug-http :12345 -cfg /config/log.toml > >(tee -a /log/logservice.log) 2>&1"] depends_on: - createbuckets ports: From 8964daa175d7700a60c088198c05127f036ebfc9 Mon Sep 17 00:00:00 2001 From: XuPeng-SH Date: Mon, 27 Jul 2026 07:30:43 +0800 Subject: [PATCH 20/25] ci: validate parallel coverage integration --- .github/workflows/coverage-integration.yaml | 77 +++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 .github/workflows/coverage-integration.yaml diff --git a/.github/workflows/coverage-integration.yaml b/.github/workflows/coverage-integration.yaml new file mode 100644 index 0000000000000..fb53b86f7284e --- /dev/null +++ b/.github/workflows/coverage-integration.yaml @@ -0,0 +1,77 @@ +name: MatrixOne Coverage Integration + +on: + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + branches: [main] + +concurrency: + group: ${{ github.event.pull_request.head.repo.full_name }}/${{ github.event.pull_request.head.ref }}/${{ github.workflow }} + cancel-in-progress: true + +# pull_request_target intentionally loads the workflow from the base branch. +# This short-lived, same-repository harness validates both PR branches together +# and is removed after the integration run. +jobs: + bvt-group-plan: + if: ${{ github.event.pull_request.head.repo.full_name == github.repository && github.event.pull_request.head.ref == 'xp/ci-race-coverage-artifacts' }} + runs-on: ubuntu-latest + outputs: + compose_group: ${{ steps.plan.outputs.compose_group }} + launch_group: ${{ steps.plan.outputs.launch_group }} + generation: ${{ steps.plan.outputs.generation }} + steps: + - id: plan + run: | + set -euo pipefail + compose_group=$(( (${{ github.run_id }} + ${{ github.run_attempt }}) % 2 )) + { + echo "compose_group=${compose_group}" + echo "launch_group=$((1 - compose_group))" + echo "generation=${{ github.run_id }}-${{ github.run_attempt }}" + } >> "$GITHUB_OUTPUT" + + matrixone-ut-coverage: + name: Matrixone UT Coverage + needs: bvt-group-plan + uses: matrixorigin/CI/.github/workflows/coverage-ut.yaml@xp/ci-two-bvt-coverage + with: + bvt_generation: ${{ needs.bvt-group-plan.outputs.generation }} + secrets: inherit + + matrixone-compose-ci: + name: Matrixone Compose CI + needs: bvt-group-plan + uses: matrixorigin/CI/.github/workflows/e2e-compose.yaml@xp/ci-two-bvt-coverage + with: + bvt_group: ${{ needs.bvt-group-plan.outputs.compose_group }} + bvt_generation: ${{ needs.bvt-group-plan.outputs.generation }} + secrets: inherit + + matrixone-standalone-ci: + name: Matrixone Standlone CI + needs: bvt-group-plan + uses: matrixorigin/CI/.github/workflows/e2e-standalone.yaml@xp/ci-two-bvt-coverage + with: + bvt_group: ${{ needs.bvt-group-plan.outputs.launch_group }} + bvt_generation: ${{ needs.bvt-group-plan.outputs.generation }} + secrets: inherit + + matrixone-utils-ci: + name: Matrixone Utils CI + needs: bvt-group-plan + uses: matrixorigin/CI/.github/workflows/utils.yaml@xp/ci-two-bvt-coverage + with: + enable_legacy_coverage: false + secrets: inherit + + matrixone-coverage-merge: + name: Matrixone Utils CI + needs: [bvt-group-plan, matrixone-ut-coverage, matrixone-compose-ci, matrixone-standalone-ci] + if: ${{ always() }} + uses: matrixorigin/CI/.github/workflows/coverage-merge.yaml@xp/ci-two-bvt-coverage + with: + prerequisites_ready: ${{ needs.bvt-group-plan.result == 'success' && needs.matrixone-ut-coverage.result == 'success' && needs.matrixone-ut-coverage.outputs.coverage_ready == 'true' && needs.matrixone-compose-ci.result == 'success' && needs.matrixone-standalone-ci.result == 'success' }} + expected_bvt_generation: ${{ needs.bvt-group-plan.outputs.generation }} + ci_ref: xp/ci-two-bvt-coverage + secrets: inherit From 61e04902af55cc7b1b5bd5cca755d3095c9d9839 Mon Sep 17 00:00:00 2001 From: XuPeng-SH Date: Mon, 27 Jul 2026 07:38:32 +0800 Subject: [PATCH 21/25] ci: opt main into parallel BVT --- .github/workflows/coverage-integration.yaml | 2 ++ .github/workflows/entrypoint.yaml | 2 ++ 2 files changed, 4 insertions(+) diff --git a/.github/workflows/coverage-integration.yaml b/.github/workflows/coverage-integration.yaml index fb53b86f7284e..0cc03fa3bad1c 100644 --- a/.github/workflows/coverage-integration.yaml +++ b/.github/workflows/coverage-integration.yaml @@ -44,6 +44,7 @@ jobs: needs: bvt-group-plan uses: matrixorigin/CI/.github/workflows/e2e-compose.yaml@xp/ci-two-bvt-coverage with: + enable_parallel_bvt: true bvt_group: ${{ needs.bvt-group-plan.outputs.compose_group }} bvt_generation: ${{ needs.bvt-group-plan.outputs.generation }} secrets: inherit @@ -53,6 +54,7 @@ jobs: needs: bvt-group-plan uses: matrixorigin/CI/.github/workflows/e2e-standalone.yaml@xp/ci-two-bvt-coverage with: + enable_parallel_bvt: true bvt_group: ${{ needs.bvt-group-plan.outputs.launch_group }} bvt_generation: ${{ needs.bvt-group-plan.outputs.generation }} secrets: inherit diff --git a/.github/workflows/entrypoint.yaml b/.github/workflows/entrypoint.yaml index c340aed950507..1e73ab87c52fe 100644 --- a/.github/workflows/entrypoint.yaml +++ b/.github/workflows/entrypoint.yaml @@ -82,6 +82,7 @@ jobs: if: ${{ needs.check-pr-valid.outputs.pr_valid == 'true' && github.base_ref != '3.0-dev' }} uses: matrixorigin/CI/.github/workflows/e2e-compose.yaml@main with: + enable_parallel_bvt: true bvt_group: ${{ needs.bvt-group-plan.outputs.compose_group }} bvt_generation: ${{ needs.bvt-group-plan.outputs.generation }} secrets: inherit @@ -92,6 +93,7 @@ jobs: if: ${{ needs.check-pr-valid.outputs.pr_valid == 'true' && github.base_ref != '3.0-dev' }} uses: matrixorigin/CI/.github/workflows/e2e-standalone.yaml@main with: + enable_parallel_bvt: true bvt_group: ${{ needs.bvt-group-plan.outputs.launch_group }} bvt_generation: ${{ needs.bvt-group-plan.outputs.generation }} secrets: inherit From 218eedcd22554b878f35b59a13bb57b9bafaff38 Mon Sep 17 00:00:00 2001 From: XuPeng-SH Date: Mon, 27 Jul 2026 07:48:03 +0800 Subject: [PATCH 22/25] ci: isolate parallel coverage callers --- .github/workflows/coverage-integration.yaml | 10 +++------- .github/workflows/entrypoint.yaml | 10 +++------- 2 files changed, 6 insertions(+), 14 deletions(-) diff --git a/.github/workflows/coverage-integration.yaml b/.github/workflows/coverage-integration.yaml index 0cc03fa3bad1c..8e27c005b4fad 100644 --- a/.github/workflows/coverage-integration.yaml +++ b/.github/workflows/coverage-integration.yaml @@ -42,9 +42,8 @@ jobs: matrixone-compose-ci: name: Matrixone Compose CI needs: bvt-group-plan - uses: matrixorigin/CI/.github/workflows/e2e-compose.yaml@xp/ci-two-bvt-coverage + uses: matrixorigin/CI/.github/workflows/e2e-compose-parallel.yaml@xp/ci-two-bvt-coverage with: - enable_parallel_bvt: true bvt_group: ${{ needs.bvt-group-plan.outputs.compose_group }} bvt_generation: ${{ needs.bvt-group-plan.outputs.generation }} secrets: inherit @@ -52,9 +51,8 @@ jobs: matrixone-standalone-ci: name: Matrixone Standlone CI needs: bvt-group-plan - uses: matrixorigin/CI/.github/workflows/e2e-standalone.yaml@xp/ci-two-bvt-coverage + uses: matrixorigin/CI/.github/workflows/e2e-standalone-parallel.yaml@xp/ci-two-bvt-coverage with: - enable_parallel_bvt: true bvt_group: ${{ needs.bvt-group-plan.outputs.launch_group }} bvt_generation: ${{ needs.bvt-group-plan.outputs.generation }} secrets: inherit @@ -62,9 +60,7 @@ jobs: matrixone-utils-ci: name: Matrixone Utils CI needs: bvt-group-plan - uses: matrixorigin/CI/.github/workflows/utils.yaml@xp/ci-two-bvt-coverage - with: - enable_legacy_coverage: false + uses: matrixorigin/CI/.github/workflows/utils-parallel.yaml@xp/ci-two-bvt-coverage secrets: inherit matrixone-coverage-merge: diff --git a/.github/workflows/entrypoint.yaml b/.github/workflows/entrypoint.yaml index 1e73ab87c52fe..d237f3985a616 100644 --- a/.github/workflows/entrypoint.yaml +++ b/.github/workflows/entrypoint.yaml @@ -80,9 +80,8 @@ jobs: name: Matrixone Compose CI needs: [check-pr-valid, bvt-group-plan] if: ${{ needs.check-pr-valid.outputs.pr_valid == 'true' && github.base_ref != '3.0-dev' }} - uses: matrixorigin/CI/.github/workflows/e2e-compose.yaml@main + uses: matrixorigin/CI/.github/workflows/e2e-compose-parallel.yaml@main with: - enable_parallel_bvt: true bvt_group: ${{ needs.bvt-group-plan.outputs.compose_group }} bvt_generation: ${{ needs.bvt-group-plan.outputs.generation }} secrets: inherit @@ -91,9 +90,8 @@ jobs: name: Matrixone Standlone CI needs: [check-pr-valid, bvt-group-plan] if: ${{ needs.check-pr-valid.outputs.pr_valid == 'true' && github.base_ref != '3.0-dev' }} - uses: matrixorigin/CI/.github/workflows/e2e-standalone.yaml@main + uses: matrixorigin/CI/.github/workflows/e2e-standalone-parallel.yaml@main with: - enable_parallel_bvt: true bvt_group: ${{ needs.bvt-group-plan.outputs.launch_group }} bvt_generation: ${{ needs.bvt-group-plan.outputs.generation }} secrets: inherit @@ -102,9 +100,7 @@ jobs: name: Matrixone Utils CI needs: check-pr-valid if: ${{ needs.check-pr-valid.outputs.pr_valid == 'true' && github.base_ref != '3.0-dev' }} - uses: matrixorigin/CI/.github/workflows/utils.yaml@main - with: - enable_legacy_coverage: false + uses: matrixorigin/CI/.github/workflows/utils-parallel.yaml@main secrets: inherit matrixone-coverage-merge: From 16b812a9356388c09a1371dd71ed4e3010c89f7d Mon Sep 17 00:00:00 2001 From: XuPeng-SH Date: Mon, 27 Jul 2026 07:50:56 +0800 Subject: [PATCH 23/25] ci: restrict workflow token permissions --- .github/workflows/coverage-integration.yaml | 9 ++++++--- .github/workflows/entrypoint.yaml | 3 +++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/.github/workflows/coverage-integration.yaml b/.github/workflows/coverage-integration.yaml index 8e27c005b4fad..50e3f30549d21 100644 --- a/.github/workflows/coverage-integration.yaml +++ b/.github/workflows/coverage-integration.yaml @@ -9,9 +9,12 @@ concurrency: group: ${{ github.event.pull_request.head.repo.full_name }}/${{ github.event.pull_request.head.ref }}/${{ github.workflow }} cancel-in-progress: true -# pull_request_target intentionally loads the workflow from the base branch. -# This short-lived, same-repository harness validates both PR branches together -# and is removed after the integration run. +permissions: + contents: read + +# The production pull_request_target workflow loads its definition from the +# base branch. This short-lived, same-repository pull_request harness validates +# both PR branches together and is removed after the integration run. jobs: bvt-group-plan: if: ${{ github.event.pull_request.head.repo.full_name == github.repository && github.event.pull_request.head.ref == 'xp/ci-race-coverage-artifacts' }} diff --git a/.github/workflows/entrypoint.yaml b/.github/workflows/entrypoint.yaml index d237f3985a616..b8a7e63cca164 100644 --- a/.github/workflows/entrypoint.yaml +++ b/.github/workflows/entrypoint.yaml @@ -9,6 +9,9 @@ concurrency: group: ${{ github.event.pull_request.head.repo.full_name}}/${{ github.event.pull_request.head.ref }}/${{ github.workflow }} cancel-in-progress: true +permissions: + contents: read + jobs: check-pr-valid: runs-on: ubuntu-latest From f5fe7732f0112b4c791481e4d2b432cfd0ada753 Mon Sep 17 00:00:00 2001 From: XuPeng-SH Date: Mon, 27 Jul 2026 08:28:58 +0800 Subject: [PATCH 24/25] ci: validate coverage against merged workflows --- .github/workflows/coverage-integration.yaml | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/.github/workflows/coverage-integration.yaml b/.github/workflows/coverage-integration.yaml index 50e3f30549d21..30f2861ea2c97 100644 --- a/.github/workflows/coverage-integration.yaml +++ b/.github/workflows/coverage-integration.yaml @@ -37,7 +37,7 @@ jobs: matrixone-ut-coverage: name: Matrixone UT Coverage needs: bvt-group-plan - uses: matrixorigin/CI/.github/workflows/coverage-ut.yaml@xp/ci-two-bvt-coverage + uses: matrixorigin/CI/.github/workflows/coverage-ut.yaml@main with: bvt_generation: ${{ needs.bvt-group-plan.outputs.generation }} secrets: inherit @@ -45,7 +45,7 @@ jobs: matrixone-compose-ci: name: Matrixone Compose CI needs: bvt-group-plan - uses: matrixorigin/CI/.github/workflows/e2e-compose-parallel.yaml@xp/ci-two-bvt-coverage + uses: matrixorigin/CI/.github/workflows/e2e-compose-parallel.yaml@main with: bvt_group: ${{ needs.bvt-group-plan.outputs.compose_group }} bvt_generation: ${{ needs.bvt-group-plan.outputs.generation }} @@ -54,7 +54,7 @@ jobs: matrixone-standalone-ci: name: Matrixone Standlone CI needs: bvt-group-plan - uses: matrixorigin/CI/.github/workflows/e2e-standalone-parallel.yaml@xp/ci-two-bvt-coverage + uses: matrixorigin/CI/.github/workflows/e2e-standalone-parallel.yaml@main with: bvt_group: ${{ needs.bvt-group-plan.outputs.launch_group }} bvt_generation: ${{ needs.bvt-group-plan.outputs.generation }} @@ -63,16 +63,15 @@ jobs: matrixone-utils-ci: name: Matrixone Utils CI needs: bvt-group-plan - uses: matrixorigin/CI/.github/workflows/utils-parallel.yaml@xp/ci-two-bvt-coverage + uses: matrixorigin/CI/.github/workflows/utils-parallel.yaml@main secrets: inherit matrixone-coverage-merge: name: Matrixone Utils CI needs: [bvt-group-plan, matrixone-ut-coverage, matrixone-compose-ci, matrixone-standalone-ci] if: ${{ always() }} - uses: matrixorigin/CI/.github/workflows/coverage-merge.yaml@xp/ci-two-bvt-coverage + uses: matrixorigin/CI/.github/workflows/coverage-merge.yaml@main with: prerequisites_ready: ${{ needs.bvt-group-plan.result == 'success' && needs.matrixone-ut-coverage.result == 'success' && needs.matrixone-ut-coverage.outputs.coverage_ready == 'true' && needs.matrixone-compose-ci.result == 'success' && needs.matrixone-standalone-ci.result == 'success' }} expected_bvt_generation: ${{ needs.bvt-group-plan.outputs.generation }} - ci_ref: xp/ci-two-bvt-coverage secrets: inherit From 02532a0c365f0927491c05c78cbbf1ba2b6be485 Mon Sep 17 00:00:00 2001 From: XuPeng-SH Date: Mon, 27 Jul 2026 09:10:06 +0800 Subject: [PATCH 25/25] ci: remove parallel coverage validation harness --- .github/workflows/coverage-integration.yaml | 77 --------------------- 1 file changed, 77 deletions(-) delete mode 100644 .github/workflows/coverage-integration.yaml diff --git a/.github/workflows/coverage-integration.yaml b/.github/workflows/coverage-integration.yaml deleted file mode 100644 index 30f2861ea2c97..0000000000000 --- a/.github/workflows/coverage-integration.yaml +++ /dev/null @@ -1,77 +0,0 @@ -name: MatrixOne Coverage Integration - -on: - pull_request: - types: [opened, synchronize, reopened, ready_for_review] - branches: [main] - -concurrency: - group: ${{ github.event.pull_request.head.repo.full_name }}/${{ github.event.pull_request.head.ref }}/${{ github.workflow }} - cancel-in-progress: true - -permissions: - contents: read - -# The production pull_request_target workflow loads its definition from the -# base branch. This short-lived, same-repository pull_request harness validates -# both PR branches together and is removed after the integration run. -jobs: - bvt-group-plan: - if: ${{ github.event.pull_request.head.repo.full_name == github.repository && github.event.pull_request.head.ref == 'xp/ci-race-coverage-artifacts' }} - runs-on: ubuntu-latest - outputs: - compose_group: ${{ steps.plan.outputs.compose_group }} - launch_group: ${{ steps.plan.outputs.launch_group }} - generation: ${{ steps.plan.outputs.generation }} - steps: - - id: plan - run: | - set -euo pipefail - compose_group=$(( (${{ github.run_id }} + ${{ github.run_attempt }}) % 2 )) - { - echo "compose_group=${compose_group}" - echo "launch_group=$((1 - compose_group))" - echo "generation=${{ github.run_id }}-${{ github.run_attempt }}" - } >> "$GITHUB_OUTPUT" - - matrixone-ut-coverage: - name: Matrixone UT Coverage - needs: bvt-group-plan - uses: matrixorigin/CI/.github/workflows/coverage-ut.yaml@main - with: - bvt_generation: ${{ needs.bvt-group-plan.outputs.generation }} - secrets: inherit - - matrixone-compose-ci: - name: Matrixone Compose CI - needs: bvt-group-plan - uses: matrixorigin/CI/.github/workflows/e2e-compose-parallel.yaml@main - with: - bvt_group: ${{ needs.bvt-group-plan.outputs.compose_group }} - bvt_generation: ${{ needs.bvt-group-plan.outputs.generation }} - secrets: inherit - - matrixone-standalone-ci: - name: Matrixone Standlone CI - needs: bvt-group-plan - uses: matrixorigin/CI/.github/workflows/e2e-standalone-parallel.yaml@main - with: - bvt_group: ${{ needs.bvt-group-plan.outputs.launch_group }} - bvt_generation: ${{ needs.bvt-group-plan.outputs.generation }} - secrets: inherit - - matrixone-utils-ci: - name: Matrixone Utils CI - needs: bvt-group-plan - uses: matrixorigin/CI/.github/workflows/utils-parallel.yaml@main - secrets: inherit - - matrixone-coverage-merge: - name: Matrixone Utils CI - needs: [bvt-group-plan, matrixone-ut-coverage, matrixone-compose-ci, matrixone-standalone-ci] - if: ${{ always() }} - uses: matrixorigin/CI/.github/workflows/coverage-merge.yaml@main - with: - prerequisites_ready: ${{ needs.bvt-group-plan.result == 'success' && needs.matrixone-ut-coverage.result == 'success' && needs.matrixone-ut-coverage.outputs.coverage_ready == 'true' && needs.matrixone-compose-ci.result == 'success' && needs.matrixone-standalone-ci.result == 'success' }} - expected_bvt_generation: ${{ needs.bvt-group-plan.outputs.generation }} - secrets: inherit