From 77d2029fce53e5ae4706fcca2ca161bf439daf47 Mon Sep 17 00:00:00 2001 From: contrueCT Date: Mon, 17 Aug 2026 17:38:25 +0800 Subject: [PATCH 1/3] fix(ci): preserve PD and Store coverage aggregation --- .github/workflows/pd-store-ci.yml | 86 +++++-- hugegraph-pd/hg-pd-test/pom.xml | 37 +--- hugegraph-pd/pom.xml | 3 +- .../assembly/travis/check-jacoco-report.sh | 60 +++++ .../travis/test-check-jacoco-report.sh | 209 ++++++++++++++++++ hugegraph-store/hg-store-test/pom.xml | 33 +-- hugegraph-store/pom.xml | 5 +- 7 files changed, 360 insertions(+), 73 deletions(-) create mode 100755 hugegraph-server/hugegraph-dist/src/assembly/travis/check-jacoco-report.sh create mode 100755 hugegraph-server/hugegraph-dist/src/assembly/travis/test-check-jacoco-report.sh diff --git a/.github/workflows/pd-store-ci.yml b/.github/workflows/pd-store-ci.yml index 6f670e1cb9..b646d6ef68 100644 --- a/.github/workflows/pd-store-ci.yml +++ b/.github/workflows/pd-store-ci.yml @@ -33,6 +33,9 @@ jobs: with: fetch-depth: 5 + - name: Run JaCoCo report validator tests + run: hugegraph-server/hugegraph-dist/src/assembly/travis/test-check-jacoco-report.sh + - name: Use staged maven repo settings run: | cp $HOME/.m2/settings.xml /tmp/settings.xml || true @@ -65,7 +68,7 @@ jobs: USE_STAGE: 'false' # Whether to include the stage repository. # TODO: remove outdated env TRAVIS_DIR: hugegraph-server/hugegraph-dist/src/assembly/travis - REPORT_DIR: target/site/jacoco + REPORT_FILE: hugegraph-pd/hg-pd-test/target/site/jacoco/jacoco.xml steps: - name: Install JDK 11 @@ -92,20 +95,22 @@ jobs: cp $HOME/.m2/settings.xml /tmp/settings.xml mv -vf .github/configs/settings.xml $HOME/.m2/settings.xml - - name: Run common test + - name: Package + # todo remove --fail-at-end after test run: | - mvn test -pl hugegraph-pd/hg-pd-test -am -P pd-common-test + mvn clean package -U -Dmaven.javadoc.skip=true -Dmaven.test.skip=true -ntp --fail-at-end - - name: Run core test + # These tests do not require starting a PD instance. Run them after the + # clean package so their JaCoCo data survives until the final report. + - name: Run common test run: | - mvn test -pl hugegraph-pd/hg-pd-test -am -P pd-core-test - - # The above tests do not require starting a PD instance. + mvn test -pl hugegraph-pd/hg-pd-test -am \ + -P pd-common-test -Djacoco.sessionId=pd-common-test - - name: Package - # todo remove --fail-at-end after test + - name: Run core test run: | - mvn clean package -U -Dmaven.javadoc.skip=true -Dmaven.test.skip=true -ntp --fail-at-end + mvn test -pl hugegraph-pd/hg-pd-test -am \ + -P pd-core-test -Djacoco.sessionId=pd-core-test - name: Check startup test prerequisites (PD) id: pd-preflight @@ -139,16 +144,32 @@ jobs: - name: Run client test run: | - mvn test -pl hugegraph-pd/hg-pd-test -am -P pd-client-test + mvn test -pl hugegraph-pd/hg-pd-test -am \ + -P pd-client-test -Djacoco.sessionId=pd-client-test - name: Run rest test run: | - mvn test -pl hugegraph-pd/hg-pd-test -am -P pd-rest-test + mvn test -pl hugegraph-pd/hg-pd-test -am \ + -P pd-rest-test -Djacoco.sessionId=pd-rest-test + + - name: Generate aggregate coverage report + run: | + mvn verify -pl hugegraph-pd/hg-pd-test -am -P jacoco -DskipTests -ntp + + - name: Validate aggregate coverage report + run: | + $TRAVIS_DIR/check-jacoco-report.sh \ + --require-session pd-common-test \ + --require-session pd-core-test \ + --require-session pd-client-test \ + --require-session pd-rest-test \ + "$REPORT_FILE" \ + hg-pd-grpc hg-pd-common hg-pd-client hg-pd-core hg-pd-service hg-pd-dist - name: Upload coverage to Codecov uses: codecov/codecov-action@v3.0.0 with: - file: ${{ env.REPORT_DIR }}/*.xml + files: ${{ env.REPORT_FILE }} store: needs: struct @@ -157,7 +178,7 @@ jobs: USE_STAGE: 'false' # Whether to include the stage repository. # TODO: remove outdated env TRAVIS_DIR: hugegraph-server/hugegraph-dist/src/assembly/travis - REPORT_DIR: target/site/jacoco + REPORT_FILE: hugegraph-store/hg-store-test/target/site/jacoco/jacoco.xml steps: - name: Install JDK 11 @@ -228,32 +249,55 @@ jobs: - name: Run common test run: | - mvn test -pl hugegraph-store/hg-store-test -am -P store-common-test + mvn test -pl hugegraph-store/hg-store-test -am \ + -P store-common-test -Djacoco.sessionId=store-common-test - name: Run client test run: | - mvn test -pl hugegraph-store/hg-store-test -am -P store-client-test + mvn test -pl hugegraph-store/hg-store-test -am \ + -P store-client-test -Djacoco.sessionId=store-client-test - name: Run core test run: | - mvn test -pl hugegraph-store/hg-store-test -am -P store-core-test + mvn test -pl hugegraph-store/hg-store-test -am \ + -P store-core-test -Djacoco.sessionId=store-core-test - name: Run rocksdb test run: | - mvn test -pl hugegraph-store/hg-store-test -am -P store-rocksdb-test + mvn test -pl hugegraph-store/hg-store-test -am \ + -P store-rocksdb-test -Djacoco.sessionId=store-rocksdb-test - name: Run server test run: | - mvn test -pl hugegraph-store/hg-store-test -am -P store-server-test + mvn test -pl hugegraph-store/hg-store-test -am \ + -P store-server-test -Djacoco.sessionId=store-server-test - name: Run raft-core test run: | - mvn test -pl hugegraph-store/hg-store-test -am -P store-raftcore-test + mvn test -pl hugegraph-store/hg-store-test -am \ + -P store-raftcore-test -Djacoco.sessionId=store-raftcore-test + + - name: Generate aggregate coverage report + run: | + mvn verify -pl hugegraph-store/hg-store-test -am -P jacoco -DskipTests -ntp + + - name: Validate aggregate coverage report + run: | + $TRAVIS_DIR/check-jacoco-report.sh \ + --require-session store-common-test \ + --require-session store-client-test \ + --require-session store-core-test \ + --require-session store-rocksdb-test \ + --require-session store-server-test \ + --require-session store-raftcore-test \ + "$REPORT_FILE" \ + hg-store-grpc hg-store-common hg-store-client hg-store-rocksdb \ + hg-store-core hg-store-node - name: Upload coverage to Codecov uses: codecov/codecov-action@v3.0.0 with: - file: ${{ env.REPORT_DIR }}/*.xml + files: ${{ env.REPORT_FILE }} hstore: needs: struct diff --git a/hugegraph-pd/hg-pd-test/pom.xml b/hugegraph-pd/hg-pd-test/pom.xml index 44d5d084ca..72cdef81aa 100644 --- a/hugegraph-pd/hg-pd-test/pom.xml +++ b/hugegraph-pd/hg-pd-test/pom.xml @@ -46,18 +46,19 @@ org.jacoco jacoco-maven-plugin - 0.8.4 - - - **/grpc/**.* - **/config/**.* - - + 0.8.8 + coverage-report + verify - prepare-agent + report-aggregate + + + ${project.basedir}/target/site/jacoco + + @@ -258,25 +259,7 @@ org.jacoco jacoco-maven-plugin - 0.8.4 - - - pre-test - - prepare-agent - - - - post-test - test - - report-aggregate - - - ${basedir}/target/site/jacoco - - - + 0.8.8 org/apache/hugegraph/pd/rest/*.class diff --git a/hugegraph-pd/pom.xml b/hugegraph-pd/pom.xml index ceb8af33b2..83b39c8377 100644 --- a/hugegraph-pd/pom.xml +++ b/hugegraph-pd/pom.xml @@ -74,8 +74,9 @@ org.jacoco jacoco-maven-plugin - 0.8.4 + 0.8.8 + true **/grpc/**.* **/config/**.* diff --git a/hugegraph-server/hugegraph-dist/src/assembly/travis/check-jacoco-report.sh b/hugegraph-server/hugegraph-dist/src/assembly/travis/check-jacoco-report.sh new file mode 100755 index 0000000000..db4ebeced9 --- /dev/null +++ b/hugegraph-server/hugegraph-dist/src/assembly/travis/check-jacoco-report.sh @@ -0,0 +1,60 @@ +#!/bin/bash +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +set -uo pipefail + +REQUIRED_SESSIONS=() +while [[ "${1:-}" == "--require-session" ]]; do + REQUIRED_SESSIONS+=("${2:-}") + shift 2 || true +done + +if (( ${#REQUIRED_SESSIONS[@]} == 0 )); then + echo "ERROR: at least one --require-session is required" >&2 + exit 1 +fi + +REPORT_FILE="${1:-}" +shift || true + +if [[ -z "${REPORT_FILE}" || ! -s "${REPORT_FILE}" ]]; then + echo "ERROR: JaCoCo report not found or empty: ${REPORT_FILE:-}" >&2 + exit 1 +fi + +if ! grep -Eq '&2 + exit 1 +fi + +for session in "${REQUIRED_SESSIONS[@]}"; do + if ! grep -Fq "&2 + exit 1 + fi +done + +for module in "$@"; do + if ! grep -Fq "&2 + exit 1 + fi +done + +echo "JaCoCo report ${REPORT_FILE} contains all expected modules" diff --git a/hugegraph-server/hugegraph-dist/src/assembly/travis/test-check-jacoco-report.sh b/hugegraph-server/hugegraph-dist/src/assembly/travis/test-check-jacoco-report.sh new file mode 100755 index 0000000000..68a72319f4 --- /dev/null +++ b/hugegraph-server/hugegraph-dist/src/assembly/travis/test-check-jacoco-report.sh @@ -0,0 +1,209 @@ +#!/bin/bash +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +set -uo pipefail + +SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +REPO_ROOT=$(cd "${SCRIPT_DIR}/../../../../.." && pwd) +VALIDATOR="${SCRIPT_DIR}/check-jacoco-report.sh" +TMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/jacoco-report-test.XXXXXX") +CASE_OUTPUT="" +CASE_RC=0 + +trap 'rm -rf "${TMP_DIR}"' EXIT + +fail() { + echo "FAIL: $1" >&2 + [[ -z "${CASE_OUTPUT}" ]] || printf '%s\n' "${CASE_OUTPUT}" >&2 + exit 1 +} + +run_case() { + CASE_OUTPUT=$("${VALIDATOR}" "$@" 2>&1) + CASE_RC=$? +} + +assert_success() { + [[ "${CASE_RC}" -eq 0 ]] || fail "$1 returned ${CASE_RC}" +} + +assert_failure() { + [[ "${CASE_RC}" -ne 0 ]] || fail "$1 unexpectedly succeeded" +} + +assert_output() { + [[ "${CASE_OUTPUT}" == *"$1"* ]] || fail "missing output '$1'" +} + +if [[ ! -x "${VALIDATOR}" ]]; then + fail "validator not found or not executable at ${VALIDATOR}" +fi + +echo "JaCoCo report validator tests" + +run_case --require-session suite-a "${TMP_DIR}/missing.xml" hg-pd-client +assert_failure "missing report" +assert_output "not found or empty" + +touch "${TMP_DIR}/empty.xml" +run_case --require-session suite-a "${TMP_DIR}/empty.xml" hg-pd-client +assert_failure "empty report" +assert_output "not found or empty" + +cat > "${TMP_DIR}/valid.xml" <<'EOF' + + + + + + + + +EOF + +run_case --require-session suite-a --require-session suite-b \ + "${TMP_DIR}/valid.xml" hg-pd-client hg-pd-core +assert_success "complete report" +assert_output "contains all expected modules" + +run_case --require-session suite-a --require-session suite-c \ + "${TMP_DIR}/valid.xml" hg-pd-client hg-pd-core +assert_failure "report missing a required session" +assert_output "missing JaCoCo session 'suite-c'" + +sed 's/covered="3"/covered="0"/' "${TMP_DIR}/valid.xml" > "${TMP_DIR}/uncovered.xml" +run_case --require-session suite-a --require-session suite-b \ + "${TMP_DIR}/uncovered.xml" hg-pd-client hg-pd-core +assert_failure "report without covered instructions" +assert_output "has no covered instructions" + +run_case --require-session suite-a --require-session suite-b \ + "${TMP_DIR}/valid.xml" hg-pd-client hg-pd-service +assert_failure "report missing an expected module" +assert_output "missing JaCoCo group 'hg-pd-service'" + +python3 - "${REPO_ROOT}" <<'PY' || fail "aggregation configuration contract failed" +from pathlib import Path +import sys +import xml.etree.ElementTree as ET + +ROOT = Path(sys.argv[1]) +NS = "{http://maven.apache.org/POM/4.0.0}" + + +def child_text(element, name): + child = element.find(NS + name) + return "" if child is None or child.text is None else child.text.strip() + + +def jacoco_plugin(container): + plugins = container.find(NS + "plugins") + assert plugins is not None + for plugin in plugins.findall(NS + "plugin"): + if child_text(plugin, "artifactId") == "jacoco-maven-plugin": + return plugin + raise AssertionError("JaCoCo plugin is missing") + + +def goals(plugin): + return [goal.text.strip() for goal in plugin.findall( + ".//" + NS + "goal") if goal.text] + + +def check_module(module, test_module): + parent = ET.parse(ROOT / module / "pom.xml").getroot() + parent_plugin = jacoco_plugin(parent.find(NS + "build")) + assert child_text(parent_plugin, "version") == "0.8.8" + assert child_text(parent_plugin.find(NS + "configuration"), "append") == "true" + + test = ET.parse(ROOT / module / test_module / "pom.xml").getroot() + default_plugin = jacoco_plugin(test.find(NS + "build")) + assert child_text(default_plugin, "version") == "0.8.8" + assert "report-aggregate" not in goals(default_plugin) + + profile = None + for candidate in test.findall(".//" + NS + "profile"): + if child_text(candidate, "id") == "jacoco": + profile = candidate + break + assert profile is not None + profile_plugin = jacoco_plugin(profile.find(NS + "build")) + assert child_text(profile_plugin, "version") == "0.8.8" + executions = profile_plugin.findall(".//" + NS + "execution") + aggregates = [execution for execution in executions + if "report-aggregate" in goals(execution)] + assert len(aggregates) == 1 + assert child_text(aggregates[0], "phase") == "verify" + + +check_module("hugegraph-pd", "hg-pd-test") +check_module("hugegraph-store", "hg-store-test") + +store_test = ET.parse(ROOT / "hugegraph-store/hg-store-test/pom.xml").getroot() +dependencies = store_test.find(NS + "dependencies") +assert dependencies is not None +assert any(child_text(dep, "artifactId") == "hg-store-rocksdb" + for dep in dependencies.findall(NS + "dependency")) + +workflow = (ROOT / ".github/workflows/pd-store-ci.yml").read_text() +pd_job = workflow.split("\n pd:\n", 1)[1].split("\n store:\n", 1)[0] +store_job = workflow.split("\n store:\n", 1)[1].split("\n hstore:\n", 1)[0] + + +def assert_order(job, commands): + positions = [job.index(command) for command in commands] + assert positions == sorted(positions) + + +assert_order(pd_job, [ + "mvn clean package", + "-P pd-common-test -Djacoco.sessionId=pd-common-test", + "-P pd-core-test -Djacoco.sessionId=pd-core-test", + "-P pd-client-test -Djacoco.sessionId=pd-client-test", + "-P pd-rest-test -Djacoco.sessionId=pd-rest-test", + "mvn verify", "--require-session pd-common-test", + "--require-session pd-core-test", "--require-session pd-client-test", + "--require-session pd-rest-test", "codecov/codecov-action", +]) +assert pd_job.count("mvn clean") == 1 +assert "hugegraph-pd/hg-pd-test/target/site/jacoco/jacoco.xml" in pd_job +assert "files: ${{ env.REPORT_FILE }}" in pd_job +assert "\n directory:" not in pd_job + +assert_order(store_job, [ + "mvn clean package", + "-P store-common-test -Djacoco.sessionId=store-common-test", + "-P store-client-test -Djacoco.sessionId=store-client-test", + "-P store-core-test -Djacoco.sessionId=store-core-test", + "-P store-rocksdb-test -Djacoco.sessionId=store-rocksdb-test", + "-P store-server-test -Djacoco.sessionId=store-server-test", + "-P store-raftcore-test -Djacoco.sessionId=store-raftcore-test", + "mvn verify", "--require-session store-common-test", + "--require-session store-client-test", "--require-session store-core-test", + "--require-session store-rocksdb-test", "--require-session store-server-test", + "--require-session store-raftcore-test", "codecov/codecov-action", +]) +assert store_job.count("mvn clean") == 1 +assert "hugegraph-store/hg-store-test/target/site/jacoco/jacoco.xml" in store_job +assert "files: ${{ env.REPORT_FILE }}" in store_job +assert "\n directory:" not in store_job + +print("PASS: JaCoCo aggregation configuration contract") +PY + +echo "PASS: JaCoCo report validator contract" diff --git a/hugegraph-store/hg-store-test/pom.xml b/hugegraph-store/hg-store-test/pom.xml index 36308f449d..a42fe6d270 100644 --- a/hugegraph-store/hg-store-test/pom.xml +++ b/hugegraph-store/hg-store-test/pom.xml @@ -44,17 +44,18 @@ org.jacoco jacoco-maven-plugin - 0.8.4 + 0.8.8 - default + coverage-report verify report-aggregate - ${project.basedir}/../target/site/jacoco + + ${project.basedir}/target/site/jacoco @@ -76,6 +77,11 @@ hg-store-common ${revision} + + org.apache.hugegraph + hg-store-rocksdb + ${revision} + org.projectlombok lombok @@ -295,26 +301,7 @@ org.jacoco jacoco-maven-plugin - 0.8.4 - - - pre-test - - - prepare-agent - - - - post-test - test - - report-aggregate - - - ${basedir}/target/site/jacoco - - - + 0.8.8 **/grpc/**/* diff --git a/hugegraph-store/pom.xml b/hugegraph-store/pom.xml index 9ff1e933e5..a8212b70db 100644 --- a/hugegraph-store/pom.xml +++ b/hugegraph-store/pom.xml @@ -98,7 +98,10 @@ org.jacoco jacoco-maven-plugin - 0.8.4 + 0.8.8 + + true + From e7ae238a7aef933cf61f01daba36eb229477985e Mon Sep 17 00:00:00 2001 From: contrueCT Date: Tue, 18 Aug 2026 21:00:58 +0800 Subject: [PATCH 2/3] fix(ci): avoid post-test coverage check failures --- .github/workflows/pd-store-ci.yml | 14 ++++++++++++-- .../assembly/travis/test-check-jacoco-report.sh | 14 +++++++++++++- hugegraph-store/hg-store-test/pom.xml | 12 +++++++----- 3 files changed, 32 insertions(+), 8 deletions(-) diff --git a/.github/workflows/pd-store-ci.yml b/.github/workflows/pd-store-ci.yml index b646d6ef68..1c25c541a5 100644 --- a/.github/workflows/pd-store-ci.yml +++ b/.github/workflows/pd-store-ci.yml @@ -100,6 +100,10 @@ jobs: run: | mvn clean package -U -Dmaven.javadoc.skip=true -Dmaven.test.skip=true -ntp --fail-at-end + - name: Check source formatting + run: | + mvn editorconfig:check -pl hugegraph-pd/hg-pd-test -am -ntp + # These tests do not require starting a PD instance. Run them after the # clean package so their JaCoCo data survives until the final report. - name: Run common test @@ -154,7 +158,8 @@ jobs: - name: Generate aggregate coverage report run: | - mvn verify -pl hugegraph-pd/hg-pd-test -am -P jacoco -DskipTests -ntp + mvn verify -pl hugegraph-pd/hg-pd-test -am -P jacoco \ + -DskipTests -Deditorconfig.skip=true -ntp - name: Validate aggregate coverage report run: | @@ -210,6 +215,10 @@ jobs: run: | mvn clean package -U -Dmaven.javadoc.skip=true -Dmaven.test.skip=true -ntp --fail-at-end + - name: Check source formatting + run: | + mvn editorconfig:check -pl hugegraph-store/hg-store-test -am -ntp + - name: Check startup test prerequisites (Store) id: store-preflight run: | @@ -279,7 +288,8 @@ jobs: - name: Generate aggregate coverage report run: | - mvn verify -pl hugegraph-store/hg-store-test -am -P jacoco -DskipTests -ntp + mvn verify -pl hugegraph-store/hg-store-test -am -P jacoco \ + -DskipTests -Deditorconfig.skip=true -ntp - name: Validate aggregate coverage report run: | diff --git a/hugegraph-server/hugegraph-dist/src/assembly/travis/test-check-jacoco-report.sh b/hugegraph-server/hugegraph-dist/src/assembly/travis/test-check-jacoco-report.sh index 68a72319f4..5ae6db3e93 100755 --- a/hugegraph-server/hugegraph-dist/src/assembly/travis/test-check-jacoco-report.sh +++ b/hugegraph-server/hugegraph-dist/src/assembly/travis/test-check-jacoco-report.sh @@ -157,8 +157,14 @@ check_module("hugegraph-store", "hg-store-test") store_test = ET.parse(ROOT / "hugegraph-store/hg-store-test/pom.xml").getroot() dependencies = store_test.find(NS + "dependencies") assert dependencies is not None +assert not any(child_text(dep, "artifactId") == "hg-store-rocksdb" + for dep in dependencies.findall(NS + "dependency")) +store_jacoco = next(profile for profile in store_test.findall( + ".//" + NS + "profile") if child_text(profile, "id") == "jacoco") +profile_dependencies = store_jacoco.find(NS + "dependencies") +assert profile_dependencies is not None assert any(child_text(dep, "artifactId") == "hg-store-rocksdb" - for dep in dependencies.findall(NS + "dependency")) + for dep in profile_dependencies.findall(NS + "dependency")) workflow = (ROOT / ".github/workflows/pd-store-ci.yml").read_text() pd_job = workflow.split("\n pd:\n", 1)[1].split("\n store:\n", 1)[0] @@ -172,6 +178,7 @@ def assert_order(job, commands): assert_order(pd_job, [ "mvn clean package", + "mvn editorconfig:check -pl hugegraph-pd/hg-pd-test -am -ntp", "-P pd-common-test -Djacoco.sessionId=pd-common-test", "-P pd-core-test -Djacoco.sessionId=pd-core-test", "-P pd-client-test -Djacoco.sessionId=pd-client-test", @@ -184,9 +191,12 @@ assert pd_job.count("mvn clean") == 1 assert "hugegraph-pd/hg-pd-test/target/site/jacoco/jacoco.xml" in pd_job assert "files: ${{ env.REPORT_FILE }}" in pd_job assert "\n directory:" not in pd_job +assert "mvn verify -pl hugegraph-pd/hg-pd-test -am -P jacoco \\ " \ + "-DskipTests -Deditorconfig.skip=true -ntp" in " ".join(pd_job.split()) assert_order(store_job, [ "mvn clean package", + "mvn editorconfig:check -pl hugegraph-store/hg-store-test -am -ntp", "-P store-common-test -Djacoco.sessionId=store-common-test", "-P store-client-test -Djacoco.sessionId=store-client-test", "-P store-core-test -Djacoco.sessionId=store-core-test", @@ -202,6 +212,8 @@ assert store_job.count("mvn clean") == 1 assert "hugegraph-store/hg-store-test/target/site/jacoco/jacoco.xml" in store_job assert "files: ${{ env.REPORT_FILE }}" in store_job assert "\n directory:" not in store_job +assert "mvn verify -pl hugegraph-store/hg-store-test -am -P jacoco \\ " \ + "-DskipTests -Deditorconfig.skip=true -ntp" in " ".join(store_job.split()) print("PASS: JaCoCo aggregation configuration contract") PY diff --git a/hugegraph-store/hg-store-test/pom.xml b/hugegraph-store/hg-store-test/pom.xml index a42fe6d270..8b7f10dd69 100644 --- a/hugegraph-store/hg-store-test/pom.xml +++ b/hugegraph-store/hg-store-test/pom.xml @@ -39,6 +39,13 @@ false + + + org.apache.hugegraph + hg-store-rocksdb + ${revision} + + @@ -77,11 +84,6 @@ hg-store-common ${revision} - - org.apache.hugegraph - hg-store-rocksdb - ${revision} - org.projectlombok lombok From 26f93a180ea50439ad0a5977f5566def5f817b13 Mon Sep 17 00:00:00 2001 From: contrueCT Date: Thu, 20 Aug 2026 01:50:05 +0800 Subject: [PATCH 3/3] fix(ci): validate coverage suite execution --- .github/workflows/pd-store-ci.yml | 30 +++-- .../assembly/travis/check-jacoco-report.sh | 67 +++++++++- .../travis/test-check-jacoco-report.sh | 124 ++++++++++++++++-- 3 files changed, 192 insertions(+), 29 deletions(-) diff --git a/.github/workflows/pd-store-ci.yml b/.github/workflows/pd-store-ci.yml index 1c25c541a5..e552aa43da 100644 --- a/.github/workflows/pd-store-ci.yml +++ b/.github/workflows/pd-store-ci.yml @@ -69,6 +69,7 @@ jobs: # TODO: remove outdated env TRAVIS_DIR: hugegraph-server/hugegraph-dist/src/assembly/travis REPORT_FILE: hugegraph-pd/hg-pd-test/target/site/jacoco/jacoco.xml + TEST_REPORT_DIR: hugegraph-pd/hg-pd-test/target/surefire-reports steps: - name: Install JDK 11 @@ -164,6 +165,14 @@ jobs: - name: Validate aggregate coverage report run: | $TRAVIS_DIR/check-jacoco-report.sh \ + --require-test-report \ + "$TEST_REPORT_DIR/TEST-org.apache.hugegraph.pd.common.CommonSuiteTest.xml" \ + --require-test-report \ + "$TEST_REPORT_DIR/TEST-org.apache.hugegraph.pd.core.PDCoreSuiteTest.xml" \ + --require-test-report \ + "$TEST_REPORT_DIR/TEST-org.apache.hugegraph.pd.client.PDClientSuiteTest.xml" \ + --require-test-report \ + "$TEST_REPORT_DIR/TEST-org.apache.hugegraph.pd.rest.PDRestSuiteTest.xml" \ --require-session pd-common-test \ --require-session pd-core-test \ --require-session pd-client-test \ @@ -184,6 +193,7 @@ jobs: # TODO: remove outdated env TRAVIS_DIR: hugegraph-server/hugegraph-dist/src/assembly/travis REPORT_FILE: hugegraph-store/hg-store-test/target/site/jacoco/jacoco.xml + TEST_REPORT_DIR: hugegraph-store/hg-store-test/target/surefire-reports steps: - name: Install JDK 11 @@ -266,21 +276,11 @@ jobs: mvn test -pl hugegraph-store/hg-store-test -am \ -P store-client-test -Djacoco.sessionId=store-client-test - - name: Run core test - run: | - mvn test -pl hugegraph-store/hg-store-test -am \ - -P store-core-test -Djacoco.sessionId=store-core-test - - name: Run rocksdb test run: | mvn test -pl hugegraph-store/hg-store-test -am \ -P store-rocksdb-test -Djacoco.sessionId=store-rocksdb-test - - name: Run server test - run: | - mvn test -pl hugegraph-store/hg-store-test -am \ - -P store-server-test -Djacoco.sessionId=store-server-test - - name: Run raft-core test run: | mvn test -pl hugegraph-store/hg-store-test -am \ @@ -294,11 +294,17 @@ jobs: - name: Validate aggregate coverage report run: | $TRAVIS_DIR/check-jacoco-report.sh \ + --require-test-report \ + "$TEST_REPORT_DIR/TEST-org.apache.hugegraph.store.common.CommonSuiteTest.xml" \ + --require-test-report \ + "$TEST_REPORT_DIR/TEST-org.apache.hugegraph.store.client.ClientSuiteTest.xml" \ + --require-test-report \ + "$TEST_REPORT_DIR/TEST-org.apache.hugegraph.store.rocksdb.RocksDbSuiteTest.xml" \ + --require-test-report \ + "$TEST_REPORT_DIR/TEST-org.apache.hugegraph.store.raftcore.RaftSuiteTest.xml" \ --require-session store-common-test \ --require-session store-client-test \ - --require-session store-core-test \ --require-session store-rocksdb-test \ - --require-session store-server-test \ --require-session store-raftcore-test \ "$REPORT_FILE" \ hg-store-grpc hg-store-common hg-store-client hg-store-rocksdb \ diff --git a/hugegraph-server/hugegraph-dist/src/assembly/travis/check-jacoco-report.sh b/hugegraph-server/hugegraph-dist/src/assembly/travis/check-jacoco-report.sh index db4ebeced9..d551bbdd0f 100755 --- a/hugegraph-server/hugegraph-dist/src/assembly/travis/check-jacoco-report.sh +++ b/hugegraph-server/hugegraph-dist/src/assembly/travis/check-jacoco-report.sh @@ -19,9 +19,33 @@ set -uo pipefail REQUIRED_SESSIONS=() -while [[ "${1:-}" == "--require-session" ]]; do - REQUIRED_SESSIONS+=("${2:-}") - shift 2 || true +REQUIRED_TEST_REPORTS=() +while (( $# > 0 )); do + case "${1}" in + --require-session) + if (( $# < 2 )) || [[ -z "${2:-}" || "${2}" == --* ]]; then + echo "ERROR: --require-session requires a non-empty value" >&2 + exit 1 + fi + REQUIRED_SESSIONS+=("${2}") + shift 2 + ;; + --require-test-report) + if (( $# < 2 )) || [[ -z "${2:-}" || "${2}" == --* ]]; then + echo "ERROR: --require-test-report requires a non-empty value" >&2 + exit 1 + fi + REQUIRED_TEST_REPORTS+=("${2}") + shift 2 + ;; + --*) + echo "ERROR: unknown option: ${1}" >&2 + exit 1 + ;; + *) + break + ;; + esac done if (( ${#REQUIRED_SESSIONS[@]} == 0 )); then @@ -29,14 +53,49 @@ if (( ${#REQUIRED_SESSIONS[@]} == 0 )); then exit 1 fi +if (( ${#REQUIRED_TEST_REPORTS[@]} == 0 )); then + echo "ERROR: at least one --require-test-report is required" >&2 + exit 1 +fi + REPORT_FILE="${1:-}" -shift || true +if (( $# > 0 )); then + shift +fi if [[ -z "${REPORT_FILE}" || ! -s "${REPORT_FILE}" ]]; then echo "ERROR: JaCoCo report not found or empty: ${REPORT_FILE:-}" >&2 exit 1 fi +if (( $# == 0 )); then + echo "ERROR: at least one expected module is required" >&2 + exit 1 +fi + +for test_report in "${REQUIRED_TEST_REPORTS[@]}"; do + if [[ ! -s "${test_report}" ]]; then + echo "ERROR: Surefire report not found or empty: ${test_report}" >&2 + exit 1 + fi + + if ! test_count=$(python3 - "${test_report}" <<'PY' +import sys +import xml.etree.ElementTree as ET + +root = ET.parse(sys.argv[1]).getroot() +print(int(root.attrib.get("tests", "0"))) +PY + ); then + echo "ERROR: unable to parse Surefire report: ${test_report}" >&2 + exit 1 + fi + if (( test_count <= 0 )); then + echo "ERROR: Surefire report has no tests: ${test_report}" >&2 + exit 1 + fi +done + if ! grep -Eq '&2 diff --git a/hugegraph-server/hugegraph-dist/src/assembly/travis/test-check-jacoco-report.sh b/hugegraph-server/hugegraph-dist/src/assembly/travis/test-check-jacoco-report.sh index 5ae6db3e93..8d8d425b64 100755 --- a/hugegraph-server/hugegraph-dist/src/assembly/travis/test-check-jacoco-report.sh +++ b/hugegraph-server/hugegraph-dist/src/assembly/travis/test-check-jacoco-report.sh @@ -38,6 +38,15 @@ run_case() { CASE_RC=$? } +run_report_case() { + run_case --require-test-report "${TMP_DIR}/tests.xml" "$@" +} + +run_case_with_timeout() { + CASE_OUTPUT=$(timeout 2 "${VALIDATOR}" "$@" 2>&1) + CASE_RC=$? +} + assert_success() { [[ "${CASE_RC}" -eq 0 ]] || fail "$1 returned ${CASE_RC}" } @@ -54,14 +63,37 @@ if [[ ! -x "${VALIDATOR}" ]]; then fail "validator not found or not executable at ${VALIDATOR}" fi +cat > "${TMP_DIR}/tests.xml" <<'EOF' + + +EOF + +cat > "${TMP_DIR}/zero-tests.xml" <<'EOF' + + +EOF + echo "JaCoCo report validator tests" -run_case --require-session suite-a "${TMP_DIR}/missing.xml" hg-pd-client +run_case_with_timeout --require-session +[[ "${CASE_RC}" -ne 124 ]] || fail "missing session value timed out" +assert_failure "missing session value" +assert_output "--require-session requires a non-empty value" + +run_case --require-session "" +assert_failure "empty session value" +assert_output "--require-session requires a non-empty value" + +run_case --require-test-report +assert_failure "missing test report value" +assert_output "--require-test-report requires a non-empty value" + +run_report_case --require-session suite-a "${TMP_DIR}/missing.xml" hg-pd-client assert_failure "missing report" assert_output "not found or empty" touch "${TMP_DIR}/empty.xml" -run_case --require-session suite-a "${TMP_DIR}/empty.xml" hg-pd-client +run_report_case --require-session suite-a "${TMP_DIR}/empty.xml" hg-pd-client assert_failure "empty report" assert_output "not found or empty" @@ -76,29 +108,52 @@ cat > "${TMP_DIR}/valid.xml" <<'EOF' EOF -run_case --require-session suite-a --require-session suite-b \ - "${TMP_DIR}/valid.xml" hg-pd-client hg-pd-core +run_report_case --require-session suite-a --require-session suite-b \ + "${TMP_DIR}/valid.xml" hg-pd-client hg-pd-core assert_success "complete report" assert_output "contains all expected modules" -run_case --require-session suite-a --require-session suite-c \ +run_case --require-session suite-a --require-session suite-b \ + "${TMP_DIR}/valid.xml" hg-pd-client hg-pd-core +assert_failure "report without required test reports" +assert_output "at least one --require-test-report is required" + +run_case --require-test-report "${TMP_DIR}/missing-tests.xml" \ + --require-session suite-a --require-session suite-b \ + "${TMP_DIR}/valid.xml" hg-pd-client hg-pd-core +assert_failure "missing required test report" +assert_output "Surefire report not found or empty" + +run_case --require-test-report "${TMP_DIR}/zero-tests.xml" \ + --require-session suite-a --require-session suite-b \ "${TMP_DIR}/valid.xml" hg-pd-client hg-pd-core +assert_failure "required test report without tests" +assert_output "Surefire report has no tests" + +run_report_case --require-session suite-a --require-session suite-b \ + "${TMP_DIR}/valid.xml" +assert_failure "report without expected modules" +assert_output "at least one expected module is required" + +run_report_case --require-session suite-a --require-session suite-c \ + "${TMP_DIR}/valid.xml" hg-pd-client hg-pd-core assert_failure "report missing a required session" assert_output "missing JaCoCo session 'suite-c'" sed 's/covered="3"/covered="0"/' "${TMP_DIR}/valid.xml" > "${TMP_DIR}/uncovered.xml" -run_case --require-session suite-a --require-session suite-b \ - "${TMP_DIR}/uncovered.xml" hg-pd-client hg-pd-core +run_report_case --require-session suite-a --require-session suite-b \ + "${TMP_DIR}/uncovered.xml" hg-pd-client hg-pd-core assert_failure "report without covered instructions" assert_output "has no covered instructions" -run_case --require-session suite-a --require-session suite-b \ - "${TMP_DIR}/valid.xml" hg-pd-client hg-pd-service +run_report_case --require-session suite-a --require-session suite-b \ + "${TMP_DIR}/valid.xml" hg-pd-client hg-pd-service assert_failure "report missing an expected module" assert_output "missing JaCoCo group 'hg-pd-service'" python3 - "${REPO_ROOT}" <<'PY' || fail "aggregation configuration contract failed" from pathlib import Path +import re import sys import xml.etree.ElementTree as ET @@ -176,6 +231,25 @@ def assert_order(job, commands): assert positions == sorted(positions) +def validation_command(job): + return job.split("$TRAVIS_DIR/check-jacoco-report.sh", 1)[1].split( + "- name: Upload coverage", 1)[0] + + +def required_test_reports(job): + return set(re.findall(r"TEST-[A-Za-z0-9_.]+SuiteTest[.]xml", + validation_command(job))) + + +def required_modules(job): + command = validation_command(job).split('"$REPORT_FILE"', 1)[1] + return set(re.findall(r"\bhg-(?:pd|store)-[a-z0-9-]+\b", command)) + + +def selected_profiles(job, prefix): + return set(re.findall(r"-P (" + prefix + r"-[a-z0-9-]+-test)\b", job)) + + assert_order(pd_job, [ "mvn clean package", "mvn editorconfig:check -pl hugegraph-pd/hg-pd-test -am -ntp", @@ -193,19 +267,29 @@ assert "files: ${{ env.REPORT_FILE }}" in pd_job assert "\n directory:" not in pd_job assert "mvn verify -pl hugegraph-pd/hg-pd-test -am -P jacoco \\ " \ "-DskipTests -Deditorconfig.skip=true -ntp" in " ".join(pd_job.split()) +assert selected_profiles(pd_job, "pd") == { + "pd-common-test", "pd-core-test", "pd-client-test", "pd-rest-test", +} +assert required_test_reports(pd_job) == { + "TEST-org.apache.hugegraph.pd.common.CommonSuiteTest.xml", + "TEST-org.apache.hugegraph.pd.core.PDCoreSuiteTest.xml", + "TEST-org.apache.hugegraph.pd.client.PDClientSuiteTest.xml", + "TEST-org.apache.hugegraph.pd.rest.PDRestSuiteTest.xml", +} +assert required_modules(pd_job) == { + "hg-pd-grpc", "hg-pd-common", "hg-pd-client", "hg-pd-core", + "hg-pd-service", "hg-pd-dist", +} assert_order(store_job, [ "mvn clean package", "mvn editorconfig:check -pl hugegraph-store/hg-store-test -am -ntp", "-P store-common-test -Djacoco.sessionId=store-common-test", "-P store-client-test -Djacoco.sessionId=store-client-test", - "-P store-core-test -Djacoco.sessionId=store-core-test", "-P store-rocksdb-test -Djacoco.sessionId=store-rocksdb-test", - "-P store-server-test -Djacoco.sessionId=store-server-test", "-P store-raftcore-test -Djacoco.sessionId=store-raftcore-test", "mvn verify", "--require-session store-common-test", - "--require-session store-client-test", "--require-session store-core-test", - "--require-session store-rocksdb-test", "--require-session store-server-test", + "--require-session store-client-test", "--require-session store-rocksdb-test", "--require-session store-raftcore-test", "codecov/codecov-action", ]) assert store_job.count("mvn clean") == 1 @@ -214,6 +298,20 @@ assert "files: ${{ env.REPORT_FILE }}" in store_job assert "\n directory:" not in store_job assert "mvn verify -pl hugegraph-store/hg-store-test -am -P jacoco \\ " \ "-DskipTests -Deditorconfig.skip=true -ntp" in " ".join(store_job.split()) +assert selected_profiles(store_job, "store") == { + "store-common-test", "store-client-test", "store-rocksdb-test", + "store-raftcore-test", +} +assert required_test_reports(store_job) == { + "TEST-org.apache.hugegraph.store.common.CommonSuiteTest.xml", + "TEST-org.apache.hugegraph.store.client.ClientSuiteTest.xml", + "TEST-org.apache.hugegraph.store.rocksdb.RocksDbSuiteTest.xml", + "TEST-org.apache.hugegraph.store.raftcore.RaftSuiteTest.xml", +} +assert required_modules(store_job) == { + "hg-store-grpc", "hg-store-common", "hg-store-client", + "hg-store-rocksdb", "hg-store-core", "hg-store-node", +} print("PASS: JaCoCo aggregation configuration contract") PY