This is an automated email from the ASF dual-hosted git repository.

imbajin pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/hugegraph.git


The following commit(s) were added to refs/heads/master by this push:
     new 5db70c02f chore(ci): fix PD and Store coverage aggregation (#3161)
5db70c02f is described below

commit 5db70c02f3b0f907b3a0a04bf2e226c79b75680d
Author: contrueCT <[email protected]>
AuthorDate: Tue Aug 25 01:03:59 2026 +0800

    chore(ci): fix PD and Store coverage aggregation (#3161)
    
    PD and Store CI run several Maven test profiles in separate invocations, but
    each invocation previously replaced JaCoCo execution data and generated a
    partial report. PD also ran mvn clean package after two coverage-producing
    test profiles, deleting their data before upload. As a result, Codecov could
    receive only the last profile's coverage or an imprecisely selected report.
    
    The existing JaCoCo 0.8.4 configuration also cannot instrument Java 17 class
    files, which blocks the project's planned Java 17 migration.
---
 .github/workflows/pd-store-ci.yml                  | 117 ++++-
 hugegraph-pd/hg-pd-test/pom.xml                    |  37 +-
 hugegraph-pd/pom.xml                               |   3 +-
 .../src/assembly/travis/check-jacoco-report.sh     | 210 +++++++++
 .../assembly/travis/test-check-jacoco-report.sh    | 522 +++++++++++++++++++++
 hugegraph-store/hg-store-test/pom.xml              |  35 +-
 hugegraph-store/pom.xml                            |   5 +-
 7 files changed, 851 insertions(+), 78 deletions(-)

diff --git a/.github/workflows/pd-store-ci.yml 
b/.github/workflows/pd-store-ci.yml
index 6f670e1cb..2ad45bd64 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,8 @@ 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
+      TEST_REPORT_DIR: hugegraph-pd/hg-pd-test/target/surefire-reports
 
     steps:
       - name: Install JDK 11
@@ -92,20 +96,26 @@ 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
+      - name: Check source formatting
         run: |
-          mvn test -pl hugegraph-pd/hg-pd-test -am -P pd-core-test
+          mvn editorconfig:check -pl hugegraph-pd/hg-pd-test -am -ntp
 
-      # The above tests do not require starting a PD instance.
+      # 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-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 +149,44 @@ 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 -Deditorconfig.skip=true -ntp
+
+      - 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-covered-group hg-pd-common \
+            --require-covered-group hg-pd-client \
+            --require-covered-group hg-pd-core \
+            --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/[email protected]
         with:
-          file: ${{ env.REPORT_DIR }}/*.xml
+          files: ${{ env.REPORT_FILE }}
 
   store:
     needs: struct
@@ -157,7 +195,8 @@ 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
+      TEST_REPORT_DIR: hugegraph-store/hg-store-test/target/surefire-reports
 
     steps:
       - name: Install JDK 11
@@ -189,6 +228,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: |
@@ -228,32 +271,54 @@ 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
-
-      - 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-client-test -Djacoco.sessionId=store-client-test
 
       - name: Run rocksdb test
         run: |
-          mvn test -pl hugegraph-store/hg-store-test -am -P 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-rocksdb-test -Djacoco.sessionId=store-rocksdb-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 -Deditorconfig.skip=true -ntp
+
+      - 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-covered-group hg-store-common \
+            --require-covered-group hg-store-client \
+            --require-covered-group hg-store-rocksdb \
+            --require-session store-common-test \
+            --require-session store-client-test \
+            --require-session store-rocksdb-test \
+            --require-session store-raftcore-test \
+            "$REPORT_FILE" \
+            hg-store-grpc hg-store-common hg-store-client hg-store-rocksdb
 
       - name: Upload coverage to Codecov
         uses: codecov/[email protected]
         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 44d5d084c..72cdef81a 100644
--- a/hugegraph-pd/hg-pd-test/pom.xml
+++ b/hugegraph-pd/hg-pd-test/pom.xml
@@ -46,18 +46,19 @@
                     <plugin>
                         <groupId>org.jacoco</groupId>
                         <artifactId>jacoco-maven-plugin</artifactId>
-                        <version>0.8.4</version>
-                        <configuration>
-                            <excludes>
-                                <exclude>**/grpc/**.*</exclude>
-                                <exclude>**/config/**.*</exclude>
-                            </excludes>
-                        </configuration>
+                        <version>0.8.8</version>
                         <executions>
                             <execution>
+                                <id>coverage-report</id>
+                                <phase>verify</phase>
                                 <goals>
-                                    <goal>prepare-agent</goal>
+                                    <goal>report-aggregate</goal>
                                 </goals>
+                                <configuration>
+                                    <outputDirectory>
+                                        ${project.basedir}/target/site/jacoco
+                                    </outputDirectory>
+                                </configuration>
                             </execution>
                         </executions>
                     </plugin>
@@ -258,25 +259,7 @@
             <plugin>
                 <groupId>org.jacoco</groupId>
                 <artifactId>jacoco-maven-plugin</artifactId>
-                <version>0.8.4</version>
-                <executions>
-                    <execution>
-                        <id>pre-test</id>
-                        <goals>
-                            <goal>prepare-agent</goal>
-                        </goals>
-                    </execution>
-                    <execution>
-                        <id>post-test</id>
-                        <phase>test</phase>
-                        <goals>
-                            <goal>report-aggregate</goal>
-                        </goals>
-                        <configuration>
-                            
<outputDirectory>${basedir}/target/site/jacoco</outputDirectory>
-                        </configuration>
-                    </execution>
-                </executions>
+                <version>0.8.8</version>
                 <configuration>
                     <excludes>
                         <exclude>org/apache/hugegraph/pd/rest/*.class</exclude>
diff --git a/hugegraph-pd/pom.xml b/hugegraph-pd/pom.xml
index ceb8af33b..83b39c837 100644
--- a/hugegraph-pd/pom.xml
+++ b/hugegraph-pd/pom.xml
@@ -74,8 +74,9 @@
             <plugin>
                 <groupId>org.jacoco</groupId>
                 <artifactId>jacoco-maven-plugin</artifactId>
-                <version>0.8.4</version>
+                <version>0.8.8</version>
                 <configuration>
+                    <append>true</append>
                     <excludes>
                         <exclude>**/grpc/**.*</exclude>
                         <exclude>**/config/**.*</exclude>
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 000000000..a0d674f15
--- /dev/null
+++ b/hugegraph-server/hugegraph-dist/src/assembly/travis/check-jacoco-report.sh
@@ -0,0 +1,210 @@
+#!/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=()
+REQUIRED_TEST_REPORTS=()
+REQUIRED_COVERED_GROUPS=()
+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
+            ;;
+        --require-covered-group)
+            if (( $# < 2 )) || [[ -z "${2:-}" || "${2}" == --* ]]; then
+                echo "ERROR: --require-covered-group requires a non-empty 
value" >&2
+                exit 1
+            fi
+            REQUIRED_COVERED_GROUPS+=("${2}")
+            shift 2
+            ;;
+        --*)
+            echo "ERROR: unknown option: ${1}" >&2
+            exit 1
+            ;;
+        *)
+            break
+            ;;
+    esac
+done
+
+if (( ${#REQUIRED_SESSIONS[@]} == 0 )); then
+    echo "ERROR: at least one --require-session is required" >&2
+    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:-}"
+if (( $# > 0 )); then
+    shift
+fi
+
+if [[ -z "${REPORT_FILE}" || ! -s "${REPORT_FILE}" ]]; then
+    echo "ERROR: JaCoCo report not found or empty: ${REPORT_FILE:-<unset>}" >&2
+    exit 1
+fi
+
+if (( $# == 0 )); then
+    echo "ERROR: at least one expected module is required" >&2
+    exit 1
+fi
+
+validate_test_report() {
+    local test_report="${1}"
+
+    if [[ ! -s "${test_report}" ]]; then
+        echo "ERROR: Surefire report not found or empty: ${test_report}" >&2
+        return 1
+    fi
+
+    local test_counts
+    if ! test_counts=$(python3 - "${test_report}" <<'PY'
+import sys
+import xml.etree.ElementTree as ET
+
+root = ET.parse(sys.argv[1]).getroot()
+if root.tag.rsplit("}", 1)[-1] != "testsuite" or "tests" not in root.attrib:
+    raise ValueError("not a Surefire testsuite report")
+test_count = int(root.attrib["tests"])
+skipped_count = int(root.attrib.get("skipped", "0"))
+if test_count < 0:
+    raise ValueError("negative Surefire test count")
+if skipped_count < 0 or skipped_count > test_count:
+    raise ValueError("invalid Surefire skipped count")
+print(test_count, test_count - skipped_count)
+PY
+    ); then
+        echo "ERROR: unable to parse Surefire report: ${test_report}" >&2
+        return 1
+    fi
+    local test_count
+    local executed_count
+    read -r test_count executed_count <<< "${test_counts}"
+    if (( test_count <= 0 )); then
+        echo "ERROR: Surefire report has no tests: ${test_report}" >&2
+        return 1
+    fi
+    if (( executed_count <= 0 )); then
+        echo "ERROR: Surefire report has no executed tests: ${test_report}" >&2
+        return 1
+    fi
+}
+
+for test_report in "${REQUIRED_TEST_REPORTS[@]}"; do
+    validate_test_report "${test_report}" || exit 1
+done
+
+python3 - "${REPORT_FILE}" "${REQUIRED_SESSIONS[@]}" -- \
+    ${REQUIRED_COVERED_GROUPS[@]+"${REQUIRED_COVERED_GROUPS[@]}"} \
+    -- "$@" <<'PY' || exit 1
+import sys
+import xml.etree.ElementTree as ET
+
+report_file = sys.argv[1]
+session_separator = sys.argv.index("--", 2)
+group_separator = sys.argv.index("--", session_separator + 1)
+required_sessions = sys.argv[2:session_separator]
+required_covered_groups = sys.argv[session_separator + 1:group_separator]
+required_modules = sys.argv[group_separator + 1:]
+
+
+def fail(message):
+    print(f"ERROR: {message}", file=sys.stderr)
+    sys.exit(1)
+
+
+def local_name(tag):
+    return tag.rsplit("}", 1)[-1]
+
+
+try:
+    root = ET.parse(report_file).getroot()
+except (OSError, ET.ParseError) as error:
+    fail(f"unable to parse JaCoCo report: {report_file}: {error}")
+
+if local_name(root.tag) != "report":
+    fail(f"unable to parse JaCoCo report: {report_file}: expected report root")
+
+children = list(root)
+instruction_counters = [
+    element for element in children
+    if local_name(element.tag) == "counter" and
+    element.attrib.get("type") == "INSTRUCTION"
+]
+try:
+    has_coverage = any(int(counter.attrib.get("covered", "0")) > 0
+                       for counter in instruction_counters)
+except ValueError as error:
+    fail(f"unable to parse JaCoCo report: {report_file}: {error}")
+if not has_coverage:
+    fail(f"JaCoCo report has no covered instructions: {report_file}")
+
+session_ids = {
+    element.attrib.get("id") for element in children
+    if local_name(element.tag) == "sessioninfo"
+}
+for session in required_sessions:
+    if session not in session_ids:
+        fail(f"missing JaCoCo session '{session}' in {report_file}")
+
+groups_by_name = {
+    element.attrib.get("name"): element for element in children
+    if local_name(element.tag) == "group"
+}
+for module in required_modules:
+    if module not in groups_by_name:
+        fail(f"missing JaCoCo group '{module}' in {report_file}")
+
+for group_name in required_covered_groups:
+    group = groups_by_name.get(group_name)
+    if group is None:
+        fail(f"missing JaCoCo group '{group_name}' in {report_file}")
+    counters = [
+        element for element in list(group)
+        if local_name(element.tag) == "counter" and
+        element.attrib.get("type") == "INSTRUCTION"
+    ]
+    try:
+        has_coverage = any(int(counter.attrib.get("covered", "0")) > 0
+                           for counter in counters)
+    except ValueError as error:
+        fail(f"unable to parse JaCoCo report: {report_file}: {error}")
+    if not has_coverage:
+        fail(f"JaCoCo group '{group_name}' has no covered instructions: "
+             f"{report_file}")
+PY
+
+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 000000000..bdb09ba16
--- /dev/null
+++ 
b/hugegraph-server/hugegraph-dist/src/assembly/travis/test-check-jacoco-report.sh
@@ -0,0 +1,522 @@
+#!/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=$?
+}
+
+run_report_case() {
+    run_case --require-test-report "${TMP_DIR}/tests.xml" "$@"
+}
+
+run_case_with_timeout() {
+    CASE_OUTPUT=$(python3 - "${VALIDATOR}" "$@" <<'PY'
+import os
+import signal
+import subprocess
+import sys
+
+process = subprocess.Popen(
+    sys.argv[1:],
+    stdout=subprocess.PIPE,
+    stderr=subprocess.STDOUT,
+    universal_newlines=True,
+    start_new_session=True,
+)
+try:
+    output, _ = process.communicate(timeout=2)
+except subprocess.TimeoutExpired:
+    try:
+        os.killpg(process.pid, signal.SIGTERM)
+    except ProcessLookupError:
+        pass
+    try:
+        output, _ = process.communicate(timeout=1)
+    except subprocess.TimeoutExpired:
+        try:
+            os.killpg(process.pid, signal.SIGKILL)
+        except ProcessLookupError:
+            pass
+        output, _ = process.communicate()
+    sys.stdout.write(output)
+    sys.exit(124)
+
+sys.stdout.write(output)
+sys.exit(process.returncode)
+PY
+    )
+    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
+
+cat > "${TMP_DIR}/tests.xml" <<'EOF'
+<?xml version="1.0" encoding="UTF-8"?>
+<testsuite name="SuiteTest" tests="2" failures="0" errors="0" skipped="0"/>
+EOF
+
+cat > "${TMP_DIR}/zero-tests.xml" <<'EOF'
+<?xml version="1.0" encoding="UTF-8"?>
+<testsuite name="EmptySuiteTest" tests="0" failures="0" errors="0" 
skipped="0"/>
+EOF
+
+cat > "${TMP_DIR}/all-skipped.xml" <<'EOF'
+<?xml version="1.0" encoding="UTF-8"?>
+<testsuite name="AllSkippedSuiteTest" tests="2" failures="0" errors="0" 
skipped="2"/>
+EOF
+
+cat > "${TMP_DIR}/not-surefire.xml" <<'EOF'
+<?xml version="1.0" encoding="UTF-8"?>
+<report tests="0"/>
+EOF
+
+cat > "${TMP_DIR}/hanging-validator.sh" <<'EOF'
+#!/bin/bash
+sleep 30 >/dev/null 2>&1 &
+child_pid=$!
+printf '%s\n' "${child_pid}" > "${1}"
+wait "${child_pid}"
+EOF
+chmod +x "${TMP_DIR}/hanging-validator.sh"
+
+echo "JaCoCo report validator tests"
+
+# Simulate stock macOS, where GNU timeout is not installed by default.
+timeout() {
+    return 127
+}
+run_case_with_timeout --require-session
+unset -f timeout
+[[ "${CASE_RC}" -ne 124 ]] || fail "missing session value timed out"
+assert_failure "missing session value"
+assert_output "--require-session requires a non-empty value"
+
+REAL_VALIDATOR="${VALIDATOR}"
+VALIDATOR="${TMP_DIR}/hanging-validator.sh"
+run_case_with_timeout "${TMP_DIR}/hanging-child.pid"
+VALIDATOR="${REAL_VALIDATOR}"
+[[ "${CASE_RC}" -eq 124 ]] || fail "hanging validator returned ${CASE_RC}"
+child_pid=$(cat "${TMP_DIR}/hanging-child.pid")
+if ! python3 - "${child_pid}" <<'PY'
+import os
+import sys
+import time
+
+pid = int(sys.argv[1])
+for _ in range(20):
+    try:
+        os.kill(pid, 0)
+    except ProcessLookupError:
+        sys.exit(0)
+    time.sleep(0.05)
+sys.exit(1)
+PY
+then
+    kill "${child_pid}" 2>/dev/null || true
+    fail "timed-out validator left child process ${child_pid} running"
+fi
+
+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_case --require-covered-group
+assert_failure "missing covered group value"
+assert_output "--require-covered-group requires a non-empty value"
+
+run_case --require-covered-group ""
+assert_failure "empty covered group value"
+assert_output "--require-covered-group requires a non-empty value"
+
+run_case --require-suite-report
+assert_failure "removed suite report option"
+assert_output "unknown option: --require-suite-report"
+
+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_report_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'
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<report name="hg-pd-test">
+  <sessioninfo id="suite-a" start="1" dump="2"/>
+  <sessioninfo id="suite-b" start="3" dump="4"/>
+  <group name="hg-pd-client"/>
+  <group name="hg-pd-core"/>
+  <counter type="INSTRUCTION" missed="7" covered="3"/>
+</report>
+EOF
+
+cat > "${TMP_DIR}/comment-only.xml" <<'EOF'
+<?xml version="1.0" encoding="UTF-8"?>
+<report name="comment-only">
+  <!-- <sessioninfo id="suite-a" start="1" dump="2"/> -->
+  <!-- <sessioninfo id="suite-b" start="3" dump="4"/> -->
+  <!-- <group name="hg-pd-client"/> -->
+  <!-- <group name="hg-pd-core"/> -->
+  <!-- <counter type="INSTRUCTION" missed="7" covered="3"/> -->
+</report>
+EOF
+
+cat > "${TMP_DIR}/reordered.xml" <<'EOF'
+<?xml version="1.0" encoding="UTF-8"?>
+<report name="reordered">
+  <sessioninfo start="1" dump="2" id="suite-a"/>
+  <sessioninfo dump="4" id="suite-b" start="3"/>
+  <group name="hg-pd-client"/>
+  <group name="hg-pd-core"/>
+  <counter covered="3" type="INSTRUCTION" missed="7"/>
+</report>
+EOF
+
+cat > "${TMP_DIR}/truncated.xml" <<'EOF'
+<?xml version="1.0" encoding="UTF-8"?>
+<report name="truncated">
+  <sessioninfo id="suite-a" start="1" dump="2"/>
+  <sessioninfo id="suite-b" start="3" dump="4"/>
+  <group name="hg-pd-client"/>
+  <group name="hg-pd-core"/>
+  <counter type="INSTRUCTION" missed="7" covered="3"/>
+EOF
+
+cat > "${TMP_DIR}/partial-group-coverage.xml" <<'EOF'
+<?xml version="1.0" encoding="UTF-8"?>
+<report name="partial-group-coverage">
+  <sessioninfo id="suite-a" start="1" dump="2"/>
+  <sessioninfo id="suite-b" start="3" dump="4"/>
+  <group name="hg-pd-core">
+    <counter type="INSTRUCTION" missed="10" covered="0"/>
+  </group>
+  <group name="hg-pd-service">
+    <counter type="INSTRUCTION" missed="2" covered="5"/>
+  </group>
+  <counter type="INSTRUCTION" missed="12" covered="5"/>
+</report>
+EOF
+
+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_report_case --require-session suite-a --require-session suite-b \
+                "${TMP_DIR}/truncated.xml" hg-pd-client hg-pd-core
+assert_failure "truncated JaCoCo report"
+assert_output "unable to parse JaCoCo report"
+
+run_report_case --require-session suite-a --require-session suite-b \
+                "${TMP_DIR}/reordered.xml" hg-pd-client hg-pd-core
+assert_success "report with reordered XML attributes"
+
+run_report_case --require-session suite-a --require-session suite-b \
+                "${TMP_DIR}/comment-only.xml" hg-pd-client hg-pd-core
+assert_failure "report with evidence only in XML comments"
+assert_output "has no covered instructions"
+
+run_report_case --require-session suite-a --require-session suite-b \
+                "${TMP_DIR}/partial-group-coverage.xml" \
+                hg-pd-core hg-pd-service
+assert_success "presence-only groups with partial coverage"
+
+run_report_case --require-covered-group hg-pd-service \
+                --require-session suite-a --require-session suite-b \
+                "${TMP_DIR}/partial-group-coverage.xml" \
+                hg-pd-core hg-pd-service
+assert_success "required group with covered instructions"
+
+run_report_case --require-covered-group hg-pd-core \
+                --require-covered-group hg-pd-service \
+                --require-session suite-a --require-session suite-b \
+                "${TMP_DIR}/partial-group-coverage.xml" \
+                hg-pd-core hg-pd-service
+assert_failure "required group without covered instructions"
+assert_output "JaCoCo group 'hg-pd-core' has no covered instructions"
+
+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_case --require-test-report "${TMP_DIR}/all-skipped.xml" \
+         --require-session suite-a --require-session suite-b \
+         "${TMP_DIR}/valid.xml" hg-pd-client hg-pd-core
+assert_failure "required all-skipped test report"
+assert_output "Surefire report has no executed tests"
+
+run_case --require-test-report "${TMP_DIR}/not-surefire.xml" \
+         --require-session suite-a --require-session suite-b \
+         "${TMP_DIR}/valid.xml" hg-pd-client hg-pd-core
+assert_failure "required test report with invalid root"
+assert_output "unable to parse Surefire report"
+
+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_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_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
+
+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 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 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]
+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)
+
+
+def validation_command(job):
+    return job.split("$TRAVIS_DIR/check-jacoco-report.sh", 1)[1].split(
+        "- name: Upload coverage", 1)[0]
+
+
+def reports_for_option(job, option):
+    pattern = re.escape(option) + (
+        r'\s+\\?\s*"\$TEST_REPORT_DIR/'
+        r'(TEST-[A-Za-z0-9_.]+SuiteTest[.]xml)"'
+    )
+    return set(re.findall(pattern, validation_command(job)))
+
+
+def values_for_option(job, option):
+    pattern = re.escape(option) + r"\s+([A-Za-z0-9_-]+)"
+    return set(re.findall(pattern, 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",
+    "-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 "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 reports_for_option(pd_job, "--require-test-report") == {
+    "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 not reports_for_option(pd_job, "--require-suite-report")
+assert values_for_option(pd_job, "--require-covered-group") == {
+    "hg-pd-common", "hg-pd-client", "hg-pd-core",
+}
+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-rocksdb-test -Djacoco.sessionId=store-rocksdb-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-rocksdb-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
+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 reports_for_option(store_job, "--require-test-report") == {
+    "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 not reports_for_option(store_job, "--require-suite-report")
+assert values_for_option(store_job, "--require-covered-group") == {
+    "hg-store-common", "hg-store-client", "hg-store-rocksdb",
+}
+assert required_modules(store_job) == {
+    "hg-store-grpc", "hg-store-common", "hg-store-client",
+    "hg-store-rocksdb",
+}
+
+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 36308f449..8b7f10dd6 100644
--- a/hugegraph-store/hg-store-test/pom.xml
+++ b/hugegraph-store/hg-store-test/pom.xml
@@ -39,22 +39,30 @@
             <activation>
                 <activeByDefault>false</activeByDefault>
             </activation>
+            <dependencies>
+                <dependency>
+                    <groupId>org.apache.hugegraph</groupId>
+                    <artifactId>hg-store-rocksdb</artifactId>
+                    <version>${revision}</version>
+                </dependency>
+            </dependencies>
             <build>
                 <plugins>
                     <plugin>
                         <groupId>org.jacoco</groupId>
                         <artifactId>jacoco-maven-plugin</artifactId>
-                        <version>0.8.4</version>
+                        <version>0.8.8</version>
 
                         <executions>
                             <execution>
-                                <id>default</id>
+                                <id>coverage-report</id>
                                 <phase>verify</phase>
                                 <goals>
                                     <goal>report-aggregate</goal>
                                 </goals>
                                 <configuration>
-                                    
<outputDirectory>${project.basedir}/../target/site/jacoco
+                                    <outputDirectory>
+                                        ${project.basedir}/target/site/jacoco
                                     </outputDirectory>
                                 </configuration>
                             </execution>
@@ -295,26 +303,7 @@
             <plugin>
                 <groupId>org.jacoco</groupId>
                 <artifactId>jacoco-maven-plugin</artifactId>
-                <version>0.8.4</version>
-                <executions>
-                    <execution>
-                        <id>pre-test</id>
-
-                        <goals>
-                            <goal>prepare-agent</goal>
-                        </goals>
-                    </execution>
-                    <execution>
-                        <id>post-test</id>
-                        <phase>test</phase>
-                        <goals>
-                            <goal>report-aggregate</goal>
-                        </goals>
-                        <configuration>
-                            
<outputDirectory>${basedir}/target/site/jacoco</outputDirectory>
-                        </configuration>
-                    </execution>
-                </executions>
+                <version>0.8.8</version>
                 <configuration>
                     <excludes>
                         <exclude>**/grpc/**/*</exclude>
diff --git a/hugegraph-store/pom.xml b/hugegraph-store/pom.xml
index 9ff1e933e..a8212b70d 100644
--- a/hugegraph-store/pom.xml
+++ b/hugegraph-store/pom.xml
@@ -98,7 +98,10 @@
             <plugin>
                 <groupId>org.jacoco</groupId>
                 <artifactId>jacoco-maven-plugin</artifactId>
-                <version>0.8.4</version>
+                <version>0.8.8</version>
+                <configuration>
+                    <append>true</append>
+                </configuration>
                 <executions>
                     <execution>
                         <goals>

Reply via email to