From 404b1fec8e350a9c65944199ebcd9fb3e6f98aa6 Mon Sep 17 00:00:00 2001 From: imbajin Date: Tue, 13 Oct 2026 17:25:41 +0800 Subject: [PATCH 1/5] fix: align Java 17 runtime compatibility - share component module options across launchers and tests - reject swallowed Java compatibility errors in positive CI runs - preserve cluster logs before cleanup and stop all nodes on failure - cover custom options and document runtime validation --- .github/workflows/cluster-test-ci.yml | 27 +++- .github/workflows/commons-ci.yml | 11 +- .github/workflows/pd-store-ci.yml | 109 +++++++++++++--- .github/workflows/server-tests.yml | 47 +++++-- docs/ci.md | 21 +++ docs/server-runtime.md | 15 +++ .../apache/hugegraph/ct/env/AbstractEnv.java | 27 +++- .../ct/node/AbstractNodeWrapper.java | 61 +++++++-- .../hugegraph/ct/node/PDNodeWrapper.java | 5 + .../hugegraph/ct/node/ServerNodeWrapper.java | 4 +- .../hugegraph/ct/node/StoreNodeWrapper.java | 5 + .../hugegraph/ct/env/AbstractEnvTest.java | 68 ++++++++++ .../ct/node/AbstractNodeWrapperTest.java | 110 ++++++++++++++++ .../hugegraph/ct/node/ModuleOptionsTest.java | 120 ++++++++++++++++++ .../BaseMultiClusterTest.java | 9 +- .../SimpleClusterTest/BaseSimpleTest.java | 13 +- hugegraph-pd/README.md | 4 + .../assembly/static/bin/jvm-module.options | 19 +++ .../assembly/static/bin/start-hugegraph-pd.sh | 14 +- hugegraph-pd/hg-pd-test/pom.xml | 6 + .../src/assembly/static/bin/dump-store.sh | 9 +- .../assembly/static/bin/gremlin-console.sh | 7 +- .../travis/check-java-runtime-logs.sh | 76 +++++++++++ .../assembly/travis/run-api-test-for-raft.sh | 3 + .../src/assembly/travis/run-api-test.sh | 3 + .../travis/run-gremlin-console-smoke-test.sh | 8 +- .../travis/run-native-runtime-smoke-test.sh | 28 ++-- .../travis/test-check-java-runtime-logs.sh | 77 +++++++++++ .../travis/test-java-security-properties.sh | 2 +- .../travis/test-java17-upgrade-contracts.sh | 49 ++++++- .../travis/test-start-hugegraph-pd.sh | 73 +++++++++-- .../travis/test-start-hugegraph-store.sh | 73 +++++++++-- .../assembly/travis/test-start-hugegraph.sh | 40 +++++- hugegraph-store/README.md | 4 + .../assembly/static/bin/jvm-module.options | 19 +++ .../static/bin/start-hugegraph-store.sh | 14 +- .../assembly/travis/test-store-preloads.sh | 2 +- hugegraph-store/hg-store-test/pom.xml | 6 + 38 files changed, 1063 insertions(+), 125 deletions(-) create mode 100644 hugegraph-cluster-test/hugegraph-clustertest-minicluster/src/test/java/org/apache/hugegraph/ct/env/AbstractEnvTest.java create mode 100644 hugegraph-cluster-test/hugegraph-clustertest-minicluster/src/test/java/org/apache/hugegraph/ct/node/ModuleOptionsTest.java create mode 100644 hugegraph-pd/hg-pd-dist/src/assembly/static/bin/jvm-module.options create mode 100755 hugegraph-server/hugegraph-dist/src/assembly/travis/check-java-runtime-logs.sh create mode 100755 hugegraph-server/hugegraph-dist/src/assembly/travis/test-check-java-runtime-logs.sh create mode 100644 hugegraph-store/hg-store-dist/src/assembly/static/bin/jvm-module.options diff --git a/.github/workflows/cluster-test-ci.yml b/.github/workflows/cluster-test-ci.yml index 29c7c5bf66..c1cb8f13d6 100644 --- a/.github/workflows/cluster-test-ci.yml +++ b/.github/workflows/cluster-test-ci.yml @@ -39,13 +39,33 @@ jobs: - name: Run simple cluster test run: | + set -o pipefail + suite_log="$RUNNER_TEMP/simple-cluster-test.log" + node_logs="$RUNNER_TEMP/simple-cluster-node-logs" + test_status=0 timeout 45m mvn test -pl hugegraph-cluster-test/hugegraph-clustertest-test \ - -am -P simple-cluster-test + -am -P simple-cluster-test -Dhugegraph.ct.logArchive="$node_logs" \ + 2>&1 | tee "$suite_log" || test_status=$? + runtime_status=0 + hugegraph-server/hugegraph-dist/src/assembly/travis/check-java-runtime-logs.sh \ + "$suite_log" "$node_logs" || runtime_status=$? + if [[ "$test_status" -ne 0 ]]; then exit "$test_status"; fi + exit "$runtime_status" - name: Run multi cluster test run: | + set -o pipefail + suite_log="$RUNNER_TEMP/multi-cluster-test.log" + node_logs="$RUNNER_TEMP/multi-cluster-node-logs" + test_status=0 timeout 45m mvn test -pl hugegraph-cluster-test/hugegraph-clustertest-test \ - -am -P multi-cluster-test + -am -P multi-cluster-test -Dhugegraph.ct.logArchive="$node_logs" \ + 2>&1 | tee "$suite_log" || test_status=$? + runtime_status=0 + hugegraph-server/hugegraph-dist/src/assembly/travis/check-java-runtime-logs.sh \ + "$suite_log" "$node_logs" || runtime_status=$? + if [[ "$test_status" -ne 0 ]]; then exit "$test_status"; fi + exit "$runtime_status" - name: Show cluster diagnostics on failure if: failure() @@ -54,7 +74,8 @@ jobs: ps -ef | grep -E "HugeGraph|hg-|java" | grep -v grep || true echo "[ci] listening tcp ports:" (ss -ltnp || netstat -ltnp || true) 2>&1 - find hugegraph-cluster-test -path '*/logs/*' -type f | sort | while read -r log; do + find hugegraph-cluster-test "$RUNNER_TEMP/simple-cluster-node-logs" \ + "$RUNNER_TEMP/multi-cluster-node-logs" -type f -name '*.log' | sort | while read -r log; do echo "--- tail -n 200 $log ---" tail -n 200 "$log" || true done diff --git a/.github/workflows/commons-ci.yml b/.github/workflows/commons-ci.yml index d84c937af4..afb6945f06 100644 --- a/.github/workflows/commons-ci.yml +++ b/.github/workflows/commons-ci.yml @@ -17,6 +17,7 @@ jobs: runs-on: ubuntu-24.04 env: USE_STAGE: 'false' # Whether to include the stage repository. + TRAVIS_DIR: hugegraph-server/hugegraph-dist/src/assembly/travis strategy: fail-fast: false @@ -50,13 +51,19 @@ jobs: - name: Run common test run: | + set -o pipefail + RUNTIME_LOG=$(mktemp "$RUNNER_TEMP/java-runtime.XXXXXX") mvn test -pl hugegraph-commons/hugegraph-common -Dtest=UnitTestSuite \ - -DskipCommonsTests=false -Dsurefire.failIfNoSpecifiedTests=true + -DskipCommonsTests=false -Dsurefire.failIfNoSpecifiedTests=true 2>&1 | tee "$RUNTIME_LOG" + bash "$TRAVIS_DIR/check-java-runtime-logs.sh" "$RUNTIME_LOG" - name: Run rpc test run: | + set -o pipefail + RUNTIME_LOG=$(mktemp "$RUNNER_TEMP/java-runtime.XXXXXX") mvn test -pl hugegraph-commons/hugegraph-rpc -Dtest=UnitTestSuite \ - -DskipCommonsTests=false -Dsurefire.failIfNoSpecifiedTests=true + -DskipCommonsTests=false -Dsurefire.failIfNoSpecifiedTests=true 2>&1 | tee "$RUNTIME_LOG" + bash "$TRAVIS_DIR/check-java-runtime-logs.sh" "$RUNTIME_LOG" - name: Upload coverage to Codecov uses: codecov/codecov-action@v7 diff --git a/.github/workflows/pd-store-ci.yml b/.github/workflows/pd-store-ci.yml index 00adccfe64..5ea47cc470 100644 --- a/.github/workflows/pd-store-ci.yml +++ b/.github/workflows/pd-store-ci.yml @@ -80,6 +80,7 @@ jobs: runs-on: ubuntu-24.04 env: USE_STAGE: 'false' + TRAVIS_DIR: hugegraph-server/hugegraph-dist/src/assembly/travis steps: - name: Checkout uses: actions/checkout@v7 @@ -97,6 +98,9 @@ jobs: - name: Verify Store stop failure propagation and PID retention run: bash hugegraph-server/hugegraph-dist/src/assembly/travis/test-stop-hugegraph-store.sh + - name: Run Java runtime log checker tests + run: bash "$TRAVIS_DIR/test-check-java-runtime-logs.sh" + - name: Run JaCoCo report validator tests run: hugegraph-server/hugegraph-dist/src/assembly/travis/test-check-jacoco-report.sh @@ -129,7 +133,10 @@ jobs: - name: Run hugegraph-struct test if: ${{ hashFiles('hugegraph-struct/pom.xml') != '' }} run: | - mvn -U -ntp -pl hugegraph-struct test + set -o pipefail + RUNTIME_LOG=$(mktemp "$RUNNER_TEMP/java-runtime.XXXXXX") + mvn -U -ntp -pl hugegraph-struct test 2>&1 | tee "$RUNTIME_LOG" + bash "$TRAVIS_DIR/check-java-runtime-logs.sh" "$RUNTIME_LOG" pd: if: inputs.run-pd @@ -176,21 +183,30 @@ jobs: # clean package so their JaCoCo data survives until the final report. - name: Run common test run: | + set -o pipefail + RUNTIME_LOG=$(mktemp "$RUNNER_TEMP/java-runtime.XXXXXX") mvn test -pl hugegraph-pd/hg-pd-test -am \ - -P pd-common-test -Djacoco.sessionId=pd-common-test + -P pd-common-test -Djacoco.sessionId=pd-common-test 2>&1 | tee "$RUNTIME_LOG" + bash "$TRAVIS_DIR/check-java-runtime-logs.sh" "$RUNTIME_LOG" - name: Run core test run: | + set -o pipefail + RUNTIME_LOG=$(mktemp "$RUNNER_TEMP/java-runtime.XXXXXX") mvn test -pl hugegraph-pd/hg-pd-test -am \ - -P pd-core-test -Djacoco.sessionId=pd-core-test + -P pd-core-test -Djacoco.sessionId=pd-core-test 2>&1 | tee "$RUNTIME_LOG" + bash "$TRAVIS_DIR/check-java-runtime-logs.sh" "$RUNTIME_LOG" - name: Verify Surefire single-class routing run: | SUREFIRE_DIR=hugegraph-pd/hg-pd-test/target/surefire-reports REPORT="$SUREFIRE_DIR/TEST-org.apache.hugegraph.pd.core.PDConfigTest.xml" rm -f "$REPORT" + set -o pipefail + RUNTIME_LOG=$(mktemp "$RUNNER_TEMP/java-runtime.XXXXXX") mvn test -pl hugegraph-pd/hg-pd-test -am \ - -P pd-core-test -Dtest=PDConfigTest -ntp + -P pd-core-test -Dtest=PDConfigTest -ntp 2>&1 | tee "$RUNTIME_LOG" + bash "$TRAVIS_DIR/check-java-runtime-logs.sh" "$RUNTIME_LOG" test -s "$REPORT" || { echo "::error::PD single-class test produced no report" exit 1 @@ -254,13 +270,19 @@ jobs: - name: Run client test run: | + set -o pipefail + RUNTIME_LOG=$(mktemp "$RUNNER_TEMP/java-runtime.XXXXXX") mvn test -pl hugegraph-pd/hg-pd-test -am \ - -P pd-client-test -Djacoco.sessionId=pd-client-test + -P pd-client-test -Djacoco.sessionId=pd-client-test 2>&1 | tee "$RUNTIME_LOG" + bash "$TRAVIS_DIR/check-java-runtime-logs.sh" "$RUNTIME_LOG" - name: Run rest test run: | + set -o pipefail + RUNTIME_LOG=$(mktemp "$RUNNER_TEMP/java-runtime.XXXXXX") mvn test -pl hugegraph-pd/hg-pd-test -am \ - -P pd-rest-test -Djacoco.sessionId=pd-rest-test + -P pd-rest-test -Djacoco.sessionId=pd-rest-test 2>&1 | tee "$RUNTIME_LOG" + bash "$TRAVIS_DIR/check-java-runtime-logs.sh" "$RUNTIME_LOG" - name: Generate aggregate coverage report run: | @@ -354,8 +376,11 @@ jobs: SUREFIRE_DIR=hugegraph-store/hg-store-test/target/surefire-reports REPORT="$SUREFIRE_DIR/TEST-org.apache.hugegraph.store.raftcore.ZeroByteStringHelperTest.xml" rm -f "$REPORT" + set -o pipefail + RUNTIME_LOG=$(mktemp "$RUNNER_TEMP/java-runtime.XXXXXX") mvn test -pl hugegraph-store/hg-store-test -am \ - -P store-raftcore-test -Dtest=ZeroByteStringHelperTest -ntp + -P store-raftcore-test -Dtest=ZeroByteStringHelperTest -ntp 2>&1 | tee "$RUNTIME_LOG" + bash "$TRAVIS_DIR/check-java-runtime-logs.sh" "$RUNTIME_LOG" test -s "$REPORT" || { echo "::error::Store single-class test produced no report" exit 1 @@ -417,40 +442,61 @@ jobs: - name: Run historical Store ingress tests run: | + set -o pipefail + RUNTIME_LOG=$(mktemp "$RUNNER_TEMP/java-runtime.XXXXXX") mvn test -pl hugegraph-store/hg-store-node -am \ -Dtest=StoredRowIngressTest -Dsurefire.failIfNoSpecifiedTests=false \ -Djacoco.sessionId=store-node-test \ - -Djacoco.destFile="$PWD/hugegraph-store/hg-store-test/target/store-node-test.exec" + -Djacoco.destFile="$PWD/hugegraph-store/hg-store-test/target/store-node-test.exec" 2>&1 | tee "$RUNTIME_LOG" + bash "$TRAVIS_DIR/check-java-runtime-logs.sh" "$RUNTIME_LOG" - name: Run common test run: | + set -o pipefail + RUNTIME_LOG=$(mktemp "$RUNNER_TEMP/java-runtime.XXXXXX") mvn test -pl hugegraph-store/hg-store-test -am \ - -P store-common-test -Djacoco.sessionId=store-common-test + -P store-common-test -Djacoco.sessionId=store-common-test 2>&1 | tee "$RUNTIME_LOG" + bash "$TRAVIS_DIR/check-java-runtime-logs.sh" "$RUNTIME_LOG" - name: Run client test run: | + set -o pipefail + RUNTIME_LOG=$(mktemp "$RUNNER_TEMP/java-runtime.XXXXXX") mvn test -pl hugegraph-store/hg-store-test -am \ - -P store-client-test -Djacoco.sessionId=store-client-test + -P store-client-test -Djacoco.sessionId=store-client-test 2>&1 | tee "$RUNTIME_LOG" + bash "$TRAVIS_DIR/check-java-runtime-logs.sh" "$RUNTIME_LOG" - name: Run rocksdb test run: | + set -o pipefail + RUNTIME_LOG=$(mktemp "$RUNNER_TEMP/java-runtime.XXXXXX") mvn test -pl hugegraph-store/hg-store-test -am \ - -P store-rocksdb-test -Djacoco.sessionId=store-rocksdb-test + -P store-rocksdb-test -Djacoco.sessionId=store-rocksdb-test 2>&1 | tee "$RUNTIME_LOG" + bash "$TRAVIS_DIR/check-java-runtime-logs.sh" "$RUNTIME_LOG" - name: Run raft-core test run: | + set -o pipefail + RUNTIME_LOG=$(mktemp "$RUNNER_TEMP/java-runtime.XXXXXX") mvn test -pl hugegraph-store/hg-store-test -am \ - -P store-raftcore-test -Djacoco.sessionId=store-raftcore-test + -P store-raftcore-test -Djacoco.sessionId=store-raftcore-test 2>&1 | tee "$RUNTIME_LOG" + bash "$TRAVIS_DIR/check-java-runtime-logs.sh" "$RUNTIME_LOG" - name: Run core test run: | + set -o pipefail + RUNTIME_LOG=$(mktemp "$RUNNER_TEMP/java-runtime.XXXXXX") mvn test -pl hugegraph-store/hg-store-test -am \ - -P store-core-test -Djacoco.sessionId=store-core-test + -P store-core-test -Djacoco.sessionId=store-core-test 2>&1 | tee "$RUNTIME_LOG" + bash "$TRAVIS_DIR/check-java-runtime-logs.sh" "$RUNTIME_LOG" - name: Run server lifecycle and shutdown tests run: | + set -o pipefail + RUNTIME_LOG=$(mktemp "$RUNNER_TEMP/java-runtime.XXXXXX") mvn test -pl hugegraph-store/hg-store-test -am \ - -P store-server-test -Djacoco.sessionId=store-server-test + -P store-server-test -Djacoco.sessionId=store-server-test 2>&1 | tee "$RUNTIME_LOG" + bash "$TRAVIS_DIR/check-java-runtime-logs.sh" "$RUNTIME_LOG" - name: Generate aggregate coverage report run: | @@ -601,7 +647,10 @@ jobs: - name: Run unit test run: | - $TRAVIS_DIR/run-unit-test.sh $BACKEND + set -o pipefail + RUNTIME_LOG=$(mktemp "$RUNNER_TEMP/java-runtime.XXXXXX") + $TRAVIS_DIR/run-unit-test.sh $BACKEND 2>&1 | tee "$RUNTIME_LOG" + bash "$TRAVIS_DIR/check-java-runtime-logs.sh" "$RUNTIME_LOG" - name: Run core test # Bound stuck HStore scans so the diagnostic steps below can run @@ -610,16 +659,25 @@ jobs: env: HUGEGRAPH_TEST_TASK_LOG_LEVEL: WARN run: | - $TRAVIS_DIR/run-core-test.sh $BACKEND + set -o pipefail + RUNTIME_LOG=$(mktemp "$RUNNER_TEMP/java-runtime.XXXXXX") + $TRAVIS_DIR/run-core-test.sh $BACKEND 2>&1 | tee "$RUNTIME_LOG" + bash "$TRAVIS_DIR/check-java-runtime-logs.sh" "$RUNTIME_LOG" - name: Run api test run: | - $TRAVIS_DIR/run-api-test.sh $BACKEND $REPORT_DIR + set -o pipefail + RUNTIME_LOG=$(mktemp "$RUNNER_TEMP/java-runtime.XXXXXX") + $TRAVIS_DIR/run-api-test.sh $BACKEND $REPORT_DIR 2>&1 | tee "$RUNTIME_LOG" + bash "$TRAVIS_DIR/check-java-runtime-logs.sh" "$RUNTIME_LOG" - name: Run raft test if: ${{ env.BACKEND == 'rocksdb' }} run: | - $TRAVIS_DIR/run-api-test-for-raft.sh $BACKEND $REPORT_DIR + set -o pipefail + RUNTIME_LOG=$(mktemp "$RUNNER_TEMP/java-runtime.XXXXXX") + $TRAVIS_DIR/run-api-test-for-raft.sh $BACKEND $REPORT_DIR 2>&1 | tee "$RUNTIME_LOG" + bash "$TRAVIS_DIR/check-java-runtime-logs.sh" "$RUNTIME_LOG" # Include HStore in the upgrade compatibility gate. The dedicated HStore # service setup above makes its structure/process suites exercise the @@ -632,9 +690,11 @@ jobs: env: HUGEGRAPH_TEST_TASK_LOG_LEVEL: WARN run: | + set -o pipefail + RUNTIME_LOG=$(mktemp "$RUNNER_TEMP/java-runtime.XXXXXX") # Emit lightweight samples during execution: a lost runner cannot # upload the failure-only diagnostics below. - python3 - "$TRAVIS_DIR/run-tinkerpop-test.sh" "$BACKEND" structure <<'PYTHON' + python3 - "$TRAVIS_DIR/run-tinkerpop-test.sh" "$BACKEND" structure 2>&1 <<'PYTHON' | tee "$RUNTIME_LOG" import datetime import os import pathlib @@ -677,18 +737,25 @@ jobs: result = test.returncode sys.exit(result if result >= 0 else 128 - result) PYTHON + bash "$TRAVIS_DIR/check-java-runtime-logs.sh" "$RUNTIME_LOG" - name: Run TinkerPop process standard test if: ${{ env.RUN_TINKERPOP_TESTS == 'true' }} timeout-minutes: 120 run: | - $TRAVIS_DIR/run-tinkerpop-test.sh $BACKEND process-standard + set -o pipefail + RUNTIME_LOG=$(mktemp "$RUNNER_TEMP/java-runtime.XXXXXX") + $TRAVIS_DIR/run-tinkerpop-test.sh $BACKEND process-standard 2>&1 | tee "$RUNTIME_LOG" + bash "$TRAVIS_DIR/check-java-runtime-logs.sh" "$RUNTIME_LOG" - name: Run TinkerPop process feature test if: ${{ env.RUN_TINKERPOP_TESTS == 'true' }} timeout-minutes: 120 run: | - $TRAVIS_DIR/run-tinkerpop-test.sh $BACKEND process-feature + set -o pipefail + RUNTIME_LOG=$(mktemp "$RUNNER_TEMP/java-runtime.XXXXXX") + $TRAVIS_DIR/run-tinkerpop-test.sh $BACKEND process-feature 2>&1 | tee "$RUNTIME_LOG" + bash "$TRAVIS_DIR/check-java-runtime-logs.sh" "$RUNTIME_LOG" - name: Collect HStore diagnostics on failure or timeout if: ${{ failure() || cancelled() }} diff --git a/.github/workflows/server-tests.yml b/.github/workflows/server-tests.yml index f86d0904bb..c027d6c521 100644 --- a/.github/workflows/server-tests.yml +++ b/.github/workflows/server-tests.yml @@ -109,8 +109,11 @@ jobs: SUREFIRE_DIR=hugegraph-server/hugegraph-test/target/surefire-reports REPORT="$SUREFIRE_DIR/TEST-org.apache.hugegraph.unit.auth.HugeGraphAuthProxyTest.xml" rm -f "$REPORT" + set -o pipefail + RUNTIME_LOG=$(mktemp "$RUNNER_TEMP/java-runtime.XXXXXX") mvn test -pl hugegraph-server/hugegraph-test -am \ - -P unit-test -Dtest=HugeGraphAuthProxyTest -ntp + -P unit-test -Dtest=HugeGraphAuthProxyTest -ntp 2>&1 | tee "$RUNTIME_LOG" + bash "$TRAVIS_DIR/check-java-runtime-logs.sh" "$RUNTIME_LOG" test -s "$REPORT" || { echo "::error::Server single-class test produced no report" exit 1 @@ -168,36 +171,58 @@ jobs: - name: Run unit test run: | - $TRAVIS_DIR/run-unit-test.sh $BACKEND + set -o pipefail + RUNTIME_LOG=$(mktemp "$RUNNER_TEMP/java-runtime.XXXXXX") + $TRAVIS_DIR/run-unit-test.sh $BACKEND 2>&1 | tee "$RUNTIME_LOG" + bash "$TRAVIS_DIR/check-java-runtime-logs.sh" "$RUNTIME_LOG" - name: Run core test run: | - $TRAVIS_DIR/run-core-test.sh $BACKEND + set -o pipefail + RUNTIME_LOG=$(mktemp "$RUNNER_TEMP/java-runtime.XXXXXX") + $TRAVIS_DIR/run-core-test.sh $BACKEND 2>&1 | tee "$RUNTIME_LOG" + bash "$TRAVIS_DIR/check-java-runtime-logs.sh" "$RUNTIME_LOG" - name: Run api test run: | - if [ "$BACKEND" = "rocksdb" ]; then - $TRAVIS_DIR/run-api-test.sh $BACKEND $REPORT_DIR true - else - $TRAVIS_DIR/run-api-test.sh $BACKEND $REPORT_DIR - fi + set -o pipefail + RUNTIME_LOG=$(mktemp "$RUNNER_TEMP/java-runtime.XXXXXX") + { + if [ "$BACKEND" = "rocksdb" ]; then + $TRAVIS_DIR/run-api-test.sh $BACKEND $REPORT_DIR true + else + $TRAVIS_DIR/run-api-test.sh $BACKEND $REPORT_DIR + fi + } 2>&1 | tee "$RUNTIME_LOG" + bash "$TRAVIS_DIR/check-java-runtime-logs.sh" "$RUNTIME_LOG" # TODO: disable raft test in normal PR due to the always timeout problem - name: Run raft test if: ${{ env.RAFT_MODE == 'true' && env.BACKEND == 'rocksdb' }} timeout-minutes: 45 run: | - $TRAVIS_DIR/run-api-test-for-raft.sh $BACKEND $REPORT_DIR + set -o pipefail + RUNTIME_LOG=$(mktemp "$RUNNER_TEMP/java-runtime.XXXXXX") + $TRAVIS_DIR/run-api-test-for-raft.sh $BACKEND $REPORT_DIR 2>&1 | tee "$RUNTIME_LOG" + bash "$TRAVIS_DIR/check-java-runtime-logs.sh" "$RUNTIME_LOG" - name: Run TinkerPop structure test if: ${{ env.RUN_TINKERPOP_TESTS == 'true' && (env.BACKEND == 'memory' || env.BACKEND == 'rocksdb') }} timeout-minutes: 60 - run: $TRAVIS_DIR/run-tinkerpop-test.sh $BACKEND structure + run: | + set -o pipefail + RUNTIME_LOG=$(mktemp "$RUNNER_TEMP/java-runtime.XXXXXX") + $TRAVIS_DIR/run-tinkerpop-test.sh $BACKEND structure 2>&1 | tee "$RUNTIME_LOG" + bash "$TRAVIS_DIR/check-java-runtime-logs.sh" "$RUNTIME_LOG" - name: Run TinkerPop process test if: ${{ env.RUN_TINKERPOP_TESTS == 'true' && (env.BACKEND == 'memory' || env.BACKEND == 'rocksdb') }} timeout-minutes: 180 - run: $TRAVIS_DIR/run-tinkerpop-test.sh $BACKEND process + run: | + set -o pipefail + RUNTIME_LOG=$(mktemp "$RUNNER_TEMP/java-runtime.XXXXXX") + $TRAVIS_DIR/run-tinkerpop-test.sh $BACKEND process 2>&1 | tee "$RUNTIME_LOG" + bash "$TRAVIS_DIR/check-java-runtime-logs.sh" "$RUNTIME_LOG" - name: Upload coverage to Codecov uses: codecov/codecov-action@v7 diff --git a/docs/ci.md b/docs/ci.md index a6dd9fc1e5..d84ae3d6bf 100644 --- a/docs/ci.md +++ b/docs/ci.md @@ -75,6 +75,27 @@ does not accept a PR number, apply labels or reuse another workflow's results. Automatic retries are disabled. Use GitHub's native rerun controls for failed jobs or a run; a rerun retains its original commit and event, subject to the preflight freshness checks. +## Java runtime compatibility + +A positive suite passes only when its tests succeed and its current-run output has no +Java runtime compatibility errors. Server, PD/Store, Commons, Struct and Cluster test +steps capture both stdout and stderr before running `check-java-runtime-logs.sh`. +Startup and Console smoke tests also inspect their output before cleanup; Cluster nodes +archive logs before deletion through the test-only `hugegraph.ct.logArchive` property. +Missing or unreadable required logs fail the check. + +The gate rejects denied module access and unknown or removed JVM options, including +exceptions caught and printed by dependencies. Ordinary business errors and deliberate +negative fixtures are not compatibility failures. Native smoke additionally uses `--native` +for its existing native linkage checks. + +Launchers and tests share each component's `jvm-module.options`: PD and Store open +`java.lang` for Hessian, while Console and dump-store reuse Server's existing options. +Required module options are independent of custom `JAVA_OPTIONS` or `-j` flags; Surefire +retains the JaCoCo agent. CI continues to use the project Java runtime and its existing +suite-selection and required-check policy. + + ## Freshness, runner use and budgets PR selection uses the cumulative diff against the checked-out merge commit's base. A source diff --git a/docs/server-runtime.md b/docs/server-runtime.md index f370951a7f..5483702cb4 100644 --- a/docs/server-runtime.md +++ b/docs/server-runtime.md @@ -11,6 +11,21 @@ The runtime uses TinkerPop 3.8.1 and Groovy 4.0.25. Build and run every module w 17. Applications migrating from TinkerPop 3.5.1 must review the full version interval, including the configuration changes below. +### Required JVM module options + +Server, store initialization, store dumping and Gremlin Console always load the +distribution's `bin/jvm-module.options`. Keep this file with the shipped scripts; +each launcher rejects a missing or unreadable file. Required module permissions +are independent of caller options, so custom `JAVA_OPTIONS` or the Server launcher's +`-j` option do not replace them. Gremlin Console retains its existing +`JAVA_OPTIONS` defaults; store dumping retains its existing command-line interface. + +PD and Store use their own `bin/jvm-module.options` for the module permission needed +by Hessian serialization on Java 17. Their tests and cluster launchers use the same +component files. CI checks positive-test and startup logs for Java module-access +errors and unsupported JVM options, including errors printed without failing the +operation. + ### Launcher GC options Use `bin/start-hugegraph.sh -g g1` (or `-g G1`) to select G1 explicitly. diff --git a/hugegraph-cluster-test/hugegraph-clustertest-minicluster/src/main/java/org/apache/hugegraph/ct/env/AbstractEnv.java b/hugegraph-cluster-test/hugegraph-clustertest-minicluster/src/main/java/org/apache/hugegraph/ct/env/AbstractEnv.java index ccf0582a00..f9c5b5e5c0 100644 --- a/hugegraph-cluster-test/hugegraph-clustertest-minicluster/src/main/java/org/apache/hugegraph/ct/env/AbstractEnv.java +++ b/hugegraph-cluster-test/hugegraph-clustertest-minicluster/src/main/java/org/apache/hugegraph/ct/env/AbstractEnv.java @@ -147,14 +147,27 @@ private static void sleepBeforeRetry() { } public void stopCluster() { - for (ServerNodeWrapper serverNodeWrapper : serverNodeWrappers) { - serverNodeWrapper.stop(); - } - for (StoreNodeWrapper storeNodeWrapper : storeNodeWrappers) { - storeNodeWrapper.stop(); + List nodes = new ArrayList<>(serverNodeWrappers); + nodes.addAll(storeNodeWrappers); + nodes.addAll(pdNodeWrappers); + stopNodes(nodes); + } + + static void stopNodes(List nodes) { + AssertionError failure = null; + for (AbstractNodeWrapper node : nodes) { + try { + node.stop(); + } catch (RuntimeException | AssertionError e) { + if (failure == null) { + failure = new AssertionError("Cluster cleanup failed", e); + } else { + failure.addSuppressed(e); + } + } } - for (PDNodeWrapper pdNodeWrapper : pdNodeWrappers) { - pdNodeWrapper.stop(); + if (failure != null) { + throw failure; } } diff --git a/hugegraph-cluster-test/hugegraph-clustertest-minicluster/src/main/java/org/apache/hugegraph/ct/node/AbstractNodeWrapper.java b/hugegraph-cluster-test/hugegraph-clustertest-minicluster/src/main/java/org/apache/hugegraph/ct/node/AbstractNodeWrapper.java index 81d3a73923..701bae2afa 100644 --- a/hugegraph-cluster-test/hugegraph-clustertest-minicluster/src/main/java/org/apache/hugegraph/ct/node/AbstractNodeWrapper.java +++ b/hugegraph-cluster-test/hugegraph-clustertest-minicluster/src/main/java/org/apache/hugegraph/ct/node/AbstractNodeWrapper.java @@ -160,6 +160,41 @@ public boolean isStarted() { return false; } + protected String moduleOptions(Path options) throws IOException { + if (!Files.isRegularFile(options) || !Files.isReadable(options)) { + throw new IOException("Missing or unreadable JVM module options: " + options); + } + return "@" + options.toAbsolutePath(); + } + + private void archiveLogs() throws IOException { + String archive = System.getProperty("hugegraph.ct.logArchive"); + if (archive == null || archive.isEmpty()) { + return; + } + Path logDirectory = Paths.get(getLogPath()).getParent(); + if (!Files.isRegularFile(Paths.get(getLogPath()))) { + throw new IOException("Missing node startup log: " + getLogPath()); + } + Path archiveRoot = Paths.get(archive); + Files.createDirectories(archiveRoot); + Path destination = Files.createTempDirectory(archiveRoot, + "cluster-" + this.clusterIndex + + "-" + getID() + "-"); + try (Stream logs = Files.walk(logDirectory)) { + for (Path log : (Iterable) logs.filter(Files::isRegularFile)::iterator) { + String name = log.getFileName().toString(); + if (!name.endsWith(".log") && !name.endsWith(".out") && !name.endsWith(".err") && + !name.endsWith(".txt") && !name.matches(".*\\.log\\.[0-9]+")) { + continue; + } + Path target = destination.resolve(logDirectory.relativize(log)); + Files.createDirectories(target.getParent()); + Files.copy(log, target); + } + } + } + public void stop() { if (this.instance == null) { return; @@ -168,19 +203,25 @@ public void stop() { System.out.printf("[cluster-test] %s stopped unexpectedly: %s%n", getID(), processStatus()); dumpLog(); - deleteDir(); - return; + } else { + this.instance.destroy(); + try { + if (!this.instance.waitFor(20, TimeUnit.SECONDS)) { + this.instance.destroyForcibly().waitFor(10, TimeUnit.SECONDS); + } + } catch (InterruptedException e) { + this.instance.destroyForcibly(); + Thread.currentThread().interrupt(); + LOG.error("Waiting node to shutdown error.", e); + } } - this.instance.destroy(); try { - if (!this.instance.waitFor(20, TimeUnit.SECONDS)) { - this.instance.destroyForcibly().waitFor(10, TimeUnit.SECONDS); - } - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - LOG.error("Waiting node to shutdown error.", e); + archiveLogs(); + } catch (IOException e) { + throw new AssertionError("Archive node logs failed: " + getID(), e); + } finally { + deleteDir(); } - deleteDir(); } public boolean isAlive() { diff --git a/hugegraph-cluster-test/hugegraph-clustertest-minicluster/src/main/java/org/apache/hugegraph/ct/node/PDNodeWrapper.java b/hugegraph-cluster-test/hugegraph-clustertest-minicluster/src/main/java/org/apache/hugegraph/ct/node/PDNodeWrapper.java index e7a9e0f24f..43ecc2be6c 100644 --- a/hugegraph-cluster-test/hugegraph-clustertest-minicluster/src/main/java/org/apache/hugegraph/ct/node/PDNodeWrapper.java +++ b/hugegraph-cluster-test/hugegraph-clustertest-minicluster/src/main/java/org/apache/hugegraph/ct/node/PDNodeWrapper.java @@ -17,7 +17,9 @@ package org.apache.hugegraph.ct.node; +import static org.apache.hugegraph.ct.base.ClusterConstant.BIN_DIR; import static org.apache.hugegraph.ct.base.ClusterConstant.CONF_DIR; +import static org.apache.hugegraph.ct.base.ClusterConstant.JVM_MODULE_OPTIONS_FILE; import static org.apache.hugegraph.ct.base.ClusterConstant.JAVA_CMD; import static org.apache.hugegraph.ct.base.ClusterConstant.LOG4J_FILE; import static org.apache.hugegraph.ct.base.ClusterConstant.PD_JAR_PREFIX; @@ -67,6 +69,9 @@ public void start() { return; } + startCmd.add(moduleOptions(Paths.get(workPath).getParent() + .resolve(BIN_DIR).resolve(JVM_MODULE_OPTIONS_FILE))); + String pdNodeJarPath = getFileInDir(workPath, PD_JAR_PREFIX); startCmd.addAll(Arrays.asList( "-Dname=HugeGraphPD" + this.index, diff --git a/hugegraph-cluster-test/hugegraph-clustertest-minicluster/src/main/java/org/apache/hugegraph/ct/node/ServerNodeWrapper.java b/hugegraph-cluster-test/hugegraph-clustertest-minicluster/src/main/java/org/apache/hugegraph/ct/node/ServerNodeWrapper.java index bc093cb801..99f31dbf4f 100644 --- a/hugegraph-cluster-test/hugegraph-clustertest-minicluster/src/main/java/org/apache/hugegraph/ct/node/ServerNodeWrapper.java +++ b/hugegraph-cluster-test/hugegraph-clustertest-minicluster/src/main/java/org/apache/hugegraph/ct/node/ServerNodeWrapper.java @@ -149,8 +149,8 @@ public void start() { startCmd.addAll(Arrays.asList( "-Dname=HugeGraphServer" + this.index, - "@" + Paths.get(SERVER_PACKAGE_PATH, BIN_DIR, - JVM_MODULE_OPTIONS_FILE), + moduleOptions(Paths.get(SERVER_PACKAGE_PATH, BIN_DIR, + JVM_MODULE_OPTIONS_FILE)), "-cp", storeClassPath, "org.apache.hugegraph.dist.HugeGraphServer", "./conf/gremlin-server.yaml", diff --git a/hugegraph-cluster-test/hugegraph-clustertest-minicluster/src/main/java/org/apache/hugegraph/ct/node/StoreNodeWrapper.java b/hugegraph-cluster-test/hugegraph-clustertest-minicluster/src/main/java/org/apache/hugegraph/ct/node/StoreNodeWrapper.java index d76ceaa0b8..ed7225f5a6 100644 --- a/hugegraph-cluster-test/hugegraph-clustertest-minicluster/src/main/java/org/apache/hugegraph/ct/node/StoreNodeWrapper.java +++ b/hugegraph-cluster-test/hugegraph-clustertest-minicluster/src/main/java/org/apache/hugegraph/ct/node/StoreNodeWrapper.java @@ -17,7 +17,9 @@ package org.apache.hugegraph.ct.node; +import static org.apache.hugegraph.ct.base.ClusterConstant.BIN_DIR; import static org.apache.hugegraph.ct.base.ClusterConstant.CONF_DIR; +import static org.apache.hugegraph.ct.base.ClusterConstant.JVM_MODULE_OPTIONS_FILE; import static org.apache.hugegraph.ct.base.ClusterConstant.JAVA_CMD; import static org.apache.hugegraph.ct.base.ClusterConstant.LOG4J_FILE; import static org.apache.hugegraph.ct.base.ClusterConstant.STORE_JAR_PREFIX; @@ -64,6 +66,9 @@ public void start() { return; } + startCmd.add(moduleOptions(Paths.get(workPath).getParent() + .resolve(BIN_DIR).resolve(JVM_MODULE_OPTIONS_FILE))); + String storeNodeJarPath = getFileInDir(workPath, STORE_JAR_PREFIX); startCmd.addAll(Arrays.asList( "-Dname=HugeGraphStore" + this.index, diff --git a/hugegraph-cluster-test/hugegraph-clustertest-minicluster/src/test/java/org/apache/hugegraph/ct/env/AbstractEnvTest.java b/hugegraph-cluster-test/hugegraph-clustertest-minicluster/src/test/java/org/apache/hugegraph/ct/env/AbstractEnvTest.java new file mode 100644 index 0000000000..99cab1a9d5 --- /dev/null +++ b/hugegraph-cluster-test/hugegraph-clustertest-minicluster/src/test/java/org/apache/hugegraph/ct/env/AbstractEnvTest.java @@ -0,0 +1,68 @@ +/* + * 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. + */ + +package org.apache.hugegraph.ct.env; + +import java.util.Arrays; + +import org.apache.hugegraph.ct.node.AbstractNodeWrapper; +import org.junit.Assert; +import org.junit.Test; + +public class AbstractEnvTest { + + @Test + public void testCleanupFailureDoesNotSkipRemainingNodes() { + TestNode first = new TestNode(true); + TestNode second = new TestNode(false); + TestNode third = new TestNode(true); + AssertionError failure = Assert.assertThrows(AssertionError.class, + () -> AbstractEnv.stopNodes(Arrays.asList(first, second, third))); + Assert.assertTrue(first.stopped); + Assert.assertTrue(second.stopped); + Assert.assertTrue(third.stopped); + Assert.assertEquals(1, failure.getSuppressed().length); + } + + private static class TestNode extends AbstractNodeWrapper { + + private final boolean fail; + private boolean stopped; + + private TestNode(boolean fail) { + this.fail = fail; + } + + @Override + public void start() { + throw new UnsupportedOperationException(); + } + + @Override + public void stop() { + this.stopped = true; + if (this.fail) { + throw new AssertionError("Archive node logs failed"); + } + } + + @Override + public String getID() { + return "test"; + } + } +} diff --git a/hugegraph-cluster-test/hugegraph-clustertest-minicluster/src/test/java/org/apache/hugegraph/ct/node/AbstractNodeWrapperTest.java b/hugegraph-cluster-test/hugegraph-clustertest-minicluster/src/test/java/org/apache/hugegraph/ct/node/AbstractNodeWrapperTest.java index 2d452f04d3..cf52c4347b 100644 --- a/hugegraph-cluster-test/hugegraph-clustertest-minicluster/src/test/java/org/apache/hugegraph/ct/node/AbstractNodeWrapperTest.java +++ b/hugegraph-cluster-test/hugegraph-clustertest-minicluster/src/test/java/org/apache/hugegraph/ct/node/AbstractNodeWrapperTest.java @@ -23,7 +23,11 @@ import java.net.URISyntaxException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; +import java.nio.file.Path; import java.nio.file.Paths; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; import java.util.concurrent.TimeUnit; import org.junit.Assert; @@ -80,6 +84,112 @@ public void testStopDumpsLogOfUnexpectedlyExitedProcess() throws Exception { Assert.assertTrue(diagnostics, diagnostics.contains(failure)); } + @Test + public void testArchiveBeforeDeletingExitedNode() throws Exception { + File nodeDirectory = newNodeDirectory(); + TestNodeWrapper wrapper = new TestNodeWrapper(nodeDirectory); + String failure = "retained startup diagnostics"; + Files.writeString(Paths.get(wrapper.getLogPath()), failure); + Path raftLog = nodeDirectory.toPath().resolve("logs/raft.log"); + Files.writeString(raftLog, "raft diagnostics"); + Path archive = temporaryFolder.newFolder().toPath(); + String originalArchive = System.getProperty("hugegraph.ct.logArchive"); + try { + System.setProperty("hugegraph.ct.logArchive", archive.toString()); + wrapper.attach(startExitedProcess()); + wrapper.stop(); + Assert.assertFalse(nodeDirectory.exists()); + List archived = archivedFiles(archive); + Assert.assertEquals(2, archived.size()); + Assert.assertTrue(archived.stream().anyMatch(path -> path.getFileName().toString() + .equals("raft.log"))); + for (Path path : archived) { + if (path.getFileName().toString().endsWith("-start.log")) { + Assert.assertEquals(failure, Files.readString(path)); + } + } + } finally { + restoreArchiveProperty(originalArchive); + } + } + + @Test + public void testArchiveDoesNotOverwriteRestartsOrClusters() throws Exception { + Path archive = temporaryFolder.newFolder().toPath(); + String originalArchive = System.getProperty("hugegraph.ct.logArchive"); + try { + System.setProperty("hugegraph.ct.logArchive", archive.toString()); + for (int cluster : new int[]{1, 1, 2}) { + TestNodeWrapper wrapper = new TestNodeWrapper(newNodeDirectory()); + wrapper.clusterIndex = cluster; + Files.writeString(Paths.get(wrapper.getLogPath()), "cluster " + cluster); + wrapper.attach(startExitedProcess()); + wrapper.stop(); + } + Assert.assertEquals(3, archivedFiles(archive).size()); + } finally { + restoreArchiveProperty(originalArchive); + } + } + + @Test + public void testArchiveFailureStillStopsAndDeletesNode() throws Exception { + TestNodeWrapper wrapper = new TestNodeWrapper(newNodeDirectory()); + Files.writeString(Paths.get(wrapper.getLogPath()), START_LINE); + String originalArchive = System.getProperty("hugegraph.ct.logArchive"); + Process process = startSleeperProcess(); + wrapper.attach(process); + try { + System.setProperty("hugegraph.ct.logArchive", temporaryFolder.newFile().toString()); + Assert.assertThrows(AssertionError.class, wrapper::stop); + Assert.assertFalse(process.isAlive()); + Assert.assertFalse(Files.exists(Paths.get(wrapper.getNodePath()))); + } finally { + process.destroyForcibly(); + restoreArchiveProperty(originalArchive); + } + } + + @Test + public void testStopWithoutArchiveKeepsDefaultCleanup() throws Exception { + TestNodeWrapper wrapper = new TestNodeWrapper(newNodeDirectory()); + Files.writeString(Paths.get(wrapper.getLogPath()), START_LINE); + String originalArchive = System.getProperty("hugegraph.ct.logArchive"); + try { + System.clearProperty("hugegraph.ct.logArchive"); + wrapper.attach(startExitedProcess()); + wrapper.stop(); + Assert.assertFalse(Files.exists(Paths.get(wrapper.getNodePath()))); + } finally { + restoreArchiveProperty(originalArchive); + } + } + + @Test + public void testModuleOptionsRequireReadableFile() throws Exception { + TestNodeWrapper wrapper = new TestNodeWrapper(newNodeDirectory()); + Path options = temporaryFolder.newFile("jvm-module.options").toPath(); + Assert.assertEquals("@" + options.toAbsolutePath(), wrapper.moduleOptions(options)); + Files.delete(options); + Assert.assertThrows(java.io.IOException.class, () -> wrapper.moduleOptions(options)); + Assert.assertThrows(java.io.IOException.class, + () -> wrapper.moduleOptions(temporaryFolder.getRoot().toPath())); + } + + private static List archivedFiles(Path archive) throws Exception { + try (Stream files = Files.walk(archive)) { + return files.filter(Files::isRegularFile).collect(Collectors.toList()); + } + } + + private static void restoreArchiveProperty(String original) { + if (original == null) { + System.clearProperty("hugegraph.ct.logArchive"); + } else { + System.setProperty("hugegraph.ct.logArchive", original); + } + } + private File newNodeDirectory() throws Exception { File nodeDirectory = temporaryFolder.newFolder(); File logDirectory = new File(nodeDirectory, "logs"); diff --git a/hugegraph-cluster-test/hugegraph-clustertest-minicluster/src/test/java/org/apache/hugegraph/ct/node/ModuleOptionsTest.java b/hugegraph-cluster-test/hugegraph-clustertest-minicluster/src/test/java/org/apache/hugegraph/ct/node/ModuleOptionsTest.java new file mode 100644 index 0000000000..432cfa74df --- /dev/null +++ b/hugegraph-cluster-test/hugegraph-clustertest-minicluster/src/test/java/org/apache/hugegraph/ct/node/ModuleOptionsTest.java @@ -0,0 +1,120 @@ +/* + * 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. + */ + +package org.apache.hugegraph.ct.node; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import org.junit.Assert; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +public class ModuleOptionsTest { + + @Rule + public TemporaryFolder temporaryFolder = new TemporaryFolder(); + + @Test + public void testPDUsesPackagedModuleOptions() throws Exception { + CapturingPD wrapper = new CapturingPD(); + Path options = prepareDistribution(wrapper); + wrapper.start(); + Assert.assertTrue(wrapper.command.contains("@" + options.toAbsolutePath())); + Assert.assertFalse(wrapper.command.stream().anyMatch(arg -> arg.startsWith("--add-opens"))); + Assert.assertTrue(wrapper.instance.waitFor(10, TimeUnit.SECONDS)); + Files.delete(options); + AssertionError error = Assert.assertThrows(AssertionError.class, wrapper::start); + Assert.assertTrue(error.getMessage().contains("JVM module options")); + } + + @Test + public void testStoreUsesPackagedModuleOptions() throws Exception { + CapturingStore wrapper = new CapturingStore(); + Path options = prepareDistribution(wrapper); + wrapper.start(); + Assert.assertTrue(wrapper.command.contains("@" + options.toAbsolutePath())); + Assert.assertFalse(wrapper.command.stream().anyMatch(arg -> arg.startsWith("--add-opens"))); + Assert.assertTrue(wrapper.instance.waitFor(10, TimeUnit.SECONDS)); + Files.delete(options); + AssertionError error = Assert.assertThrows(AssertionError.class, wrapper::start); + Assert.assertTrue(error.getMessage().contains("JVM module options")); + } + + private Path prepareDistribution(AbstractNodeWrapper wrapper) throws Exception { + Path distribution = temporaryFolder.newFolder().toPath(); + Files.createDirectory(distribution.resolve("lib")); + Files.createDirectory(distribution.resolve("bin")); + Path options = distribution.resolve("bin/jvm-module.options"); + Files.writeString(options, "--add-opens=java.base/java.lang=ALL-UNNAMED\n"); + wrapper.updateWorkPath(distribution.resolve("lib").toString() + File.separator); + return options; + } + + private static ProcessBuilder harmlessProcess() { + return new ProcessBuilder(Paths.get(System.getProperty("java.home"), "bin", "java").toString(), + "-version"); + } + + private static class CapturingPD extends PDNodeWrapper { + + private List command; + + @Override + public void createNodeDir(Path source, String destination) { + // Distribution files are provided by the test. + } + + @Override + public void createLogDir() { + // No service process is launched. + } + + @Override + protected ProcessBuilder runCmd(List command, File stdout) throws IOException { + this.command = command; + return harmlessProcess(); + } + } + + private static class CapturingStore extends StoreNodeWrapper { + + private List command; + + @Override + public void createNodeDir(Path source, String destination) { + // Distribution files are provided by the test. + } + + @Override + public void createLogDir() { + // No service process is launched. + } + + @Override + protected ProcessBuilder runCmd(List command, File stdout) throws IOException { + this.command = command; + return harmlessProcess(); + } + } +} diff --git a/hugegraph-cluster-test/hugegraph-clustertest-test/src/main/java/org/apache/hugegraph/MultiClusterTest/BaseMultiClusterTest.java b/hugegraph-cluster-test/hugegraph-clustertest-test/src/main/java/org/apache/hugegraph/MultiClusterTest/BaseMultiClusterTest.java index 0f2b0d492a..25be1f637d 100644 --- a/hugegraph-cluster-test/hugegraph-clustertest-test/src/main/java/org/apache/hugegraph/MultiClusterTest/BaseMultiClusterTest.java +++ b/hugegraph-cluster-test/hugegraph-clustertest-test/src/main/java/org/apache/hugegraph/MultiClusterTest/BaseMultiClusterTest.java @@ -63,9 +63,12 @@ public static void initEnv() { @AfterClass public static void clearEnv() { - env.stopCluster(); - for (RestClient client : clients) { - client.close(); + try { + env.stopCluster(); + } finally { + for (RestClient client : clients) { + client.close(); + } } } diff --git a/hugegraph-cluster-test/hugegraph-clustertest-test/src/main/java/org/apache/hugegraph/SimpleClusterTest/BaseSimpleTest.java b/hugegraph-cluster-test/hugegraph-clustertest-test/src/main/java/org/apache/hugegraph/SimpleClusterTest/BaseSimpleTest.java index 996856f86c..08419ae061 100644 --- a/hugegraph-cluster-test/hugegraph-clustertest-test/src/main/java/org/apache/hugegraph/SimpleClusterTest/BaseSimpleTest.java +++ b/hugegraph-cluster-test/hugegraph-clustertest-test/src/main/java/org/apache/hugegraph/SimpleClusterTest/BaseSimpleTest.java @@ -72,11 +72,14 @@ public static void initEnv() { @AfterClass public static void clearEnv() throws InterruptedException { - env.stopCluster(); - Thread.sleep(2000); - if (client != null) { - client.close(); - client = null; + try { + env.stopCluster(); + Thread.sleep(2000); + } finally { + if (client != null) { + client.close(); + client = null; + } } } diff --git a/hugegraph-pd/README.md b/hugegraph-pd/README.md index 3e6ea03289..9d556bd7d7 100644 --- a/hugegraph-pd/README.md +++ b/hugegraph-pd/README.md @@ -91,6 +91,10 @@ bin/start-hugegraph-pd.sh [-g GC_TYPE] [-j "JVM_OPTIONS"] [-y ENABLE_OTEL] [-d D - `-y`: Enable OpenTelemetry tracing (`true` or `false`, default: `false`) - `-d`: Daemon mode (`true` = daemon, `false` = foreground; default: `true`). Set to `false` when running under Docker or a process supervisor so the container exits if Java dies. +The launcher always loads `bin/jvm-module.options`, including with custom `JAVA_OPTIONS` or `-j`. +Keep this file in the distribution: it grants the Java 17 access required by Hessian Raft serialization; +startup fails if the file is missing or unreadable. Direct Java launches must include `@bin/jvm-module.options`. + ### Configuration Key configuration file: `conf/application.yml` diff --git a/hugegraph-pd/hg-pd-dist/src/assembly/static/bin/jvm-module.options b/hugegraph-pd/hg-pd-dist/src/assembly/static/bin/jvm-module.options new file mode 100644 index 0000000000..b41209f448 --- /dev/null +++ b/hugegraph-pd/hg-pd-dist/src/assembly/static/bin/jvm-module.options @@ -0,0 +1,19 @@ +# +# 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. +# + +# Hessian reflects into StackTraceElement when serializing Raft operations. +--add-opens=java.base/java.lang=ALL-UNNAMED diff --git a/hugegraph-pd/hg-pd-dist/src/assembly/static/bin/start-hugegraph-pd.sh b/hugegraph-pd/hg-pd-dist/src/assembly/static/bin/start-hugegraph-pd.sh index 10d23f17d6..d687db42cb 100755 --- a/hugegraph-pd/hg-pd-dist/src/assembly/static/bin/start-hugegraph-pd.sh +++ b/hugegraph-pd/hg-pd-dist/src/assembly/static/bin/start-hugegraph-pd.sh @@ -60,6 +60,12 @@ LOGS="$TOP/logs" OUTPUT=${LOGS}/hugegraph-pd-stdout.log GITHUB="https://github.com" PID_FILE="$BIN/pid" +JVM_MODULE_OPTIONS="$BIN/jvm-module.options" + +if [[ ! -f "$JVM_MODULE_OPTIONS" || ! -r "$JVM_MODULE_OPTIONS" ]]; then + echo "Error: JVM module options file is missing or unreadable: $JVM_MODULE_OPTIONS" >&2 + exit 1 +fi . "$BIN"/util.sh @@ -193,10 +199,10 @@ fi if [[ $DAEMON == "true" ]]; then echo "Starting HugeGraphPDServer in daemon mode..." if [[ "${STDOUT_MODE:-false}" == "true" ]]; then - exec ${JAVA} -Dname="HugeGraphPD" ${JVM_OPTIONS} ${JAVA_OPTIONS} \ + exec ${JAVA} "@$JVM_MODULE_OPTIONS" -Dname="HugeGraphPD" ${JVM_OPTIONS} ${JAVA_OPTIONS} \ -Dspring.config.location=${CONF}/application.yml "${JAVA_MAIN[@]}" "$@" & else - exec ${JAVA} -Dname="HugeGraphPD" ${JVM_OPTIONS} ${JAVA_OPTIONS} \ + exec ${JAVA} "@$JVM_MODULE_OPTIONS" -Dname="HugeGraphPD" ${JVM_OPTIONS} ${JAVA_OPTIONS} \ -Dspring.config.location=${CONF}/application.yml "${JAVA_MAIN[@]}" "$@" >> ${OUTPUT} 2>&1 & fi PID="$!" @@ -209,10 +215,10 @@ else echo "$$" > "$PID_FILE" echo "[+pid] $$" if [[ "${STDOUT_MODE:-false}" == "true" ]]; then - exec ${JAVA} -Dname="HugeGraphPD" ${JVM_OPTIONS} ${JAVA_OPTIONS} \ + exec ${JAVA} "@$JVM_MODULE_OPTIONS" -Dname="HugeGraphPD" ${JVM_OPTIONS} ${JAVA_OPTIONS} \ -Dspring.config.location=${CONF}/application.yml "${JAVA_MAIN[@]}" "$@" else - exec ${JAVA} -Dname="HugeGraphPD" ${JVM_OPTIONS} ${JAVA_OPTIONS} \ + exec ${JAVA} "@$JVM_MODULE_OPTIONS" -Dname="HugeGraphPD" ${JVM_OPTIONS} ${JAVA_OPTIONS} \ -Dspring.config.location=${CONF}/application.yml "${JAVA_MAIN[@]}" "$@" >> ${OUTPUT} 2>&1 fi fi diff --git a/hugegraph-pd/hg-pd-test/pom.xml b/hugegraph-pd/hg-pd-test/pom.xml index a248bcb87b..bca134ce4e 100644 --- a/hugegraph-pd/hg-pd-test/pom.xml +++ b/hugegraph-pd/hg-pd-test/pom.xml @@ -166,6 +166,12 @@ org.apache.maven.plugins maven-surefire-plugin + + + @{argLine} + "@${project.basedir}/../hg-pd-dist/src/assembly/static/bin/jvm-module.options" + + org.apache.maven.surefire diff --git a/hugegraph-server/hugegraph-dist/src/assembly/static/bin/dump-store.sh b/hugegraph-server/hugegraph-dist/src/assembly/static/bin/dump-store.sh index f612557e82..eb05bb4568 100755 --- a/hugegraph-server/hugegraph-dist/src/assembly/static/bin/dump-store.sh +++ b/hugegraph-server/hugegraph-dist/src/assembly/static/bin/dump-store.sh @@ -29,6 +29,12 @@ BIN=`abs_path` TOP="$(cd $BIN/../ && pwd)" CONF=$TOP/conf LIB=$TOP/lib +JVM_MODULE_OPTIONS="${BIN}/jvm-module.options" + +if [[ ! -r "${JVM_MODULE_OPTIONS}" ]]; then + echo "Missing or unreadable JVM module options file: ${JVM_MODULE_OPTIONS}" >&2 + exit 1 +fi . "${BIN}"/util.sh @@ -52,5 +58,6 @@ echo "Dumping HugeGraph Store($conf)..." dump_store_ext_jar_path=$LIB/hugegraph-dist-*.jar for i in $LIB/*.jar; do dump_store_ext_jar_path=$dump_store_ext_jar_path:$i; export dump_store_ext_jar_path; done source "$BIN/preload-topling.sh" || exit 1 -exec "$JAVA" -cp "${TOPLING_RUNTIME_CLASSPATH:+$TOPLING_RUNTIME_CLASSPATH:}$dump_store_ext_jar_path" \ +exec "$JAVA" @"${JVM_MODULE_OPTIONS}" \ +-cp "${TOPLING_RUNTIME_CLASSPATH:+$TOPLING_RUNTIME_CLASSPATH:}$dump_store_ext_jar_path" \ org.apache.hugegraph.cmd.StoreDumper "$@" diff --git a/hugegraph-server/hugegraph-dist/src/assembly/static/bin/gremlin-console.sh b/hugegraph-server/hugegraph-dist/src/assembly/static/bin/gremlin-console.sh index 7a92b20068..40ec2ff7d9 100755 --- a/hugegraph-server/hugegraph-dist/src/assembly/static/bin/gremlin-console.sh +++ b/hugegraph-server/hugegraph-dist/src/assembly/static/bin/gremlin-console.sh @@ -30,6 +30,11 @@ while [ -h "$SOURCE" ]; do [[ $SOURCE != /* ]] && SOURCE="$BIN/$SOURCE" done BIN="$( cd -P "$( dirname "$SOURCE" )" && pwd )" +JVM_MODULE_OPTIONS="${BIN}/jvm-module.options" +if [[ ! -r "${JVM_MODULE_OPTIONS}" ]]; then + echo "Missing or unreadable JVM module options file: ${JVM_MODULE_OPTIONS}" >&2 + exit 1 +fi # Set $CFG to $BIN/../conf cd -P $BIN/../conf CFG=$(pwd) @@ -122,4 +127,4 @@ if [ -n "$SCRIPT_DEBUG" ]; then fi # Start the JVM, execute the application, and return its exit code -exec $JAVA $JAVA_OPTIONS $MAIN_CLASS "$@" +exec $JAVA @"${JVM_MODULE_OPTIONS}" $JAVA_OPTIONS $MAIN_CLASS "$@" diff --git a/hugegraph-server/hugegraph-dist/src/assembly/travis/check-java-runtime-logs.sh b/hugegraph-server/hugegraph-dist/src/assembly/travis/check-java-runtime-logs.sh new file mode 100755 index 0000000000..bd309a9766 --- /dev/null +++ b/hugegraph-server/hugegraph-dist/src/assembly/travis/check-java-runtime-logs.sh @@ -0,0 +1,76 @@ +#!/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 + +# Inspect only logs from a positive run, never a negative-test fixture or a +# whole workspace. Libraries can print these exceptions and still return 0. +PATTERN='java\.lang\.reflect\.InaccessibleObjectException:' +PATTERN+='|java\.lang\.(IllegalAccessError|IllegalAccessException):.*module .*does not' +PATTERN+='|Unrecognized VM option|Unrecognized option:' +PATTERN+='|Ignoring option .*support was removed|VM option .*is (not supported|obsolete|removed)' +if [[ "${1:-}" == --native ]]; then + # Keep native-linkage checks out of suites with intentional missing classes. + PATTERN+='|undefined symbol|UnsatisfiedLinkError|UnsupportedClassVersionError|NoClassDefFoundError' + shift +fi +if [[ $# -eq 0 ]]; then + echo "USAGE: $0 [--native] LOG_FILE_OR_DIRECTORY..." >&2 + exit 2 +fi + +STATUS=0 +check_file() { + local file="$1" matches code + if [[ ! -f "$file" || ! -r "$file" ]]; then + echo "Cannot read runtime log: $file" >&2 + STATUS=2 + return + fi + matches=$(LC_ALL=C grep -EinI -- "$PATTERN" "$file" 2>&1) + code=$? + if [[ $code -eq 0 ]]; then + echo "Java runtime compatibility error in $file:" >&2 + printf '%s\n' "$matches" >&2 + [[ $STATUS -eq 2 ]] || STATUS=1 + elif [[ $code -ne 1 ]]; then + printf '%s\n' "$matches" >&2 + STATUS=2 + fi +} + +for INPUT in "$@"; do + if [[ -d "$INPUT" ]]; then + # Exclude heap dumps and compressed rotations, even if they contain + # exception class names. Explicit file inputs can use any text suffix. + FILES=$(find "$INPUT" -type f \ + \( -name '*.log' -o -name '*.log.[0-9]*' -o -name '*.out' -o -name '*.err' -o -name '*.txt' \) \ + ! -name '*.gz' ! -name '*.zip' ! -name '*.bz2' ! -name '*.xz' ! -name '*.hprof*' -print) + if [[ $? -ne 0 || -z "$FILES" ]]; then + echo "Cannot collect runtime logs: $INPUT" >&2 + STATUS=2 + continue + fi + while IFS= read -r FILE; do + check_file "$FILE" + done <<<"$FILES" + else + check_file "$INPUT" + fi +done +exit "$STATUS" diff --git a/hugegraph-server/hugegraph-dist/src/assembly/travis/run-api-test-for-raft.sh b/hugegraph-server/hugegraph-dist/src/assembly/travis/run-api-test-for-raft.sh index a48894728e..e884727ac7 100755 --- a/hugegraph-server/hugegraph-dist/src/assembly/travis/run-api-test-for-raft.sh +++ b/hugegraph-server/hugegraph-dist/src/assembly/travis/run-api-test-for-raft.sh @@ -65,3 +65,6 @@ $TRAVIS_DIR/build-report.sh $BACKEND $JACOCO_PORT $REPORT_FILE $TRAVIS_DIR/stop-server.sh $RAFT1_DIR $TRAVIS_DIR/stop-server.sh $RAFT2_DIR $TRAVIS_DIR/stop-server.sh $RAFT3_DIR + +# Inspect service output after shutdown so background failures cannot pass the suite. +bash "$TRAVIS_DIR/check-java-runtime-logs.sh" "$RAFT1_DIR/logs" "$RAFT2_DIR/logs" "$RAFT3_DIR/logs" diff --git a/hugegraph-server/hugegraph-dist/src/assembly/travis/run-api-test.sh b/hugegraph-server/hugegraph-dist/src/assembly/travis/run-api-test.sh index 527f9120ed..30520e2cc5 100755 --- a/hugegraph-server/hugegraph-dist/src/assembly/travis/run-api-test.sh +++ b/hugegraph-server/hugegraph-dist/src/assembly/travis/run-api-test.sh @@ -104,3 +104,6 @@ $TRAVIS_DIR/build-report.sh $BACKEND $JACOCO_PORT $REPORT_FILE # stop server $TRAVIS_DIR/stop-server.sh $SERVER_DIR + +# Inspect service output after shutdown so background failures cannot pass the suite. +bash "$TRAVIS_DIR/check-java-runtime-logs.sh" "$SERVER_DIR/logs" diff --git a/hugegraph-server/hugegraph-dist/src/assembly/travis/run-gremlin-console-smoke-test.sh b/hugegraph-server/hugegraph-dist/src/assembly/travis/run-gremlin-console-smoke-test.sh index 35066abe58..7380a73cc6 100644 --- a/hugegraph-server/hugegraph-dist/src/assembly/travis/run-gremlin-console-smoke-test.sh +++ b/hugegraph-server/hugegraph-dist/src/assembly/travis/run-gremlin-console-smoke-test.sh @@ -20,6 +20,7 @@ set -euo pipefail SERVER_DIR=${1:?Usage: run-gremlin-console-smoke-test.sh } SMOKE_MARKER="gremlin-console-smoke-ok" REMOTE_CONFIG="$SERVER_DIR/conf/remote.yaml" +TRAVIS_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) if [ ! -d "$SERVER_DIR" ]; then echo "Server directory does not exist: $SERVER_DIR" @@ -60,6 +61,9 @@ EOF cat > "$SMOKE_SCRIPT" <&1 | tee "$SMOKE_LOG" +bash "$TRAVIS_DIR/check-java-runtime-logs.sh" "$SMOKE_LOG" +grep -q "gremlin-console-auth-ok" "$SMOKE_LOG" grep -q "$SMOKE_MARKER" "$SMOKE_LOG" grep -q "gremlin-console-gstring-ok" "$SMOKE_LOG" diff --git a/hugegraph-server/hugegraph-dist/src/assembly/travis/run-native-runtime-smoke-test.sh b/hugegraph-server/hugegraph-dist/src/assembly/travis/run-native-runtime-smoke-test.sh index 524b478846..f5c8d697ca 100755 --- a/hugegraph-server/hugegraph-dist/src/assembly/travis/run-native-runtime-smoke-test.sh +++ b/hugegraph-server/hugegraph-dist/src/assembly/travis/run-native-runtime-smoke-test.sh @@ -27,6 +27,7 @@ SERVER_DIR=$(cd "$1" && pwd) SERVER_URL=${SERVER_URL:-http://127.0.0.1:8080} EXPECTED_ARCH=${EXPECTED_ARCH:-riscv64} SERVER_START_ATTEMPTED=false +SERVER_START_COUNT=0 SERVER_STARTUP_TIMEOUT=${SERVER_STARTUP_TIMEOUT:-300} SERVER_START_COMMAND_TIMEOUT=$((SERVER_STARTUP_TIMEOUT + 30)) STORE_DUMP_TIMEOUT=${STORE_DUMP_TIMEOUT:-120} @@ -59,14 +60,20 @@ start_server() { if [[ -n "${SERVER_JAVA_OPTIONS:-}" ]]; then start_options+=(-j "$SERVER_JAVA_OPTIONS") fi + SERVER_START_COUNT=$((SERVER_START_COUNT + 1)) + local startup_log="$WORK_DIR/startup-$SERVER_START_COUNT.log" status=0 + # Daemon stdout must go to a file so a pipe reader does not wait for JVM exit. "$TIMEOUT_COMMAND" --foreground --kill-after=15s \ "${SERVER_START_COMMAND_TIMEOUT}s" \ - "$SERVER_DIR/bin/start-hugegraph.sh" "${start_options[@]}" + "$SERVER_DIR/bin/start-hugegraph.sh" "${start_options[@]}" \ + >"$startup_log" 2>&1 || status=$? + cat "$startup_log" + return "$status" } -"$TRAVIS_DIR/run-rocksdb-jni-smoke-test.sh" "$SERVER_DIR" +"$TRAVIS_DIR/run-rocksdb-jni-smoke-test.sh" "$SERVER_DIR" 2>&1 | tee "$WORK_DIR/jni.log" -"$SERVER_DIR/bin/init-store.sh" +"$SERVER_DIR/bin/init-store.sh" 2>&1 | tee "$WORK_DIR/init-store.log" SERVER_START_ATTEMPTED=true start_server "$TRAVIS_DIR/run-server-e2e-smoke-test.sh" "$SERVER_URL" create "$RUN_ID" | \ @@ -75,7 +82,7 @@ grep -q '^server-e2e-smoke-create-ok$' "$WORK_DIR/create.log" "$SERVER_DIR/bin/stop-hugegraph.sh" -m false "$TIMEOUT_COMMAND" --foreground --kill-after=15s "${STORE_DUMP_TIMEOUT}s" \ - "$SERVER_DIR/bin/dump-store.sh" | tee "$WORK_DIR/store-dump.log" + "$SERVER_DIR/bin/dump-store.sh" 2>&1 | tee "$WORK_DIR/store-dump.log" grep -q 'Dump table VERTEX (offset 0 limit 20):' "$WORK_DIR/store-dump.log" grep -Fq "riscv-smoke-v1-$RUN_ID" "$WORK_DIR/store-dump.log" grep -Fq "riscv-smoke-v2-$RUN_ID" "$WORK_DIR/store-dump.log" @@ -86,14 +93,11 @@ start_server tee "$WORK_DIR/verify.log" grep -q '^server-e2e-smoke-verify-ok$' "$WORK_DIR/verify.log" -# Heap dumps under logs/heapdump_*/ contain class names as plain strings, so -# leave them out of the search. -LINKAGE_ERRORS='undefined symbol|UnsatisfiedLinkError|UnsupportedClassVersionError|NoClassDefFoundError' -if [[ -d "$SERVER_DIR/logs" ]] && \ - grep -Eirq --exclude='*.hprof*' "$LINKAGE_ERRORS" "$SERVER_DIR/logs"; then - echo "Native linkage or Java compatibility error found in $SERVER_DIR/logs" >&2 - grep -Eirn --exclude='*.hprof*' "$LINKAGE_ERRORS" "$SERVER_DIR/logs" >&2 - exit 1 +LOG_INPUTS=("$WORK_DIR") +if [[ "${STDOUT_MODE:-false}" != true ]] || \ + [[ -n "$(find "$SERVER_DIR/logs" -type f -name '*.log' -print -quit 2>/dev/null)" ]]; then + LOG_INPUTS+=("$SERVER_DIR/logs") fi +"$TRAVIS_DIR/check-java-runtime-logs.sh" --native "${LOG_INPUTS[@]}" echo "native-runtime-smoke-ok" diff --git a/hugegraph-server/hugegraph-dist/src/assembly/travis/test-check-java-runtime-logs.sh b/hugegraph-server/hugegraph-dist/src/assembly/travis/test-check-java-runtime-logs.sh new file mode 100755 index 0000000000..4220cd259f --- /dev/null +++ b/hugegraph-server/hugegraph-dist/src/assembly/travis/test-check-java-runtime-logs.sh @@ -0,0 +1,77 @@ +#!/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 -euo pipefail + +SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +CHECKER="$SCRIPT_DIR/check-java-runtime-logs.sh" +WORK_DIR=$(mktemp -d "${TMPDIR:-/tmp}/java-runtime-logs.XXXXXX") +trap 'rm -rf "$WORK_DIR"' EXIT + +check_case() { + local expected="$1" status=0 + shift + "$CHECKER" "$@" >"$WORK_DIR/result" 2>&1 || status=$? + if [[ $status -ne $expected ]]; then + cat "$WORK_DIR/result" >&2 + echo "FAIL: expected $expected, got $status for $*" >&2 + exit 1 + fi +} + +printf '%s\n' '[ERROR] Expected validation error: invalid input' \ + 'java.lang.IllegalAccessException: expected business access failure' \ + 'InaccessibleObjectException is a class name in this message' >"$WORK_DIR/normal.log" +check_case 0 "$WORK_DIR/normal.log" +touch "$WORK_DIR/empty.log" +check_case 0 "$WORK_DIR/empty.log" + +for MESSAGE in \ + 'java.lang.reflect.InaccessibleObjectException: module java.base does not "opens java.lang"' \ + 'java.lang.IllegalAccessError: class A cannot access class B because module java.base does not export p' \ + 'java.lang.IllegalAccessException: module java.base does not export p to unnamed module' \ + "Unrecognized VM option 'OldGCOption'" \ + 'Unrecognized option: --unknown-option' \ + 'OpenJDK warning: Ignoring option --illegal-access=permit; support was removed in 17.0'; do + # A child returning success after logging only to stderr reproduces the + # failure mode that result assertions and exit-code checks cannot catch. + bash -c 'printf "%s\n" "$1" >&2' bash "$MESSAGE" >"$WORK_DIR/positive.log" 2>&1 + check_case 1 "$WORK_DIR/positive.log" + grep -q '^1:' "$WORK_DIR/result" +done + +printf '%s\n' 'java.lang.NoClassDefFoundError: intentionally missing test dependency' >"$WORK_DIR/native.log" +check_case 0 "$WORK_DIR/native.log" +check_case 1 --native "$WORK_DIR/native.log" +printf '%s\n' 'undefined symbol: rocksdb_open' >"$WORK_DIR/native.log" +check_case 1 --native "$WORK_DIR/native.log" + +mkdir -p "$WORK_DIR/logs with spaces/nested" "$WORK_DIR/no-logs" +cp "$WORK_DIR/normal.log" "$WORK_DIR/logs with spaces/application.log" +printf '%s\n' 'java.lang.reflect.InaccessibleObjectException: module java.base does not opens p' \ + >"$WORK_DIR/logs with spaces/nested/application.log.1" +check_case 1 "$WORK_DIR/logs with spaces" +rm "$WORK_DIR/logs with spaces/nested/application.log.1" +for SUFFIX in hprof log.gz log.zip log.bz2 log.xz; do + cp "$WORK_DIR/positive.log" "$WORK_DIR/logs with spaces/heap.$SUFFIX" +done +printf '\0java.lang.reflect.InaccessibleObjectException:\0' >"$WORK_DIR/logs with spaces/binary.log" +check_case 0 "$WORK_DIR/logs with spaces" +check_case 2 "$WORK_DIR/no-logs" +check_case 2 "$WORK_DIR/missing.log" +check_case 2 +echo 'PASS: Java runtime log checks reject compatibility errors without rejecting business failures' diff --git a/hugegraph-server/hugegraph-dist/src/assembly/travis/test-java-security-properties.sh b/hugegraph-server/hugegraph-dist/src/assembly/travis/test-java-security-properties.sh index d47b7e78dd..fc496f8835 100755 --- a/hugegraph-server/hugegraph-dist/src/assembly/travis/test-java-security-properties.sh +++ b/hugegraph-server/hugegraph-dist/src/assembly/travis/test-java-security-properties.sh @@ -185,7 +185,7 @@ if [[ -n "$SOURCE_ROOT_INPUT" ]]; then assert_surefire_arg_lines "$COMMONS_POM" \ '@${project.parent.basedir}/../hugegraph-server/hugegraph-test/conf/jvm-test-module.options' assert_source_consumer "$CLUSTER_WRAPPER" \ - '"@" + Paths.get(SERVER_PACKAGE_PATH, BIN_DIR,' + 'moduleOptions(Paths.get(SERVER_PACKAGE_PATH, BIN_DIR,' assert_no_inline_module_options \ "$SERVER_LAUNCHER_SOURCE" "$INIT_STORE_SOURCE" "$SUREFIRE_POM" \ "$COMMONS_POM" "$CLUSTER_WRAPPER" "$SERVER_DOCKERFILE" \ diff --git a/hugegraph-server/hugegraph-dist/src/assembly/travis/test-java17-upgrade-contracts.sh b/hugegraph-server/hugegraph-dist/src/assembly/travis/test-java17-upgrade-contracts.sh index 926a96de22..e51d369ce0 100755 --- a/hugegraph-server/hugegraph-dist/src/assembly/travis/test-java17-upgrade-contracts.sh +++ b/hugegraph-server/hugegraph-dist/src/assembly/travis/test-java17-upgrade-contracts.sh @@ -165,17 +165,21 @@ assert_surefire_execution_scope \ MODULE_OPTIONS="${SERVER_ROOT}/bin/jvm-module.options" SERVER_SCRIPT="${SERVER_ROOT}/bin/hugegraph-server.sh" INIT_STORE_SCRIPT="${SERVER_ROOT}/bin/init-store.sh" +CONSOLE_SCRIPT="${SERVER_ROOT}/bin/gremlin-console.sh" +DUMP_STORE_SCRIPT="${SERVER_ROOT}/bin/dump-store.sh" UTIL_SCRIPT="${SERVER_ROOT}/bin/util.sh" PRELOAD_SCRIPT="${SERVER_ROOT}/bin/preload-topling.sh" CONF_SOURCE="${SERVER_ROOT}/conf" for source_file in "$MODULE_OPTIONS" "$SERVER_SCRIPT" \ - "$INIT_STORE_SCRIPT" "$UTIL_SCRIPT" "$PRELOAD_SCRIPT"; do + "$INIT_STORE_SCRIPT" "$CONSOLE_SCRIPT" "$DUMP_STORE_SCRIPT" \ + "$UTIL_SCRIPT" "$PRELOAD_SCRIPT"; do [[ -f "$source_file" ]] || fail "runtime asset is missing: $source_file" done [[ -d "$CONF_SOURCE" ]] || fail "server conf is missing: $CONF_SOURCE" TEMP_DIR=$(mktemp -d) +TEMP_DIR=$(cd "$TEMP_DIR" && pwd -P) trap 'rm -rf "$TEMP_DIR"' EXIT DIST_ROOT="${TEMP_DIR}/server" @@ -183,10 +187,10 @@ MOCK_JAVA_HOME="${TEMP_DIR}/mock-java-home" mkdir -p "${DIST_ROOT}"/{bin,conf,ext,lib,logs,plugins} \ "${MOCK_JAVA_HOME}/bin" cp "$MODULE_OPTIONS" "$SERVER_SCRIPT" "$INIT_STORE_SCRIPT" "$UTIL_SCRIPT" \ - "$PRELOAD_SCRIPT" "${DIST_ROOT}/bin/" + "$CONSOLE_SCRIPT" "$DUMP_STORE_SCRIPT" "$PRELOAD_SCRIPT" "${DIST_ROOT}/bin/" cp -R "${CONF_SOURCE}/." "${DIST_ROOT}/conf/" -# Model a full pre-Phase-2 conf/ directory: it has no module argfile. Both +# Model a full pre-Phase-2 conf/ directory: it has no module argfile. All # launchers must get the immutable runtime copy from bin/ instead. if [[ -e "${DIST_ROOT}/conf/jvm-module.options" ]]; then fail "legacy conf unexpectedly contains jvm-module.options" @@ -203,7 +207,8 @@ done printf '%s\n' "$@" > "${CAPTURE_FILE:?}" MOCK chmod +x "${MOCK_JAVA_HOME}/bin/java" "${DIST_ROOT}/bin/hugegraph-server.sh" \ - "${DIST_ROOT}/bin/init-store.sh" + "${DIST_ROOT}/bin/init-store.sh" "${DIST_ROOT}/bin/gremlin-console.sh" \ + "${DIST_ROOT}/bin/dump-store.sh" SERVER_CAPTURE="${TEMP_DIR}/server.args" CAPTURE_FILE="$SERVER_CAPTURE" JAVA_HOME="$MOCK_JAVA_HOME" \ @@ -218,4 +223,40 @@ CAPTURE_FILE="$INIT_STORE_CAPTURE" JAVA_HOME="$MOCK_JAVA_HOME" \ "${DIST_ROOT}/bin/init-store.sh" >/dev/null assert_argument "@${DIST_ROOT}/bin/jvm-module.options" "$INIT_STORE_CAPTURE" +for launcher in gremlin-console dump-store; do + for options in "" "-Doperator.marker=preserved"; do + capture="${TEMP_DIR}/${launcher}-${options:+custom}.args" + CAPTURE_FILE="$capture" JAVA_HOME="$MOCK_JAVA_HOME" \ + JAVA_OPTIONS="$options" "${DIST_ROOT}/bin/${launcher}.sh" >/dev/null + assert_argument "@${DIST_ROOT}/bin/jvm-module.options" "$capture" + if [[ "$launcher" == gremlin-console && -n "$options" ]]; then + assert_argument "$options" "$capture" + fi + done +done + +# Required options survive the Server launcher's -j argument as well. +SERVER_J_CAPTURE="${TEMP_DIR}/server-j.args" +CAPTURE_FILE="$SERVER_J_CAPTURE" JAVA_HOME="$MOCK_JAVA_HOME" \ + JAVA_OPTIONS="" STDOUT_MODE=true "${DIST_ROOT}/bin/hugegraph-server.sh" \ + "${DIST_ROOT}/conf/gremlin-server.yaml" \ + "${DIST_ROOT}/conf/rest-server.properties" false \ + "-Xms512m -Xmx512m -Doperator.marker=preserved" >/dev/null +assert_argument "@${DIST_ROOT}/bin/jvm-module.options" "$SERVER_J_CAPTURE" +assert_argument "-Doperator.marker=preserved" "$SERVER_J_CAPTURE" + +mv "${DIST_ROOT}/bin/jvm-module.options" "${TEMP_DIR}/jvm-module.options" +for launcher in gremlin-console dump-store; do + capture="${TEMP_DIR}/${launcher}-missing.args" + output="${TEMP_DIR}/${launcher}-missing.log" + if CAPTURE_FILE="$capture" JAVA_HOME="$MOCK_JAVA_HOME" \ + JAVA_OPTIONS="-Doperator.marker=preserved" \ + "${DIST_ROOT}/bin/${launcher}.sh" >"$output" 2>&1; then + fail "$launcher accepted a missing JVM module options file" + fi + [[ ! -e "$capture" ]] || fail "$launcher invoked Java without required options" + grep -q "Missing or unreadable JVM module options file" "$output" || \ + fail "$launcher did not explain the missing module options file" +done + echo "PASS: Java 17 upgrade contracts" diff --git a/hugegraph-server/hugegraph-dist/src/assembly/travis/test-start-hugegraph-pd.sh b/hugegraph-server/hugegraph-dist/src/assembly/travis/test-start-hugegraph-pd.sh index 008be829ac..9677959aa9 100755 --- a/hugegraph-server/hugegraph-dist/src/assembly/travis/test-start-hugegraph-pd.sh +++ b/hugegraph-server/hugegraph-dist/src/assembly/travis/test-start-hugegraph-pd.sh @@ -33,6 +33,8 @@ PD_URL="http://localhost:8620" STARTUP_WAIT=60 # seconds to wait for PD HTTP to respond SETTLE_WAIT=5 # seconds after kill before checking exit WAIT_TIMEOUT=30 # seconds for wait_script_exit timeout +RUNTIME_LOG_CHECK="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/check-java-runtime-logs.sh" +ACTIVE_RUN=false PASS=0 FAIL=0 @@ -63,6 +65,13 @@ section() { echo "── $1 ──" } +start_service() { + mkdir -p "$PD_ROOT/logs" + ACTIVE_RUN=true + "$START_SCRIPT" "$@" > "$PD_ROOT/logs/launcher.out" 2>&1 & + SCRIPT_PID=$! +} + cleanup() { info "Cleaning up..." "$STOP_SCRIPT" >/dev/null 2>&1 || true @@ -70,7 +79,6 @@ cleanup() { kill "$(cat "$PID_FILE")" 2>/dev/null || true fi rm -f "$PID_FILE" - rm -rf "$PD_ROOT/logs/" # kill anything still holding the PD port: fuser on Linux, lsof on macOS, # which ships no fuser. Only the Linux path had to lose its lsof dependency. if [[ "$(uname)" == "Darwin" ]]; then @@ -90,6 +98,15 @@ cleanup() { echo "Warning: Neither lsof nor fuser available for PD port cleanup" fi sleep 3 + if [[ "$ACTIVE_RUN" == true ]]; then + if bash "$RUNTIME_LOG_CHECK" "$PD_ROOT/logs"; then + pass "startup logs contain no Java runtime compatibility errors" + else + fail "startup logs contain Java runtime compatibility errors or could not be checked" + fi + ACTIVE_RUN=false + fi + rm -rf "$PD_ROOT/logs/" } # Wait until PD health endpoint responds or timeout @@ -169,12 +186,16 @@ fi check_gc_options() { local fixture option capture status jvm_options fixture=$(mktemp -d) || { fail "could not create GC test fixture"; return 1; } + fixture=$(cd "$fixture" && pwd -P) if ! mkdir -p "$fixture/bin" "$fixture/conf" "$fixture/lib" "$fixture/jdk/bin" || - ! cp "$START_SCRIPT" "$BIN/util.sh" "$BIN/preload-topling.sh" "$fixture/bin/"; then + ! cp "$START_SCRIPT" "$BIN/util.sh" "$BIN/preload-topling.sh" \ + "$BIN/jvm-module.options" "$fixture/bin/"; then fail "could not prepare GC test fixture" rm -rf "$fixture" return 1 fi + # Make default memory selection independent of host memory and OS. + printf '%s\n' 'calc_xmx() { echo 1024; }' >> "$fixture/bin/util.sh" touch "$fixture/lib/hg-pd-service-test.jar" cat > "$fixture/jdk/bin/java" <<'JAVA' #!/bin/bash @@ -182,17 +203,26 @@ if [[ "$1" == "-version" ]]; then echo 'openjdk version "17.0.1"' >&2 else printf '%s\n' "$@" > "$CAPTURE_FILE" + for arg in "$@"; do + if [[ "$arg" == @* ]]; then + sed '/^[[:space:]]*#/d; /^[[:space:]]*$/d' "${arg#@}" >> "$CAPTURE_FILE" + fi + done fi JAVA # Avoid the Store allocator download, which is unrelated to GC selection. printf '%s\n' '#!/bin/sh' 'echo launcher-test' > "$fixture/jdk/bin/uname" chmod +x "$fixture/jdk/bin/"* "$fixture/bin/start-hugegraph-pd.sh" - for option in default default-serial default-zgc g1 G1 zgc ZGC invalid; do + for option in default custom-java-options user-options default-serial default-zgc \ + g1 G1 zgc ZGC invalid missing-module-options; do capture="$fixture/$option.args" local -a args=(-d false) jvm_options="-Xms64m -Xmx64m" case "$option" in - default) ;; + default) jvm_options="" ;; + custom-java-options) jvm_options="$jvm_options -Dlauncher.test.custom=true" ;; + user-options) jvm_options=""; args+=(-j "-Xmx96m -Dlauncher.test=true") ;; + missing-module-options) rm "$fixture/bin/jvm-module.options" ;; default-serial) jvm_options="$jvm_options -XX:+UseSerialGC" ;; default-zgc) jvm_options="$jvm_options -XX:+UseZGC" ;; *) args+=(-g "$option") ;; @@ -204,7 +234,14 @@ JAVA PATH="$fixture/jdk/bin:$PATH" \ bash "$fixture/bin/start-hugegraph-pd.sh" "${args[@]}" > "$fixture/output" 2>&1 status=$? - if [[ "$option" == invalid ]]; then + if [[ "$option" == missing-module-options ]]; then + if [[ $status -eq 1 && ! -e "$capture" ]] && + grep -Fq 'JVM module options file is missing or unreadable' "$fixture/output"; then + pass "missing JVM module options are rejected before application launch" + else + fail "missing JVM module options were not rejected correctly" + fi + elif [[ "$option" == invalid ]]; then if [[ $status -eq 1 && ! -e "$capture" ]] && \ grep -Fq "Unrecognized gc option: 'invalid'" "$fixture/logs/hugegraph-pd-stdout.log"; then pass "invalid GC is rejected before application launch" @@ -213,6 +250,15 @@ JAVA fi elif [[ $status -ne 0 || ! -s "$capture" ]]; then fail "GC '$option' did not reach Java (exit $status)" + elif ! grep -Fxq -- "@$fixture/bin/jvm-module.options" "$capture" || + ! grep -Fxq -- '--add-opens=java.base/java.lang=ALL-UNNAMED' "$capture"; then + fail "launcher '$option' dropped mandatory JVM module access" + elif [[ "$option" == custom-java-options ]] && + ! grep -Fxq -- '-Dlauncher.test.custom=true' "$capture"; then + fail "launcher dropped custom JAVA_OPTIONS" + elif [[ "$option" == user-options ]] && + ! grep -Fxq -- '-Dlauncher.test=true' "$capture"; then + fail "launcher dropped custom -j options" elif [[ "$option" == zgc || "$option" == ZGC ]]; then if grep -Fxq -- '-XX:+UseZGC' "$capture" && \ ! grep -Fxq -- '-XX:InitiatingHeapOccupancyPercent=50' "$capture"; then @@ -254,6 +300,11 @@ if [[ "${2:-}" == --gc-only ]]; then [[ $FAIL -eq 0 ]] && exit 0 || exit 1 fi +if [[ ! -r "$RUNTIME_LOG_CHECK" ]]; then + echo "ERROR: runtime log checker not found: $RUNTIME_LOG_CHECK" + exit 1 +fi + if [[ "$(uname)" == "Darwin" ]]; then _prereq_tools="lsof curl java jcmd" else @@ -273,8 +324,7 @@ cleanup section "Test 1 — daemon mode regression" info "Starting in daemon mode (no -d flag, default behavior)..." -"$START_SCRIPT" >/dev/null 2>&1 & -SCRIPT_PID=$! +start_service # daemon mode: script backgrounds Java and exits quickly sleep 5 @@ -321,8 +371,7 @@ cleanup section "Test 2 — foreground mode blocks until Java exits" info "Starting in foreground mode (-d false -g g1)..." -"$START_SCRIPT" -d false -g g1 >/dev/null 2>&1 & -SCRIPT_PID=$! +start_service -d false -g g1 info "Waiting up to ${STARTUP_WAIT}s for PD to come up..." if wait_for_pd; then @@ -379,8 +428,7 @@ cleanup section "Test 3 — exit code propagates from Java" info "Starting in foreground mode (-d false -g ZGC)..." -"$START_SCRIPT" -d false -g ZGC >/dev/null 2>&1 & -SCRIPT_PID=$! +start_service -d false -g ZGC info "Waiting up to ${STARTUP_WAIT}s for PD..." if wait_for_pd; then @@ -434,8 +482,7 @@ cleanup section "Test 4 — SIGTERM forwarded to Java in foreground mode" info "Starting in foreground mode (-d false)..." -"$START_SCRIPT" -d false >/dev/null 2>&1 & -SCRIPT_PID=$! +start_service -d false info "Waiting up to ${STARTUP_WAIT}s for PD..." if wait_for_pd; then diff --git a/hugegraph-server/hugegraph-dist/src/assembly/travis/test-start-hugegraph-store.sh b/hugegraph-server/hugegraph-dist/src/assembly/travis/test-start-hugegraph-store.sh index 1f1e87786e..6e1bc0394d 100755 --- a/hugegraph-server/hugegraph-dist/src/assembly/travis/test-start-hugegraph-store.sh +++ b/hugegraph-server/hugegraph-dist/src/assembly/travis/test-start-hugegraph-store.sh @@ -31,6 +31,8 @@ PD_URL="http://localhost:8620" STARTUP_WAIT=60 # seconds to wait for Store HTTP to respond SETTLE_WAIT=5 # seconds after kill before checking exit WAIT_TIMEOUT=30 # seconds for wait_script_exit timeout +RUNTIME_LOG_CHECK="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/check-java-runtime-logs.sh" +ACTIVE_RUN=false PASS=0 FAIL=0 @@ -46,6 +48,13 @@ fail() { echo -e "${RED} FAIL${NC} $1"; ERRORS+=("$1"); FAIL=$((FAIL + 1)); } info() { echo -e "${YELLOW} ....${NC} $1"; } section() { echo ""; echo "── $1 ──"; } +start_service() { + mkdir -p "$STORE_ROOT/logs" + ACTIVE_RUN=true + "$START_SCRIPT" "$@" > "$STORE_ROOT/logs/launcher.out" 2>&1 & + SCRIPT_PID=$! +} + cleanup() { info "Cleaning up..." "$STOP_SCRIPT" >/dev/null 2>&1 || true @@ -53,7 +62,6 @@ cleanup() { kill "$(cat "$PID_FILE")" 2>/dev/null || true fi rm -f "$PID_FILE" - rm -rf "$STORE_ROOT/logs/" # kill anything holding Store ports (8520 REST, 8510 raft, 8500 gRPC) if [[ "$(uname)" == "Darwin" ]]; then local port pids pid @@ -73,6 +81,15 @@ cleanup() { echo "Warning: Neither lsof nor fuser available for Store port cleanup" fi sleep 3 + if [[ "$ACTIVE_RUN" == true ]]; then + if bash "$RUNTIME_LOG_CHECK" "$STORE_ROOT/logs"; then + pass "startup logs contain no Java runtime compatibility errors" + else + fail "startup logs contain Java runtime compatibility errors or could not be checked" + fi + ACTIVE_RUN=false + fi + rm -rf "$STORE_ROOT/logs/" } wait_for_store() { @@ -127,12 +144,16 @@ fi check_gc_options() { local fixture option capture status jvm_options fixture=$(mktemp -d) || { fail "could not create GC test fixture"; return 1; } + fixture=$(cd "$fixture" && pwd -P) if ! mkdir -p "$fixture/bin" "$fixture/conf" "$fixture/lib" "$fixture/jdk/bin" || - ! cp "$START_SCRIPT" "$BIN/util.sh" "$BIN/preload-topling.sh" "$fixture/bin/"; then + ! cp "$START_SCRIPT" "$BIN/util.sh" "$BIN/preload-topling.sh" \ + "$BIN/jvm-module.options" "$fixture/bin/"; then fail "could not prepare GC test fixture" rm -rf "$fixture" return 1 fi + # Make default memory selection independent of host memory and OS. + printf '%s\n' 'calc_xmx() { echo 1024; }' >> "$fixture/bin/util.sh" touch "$fixture/lib/hg-store-node-test.jar" cat > "$fixture/jdk/bin/java" <<'JAVA' #!/bin/bash @@ -140,17 +161,26 @@ if [[ "$1" == "-version" ]]; then echo 'openjdk version "17.0.1"' >&2 else printf '%s\n' "$@" > "$CAPTURE_FILE" + for arg in "$@"; do + if [[ "$arg" == @* ]]; then + sed '/^[[:space:]]*#/d; /^[[:space:]]*$/d' "${arg#@}" >> "$CAPTURE_FILE" + fi + done fi JAVA # Avoid the Store allocator download, which is unrelated to GC selection. printf '%s\n' '#!/bin/sh' 'echo launcher-test' > "$fixture/jdk/bin/uname" chmod +x "$fixture/jdk/bin/"* "$fixture/bin/start-hugegraph-store.sh" - for option in default default-serial default-zgc g1 G1 zgc ZGC invalid; do + for option in default custom-java-options user-options default-serial default-zgc \ + g1 G1 zgc ZGC invalid missing-module-options; do capture="$fixture/$option.args" local -a args=(-d false) jvm_options="-Xms64m -Xmx64m" case "$option" in - default) ;; + default) jvm_options="" ;; + custom-java-options) jvm_options="$jvm_options -Dlauncher.test.custom=true" ;; + user-options) jvm_options=""; args+=(-j "-Xmx96m -Dlauncher.test=true") ;; + missing-module-options) rm "$fixture/bin/jvm-module.options" ;; default-serial) jvm_options="$jvm_options -XX:+UseSerialGC" ;; default-zgc) jvm_options="$jvm_options -XX:+UseZGC" ;; *) args+=(-g "$option") ;; @@ -162,7 +192,14 @@ JAVA PATH="$fixture/jdk/bin:$PATH" \ bash "$fixture/bin/start-hugegraph-store.sh" "${args[@]}" > "$fixture/output" 2>&1 status=$? - if [[ "$option" == invalid ]]; then + if [[ "$option" == missing-module-options ]]; then + if [[ $status -eq 1 && ! -e "$capture" ]] && + grep -Fq 'JVM module options file is missing or unreadable' "$fixture/output"; then + pass "missing JVM module options are rejected before application launch" + else + fail "missing JVM module options were not rejected correctly" + fi + elif [[ "$option" == invalid ]]; then if [[ $status -eq 1 && ! -e "$capture" ]] && \ grep -Fq "Unrecognized gc option: 'invalid'" "$fixture/logs/hugegraph-store-server.log"; then pass "invalid GC is rejected before application launch" @@ -171,6 +208,15 @@ JAVA fi elif [[ $status -ne 0 || ! -s "$capture" ]]; then fail "GC '$option' did not reach Java (exit $status)" + elif ! grep -Fxq -- "@$fixture/bin/jvm-module.options" "$capture" || + ! grep -Fxq -- '--add-opens=java.base/java.lang=ALL-UNNAMED' "$capture"; then + fail "launcher '$option' dropped mandatory JVM module access" + elif [[ "$option" == custom-java-options ]] && + ! grep -Fxq -- '-Dlauncher.test.custom=true' "$capture"; then + fail "launcher dropped custom JAVA_OPTIONS" + elif [[ "$option" == user-options ]] && + ! grep -Fxq -- '-Dlauncher.test=true' "$capture"; then + fail "launcher dropped custom -j options" elif [[ "$option" == zgc || "$option" == ZGC ]]; then if grep -Fxq -- '-XX:+UseZGC' "$capture" && \ ! grep -Fxq -- '-XX:InitiatingHeapOccupancyPercent=50' "$capture"; then @@ -212,6 +258,11 @@ if [[ "${2:-}" == --gc-only ]]; then [[ $FAIL -eq 0 ]] && exit 0 || exit 1 fi +if [[ ! -r "$RUNTIME_LOG_CHECK" ]]; then + echo "ERROR: runtime log checker not found: $RUNTIME_LOG_CHECK" + exit 1 +fi + if [[ "$(uname)" == "Darwin" ]]; then _prereq_tools="lsof curl java jcmd" else @@ -250,8 +301,7 @@ cleanup section "Test 1 — daemon mode regression" info "Starting in daemon mode (no -d flag, default behavior)..." -"$START_SCRIPT" >/dev/null 2>&1 & -SCRIPT_PID=$! +start_service sleep 5 if ! ps -p "$SCRIPT_PID" >/dev/null 2>&1; then @@ -294,8 +344,7 @@ cleanup section "Test 2 — foreground mode blocks until Java exits" info "Starting in foreground mode (-d false -g g1)..." -"$START_SCRIPT" -d false -g g1 >/dev/null 2>&1 & -SCRIPT_PID=$! +start_service -d false -g g1 info "Waiting up to ${STARTUP_WAIT}s for Store to come up..." if wait_for_store; then @@ -350,8 +399,7 @@ cleanup section "Test 3 — exit code propagates from Java" info "Starting in foreground mode (-d false -g ZGC)..." -"$START_SCRIPT" -d false -g ZGC >/dev/null 2>&1 & -SCRIPT_PID=$! +start_service -d false -g ZGC info "Waiting up to ${STARTUP_WAIT}s for Store..." if wait_for_store; then @@ -405,8 +453,7 @@ cleanup section "Test 4 — SIGTERM forwarded to Java in foreground mode" info "Starting in foreground mode (-d false)..." -"$START_SCRIPT" -d false >/dev/null 2>&1 & -SCRIPT_PID=$! +start_service -d false info "Waiting up to ${STARTUP_WAIT}s for Store..." if wait_for_store; then diff --git a/hugegraph-server/hugegraph-dist/src/assembly/travis/test-start-hugegraph.sh b/hugegraph-server/hugegraph-dist/src/assembly/travis/test-start-hugegraph.sh index db65ec51e8..f06d2dc24a 100755 --- a/hugegraph-server/hugegraph-dist/src/assembly/travis/test-start-hugegraph.sh +++ b/hugegraph-server/hugegraph-dist/src/assembly/travis/test-start-hugegraph.sh @@ -45,6 +45,8 @@ WAIT_TIMEOUT=30 # seconds for timeout on wait calls PASS=0 FAIL=0 ERRORS=() +STARTUP_LOG="$HUGEGRAPH_ROOT/logs/startup-test.log" +RUNTIME_STARTED=false GREEN='\033[0;32m' RED='\033[0;31m' @@ -102,6 +104,21 @@ cleanup() { fi sleep 3 crontab -l 2>/dev/null | grep -v monitor-hugegraph | crontab - 2>/dev/null || true + if [[ "$RUNTIME_STARTED" == true ]]; then + local logs=("$STARTUP_LOG") + if [[ "${STDOUT_MODE:-false}" != true ]]; then + logs+=("$HUGEGRAPH_ROOT/logs/hugegraph-server.log") + fi + if bash "$SCRIPT_DIR/check-java-runtime-logs.sh" "${logs[@]}"; then + pass "startup has no Java runtime compatibility errors" + else + fail "startup log contains a compatibility error or is unavailable" + fi + RUNTIME_STARTED=false + fi + # Each positive start must be checked independently of previous runs. + mkdir -p "$HUGEGRAPH_ROOT/logs" + rm -f "$STARTUP_LOG" "$HUGEGRAPH_ROOT/logs/hugegraph-server.log" } # Wait until server HTTP endpoint responds (200 or 401) or timeout @@ -195,9 +212,15 @@ fi # Run init-store.sh once if RocksDB has not been initialized yet if [[ ! -d "$HUGEGRAPH_ROOT/rocksdb-data" ]]; then info "RocksDB not initialized — running init-store.sh..." - if "$BIN/init-store.sh" >/dev/null 2>&1; then + mkdir -p "$HUGEGRAPH_ROOT/logs" + INIT_LOG="$HUGEGRAPH_ROOT/logs/startup-init.log" + if "$BIN/init-store.sh" >"$INIT_LOG" 2>&1; then + if ! bash "$SCRIPT_DIR/check-java-runtime-logs.sh" "$INIT_LOG"; then + exit 1 + fi pass "init-store.sh completed successfully" else + cat "$INIT_LOG" echo -e "${RED}ERROR:${NC} init-store.sh failed. Cannot run tests." exit 1 fi @@ -212,7 +235,8 @@ cleanup section "Test 1 — daemon mode regression" info "Starting in daemon mode (waiting up to ${STARTUP_WAIT}s for HTTP)..." -"$START_SCRIPT" -d true -t "$STARTUP_WAIT" >/dev/null 2>&1 +RUNTIME_STARTED=true +"$START_SCRIPT" -d true -t "$STARTUP_WAIT" >"$STARTUP_LOG" 2>&1 EXIT_CODE=$? if [[ $EXIT_CODE -eq 0 ]]; then @@ -259,7 +283,8 @@ cleanup section "Test 2 — foreground mode blocks until Java exits" info "Starting in foreground mode (backgrounded for observation)..." -"$START_SCRIPT" -d false >/dev/null 2>&1 & +RUNTIME_STARTED=true +"$START_SCRIPT" -d false >"$STARTUP_LOG" 2>&1 & SCRIPT_PID=$! info "Waiting up to ${STARTUP_WAIT}s for server to come up..." @@ -314,7 +339,8 @@ cleanup section "Test 3 — foreground PID tracking and exit propagation" info "Starting in foreground mode..." -"$START_SCRIPT" -d false >/dev/null 2>&1 & +RUNTIME_STARTED=true +"$START_SCRIPT" -d false >"$STARTUP_LOG" 2>&1 & SCRIPT_PID=$! info "Waiting up to ${STARTUP_WAIT}s for server..." @@ -372,7 +398,8 @@ info "Clearing crontab..." crontab -l 2>/dev/null | grep -v monitor-hugegraph | crontab - 2>/dev/null || true info "Starting in daemon mode with -m true (waiting up to ${STARTUP_WAIT}s)..." -"$START_SCRIPT" -d true -m true -t "$STARTUP_WAIT" >/dev/null 2>&1 +RUNTIME_STARTED=true +"$START_SCRIPT" -d true -m true -t "$STARTUP_WAIT" >"$STARTUP_LOG" 2>&1 EXIT_CODE=$? if [[ $EXIT_CODE -eq 0 ]]; then @@ -398,7 +425,8 @@ cleanup section "Test 5 — SIGTERM forwarded to Java in foreground mode" info "Starting in foreground mode..." -"$START_SCRIPT" -d false >/dev/null 2>&1 & +RUNTIME_STARTED=true +"$START_SCRIPT" -d false >"$STARTUP_LOG" 2>&1 & SCRIPT_PID=$! info "Waiting up to ${STARTUP_WAIT}s for server..." diff --git a/hugegraph-store/README.md b/hugegraph-store/README.md index bda2066c67..e742391348 100644 --- a/hugegraph-store/README.md +++ b/hugegraph-store/README.md @@ -252,6 +252,10 @@ bin/start-hugegraph-store.sh [-g GC_TYPE] [-j "JVM_OPTIONS"] [-d DAEMON] - `-j`: Custom JVM options (e.g., `-j "-Xmx16g -Xms8g"`) - `-d`: Daemon mode (`true` = daemon, `false` = foreground; default: `true`). Set to `false` when running under Docker or a process supervisor so the container exits if Java dies. +The launcher always loads `bin/jvm-module.options`, including with custom `JAVA_OPTIONS` or `-j`. +Keep this file in the distribution: it grants the Java 17 access required by Hessian Raft serialization; +startup fails if the file is missing or unreadable. Direct Java launches must include `@bin/jvm-module.options`. + On supported architectures, startup reuses a checksum-verified jemalloc library or tries to download it. The optional download allows 5 seconds to connect and 30 seconds for the complete transfer. If it fails or times out, Store continues without adding jemalloc to `LD_PRELOAD`; partial downloads are discarded. diff --git a/hugegraph-store/hg-store-dist/src/assembly/static/bin/jvm-module.options b/hugegraph-store/hg-store-dist/src/assembly/static/bin/jvm-module.options new file mode 100644 index 0000000000..b41209f448 --- /dev/null +++ b/hugegraph-store/hg-store-dist/src/assembly/static/bin/jvm-module.options @@ -0,0 +1,19 @@ +# +# 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. +# + +# Hessian reflects into StackTraceElement when serializing Raft operations. +--add-opens=java.base/java.lang=ALL-UNNAMED diff --git a/hugegraph-store/hg-store-dist/src/assembly/static/bin/start-hugegraph-store.sh b/hugegraph-store/hg-store-dist/src/assembly/static/bin/start-hugegraph-store.sh index 6846e1f47c..d1ccc651c7 100755 --- a/hugegraph-store/hg-store-dist/src/assembly/static/bin/start-hugegraph-store.sh +++ b/hugegraph-store/hg-store-dist/src/assembly/static/bin/start-hugegraph-store.sh @@ -36,6 +36,12 @@ LOGS="$TOP/logs" OUTPUT=${LOGS}/hugegraph-store-server.log GITHUB="https://github.com" PID_FILE="$BIN/pid" +JVM_MODULE_OPTIONS="$BIN/jvm-module.options" + +if [[ ! -f "$JVM_MODULE_OPTIONS" || ! -r "$JVM_MODULE_OPTIONS" ]]; then + echo "Error: JVM module options file is missing or unreadable: $JVM_MODULE_OPTIONS" >&2 + exit 1 +fi . "$BIN"/util.sh @@ -260,11 +266,11 @@ fi if [[ $DAEMON == "true" ]]; then echo "Starting HugeGraphStoreServer in daemon mode..." if [[ "${STDOUT_MODE:-false}" == "true" ]]; then - exec ${JAVA} -Dname="HugeGraphStore" ${JVM_OPTIONS} ${JAVA_OPTIONS} \ + exec ${JAVA} "@$JVM_MODULE_OPTIONS" -Dname="HugeGraphStore" ${JVM_OPTIONS} ${JAVA_OPTIONS} \ -Dspring.config.location=${CONF}/application.yml \ "${JAVA_MAIN[@]}" "$@" & else - exec ${JAVA} -Dname="HugeGraphStore" ${JVM_OPTIONS} ${JAVA_OPTIONS} \ + exec ${JAVA} "@$JVM_MODULE_OPTIONS" -Dname="HugeGraphStore" ${JVM_OPTIONS} ${JAVA_OPTIONS} \ -Dspring.config.location=${CONF}/application.yml \ "${JAVA_MAIN[@]}" "$@" >> ${OUTPUT} 2>&1 & fi @@ -278,11 +284,11 @@ else echo "$$" > "$PID_FILE" echo "[+pid] $$" if [[ "${STDOUT_MODE:-false}" == "true" ]]; then - exec ${JAVA} -Dname="HugeGraphStore" ${JVM_OPTIONS} ${JAVA_OPTIONS} \ + exec ${JAVA} "@$JVM_MODULE_OPTIONS" -Dname="HugeGraphStore" ${JVM_OPTIONS} ${JAVA_OPTIONS} \ -Dspring.config.location=${CONF}/application.yml \ "${JAVA_MAIN[@]}" "$@" else - exec ${JAVA} -Dname="HugeGraphStore" ${JVM_OPTIONS} ${JAVA_OPTIONS} \ + exec ${JAVA} "@$JVM_MODULE_OPTIONS" -Dname="HugeGraphStore" ${JVM_OPTIONS} ${JAVA_OPTIONS} \ -Dspring.config.location=${CONF}/application.yml \ "${JAVA_MAIN[@]}" "$@" >> ${OUTPUT} 2>&1 fi diff --git a/hugegraph-store/hg-store-dist/src/assembly/travis/test-store-preloads.sh b/hugegraph-store/hg-store-dist/src/assembly/travis/test-store-preloads.sh index e3fdc42c21..e5ad4e2495 100755 --- a/hugegraph-store/hg-store-dist/src/assembly/travis/test-store-preloads.sh +++ b/hugegraph-store/hg-store-dist/src/assembly/travis/test-store-preloads.sh @@ -50,7 +50,7 @@ run_launcher() { local -a provider_env=(-u TOPLINGDB_ROCKSDB_PROVIDER) [ "$provider" = default ] || provider_env=("TOPLINGDB_ROCKSDB_PROVIDER=$provider") mkdir -p "$top/bin" "$top/conf" "$top/lib" "$top/topling" - cp "$SOURCE_BIN/start-hugegraph-store.sh" "$top/bin/" + cp "$SOURCE_BIN/start-hugegraph-store.sh" "$SOURCE_BIN/jvm-module.options" "$top/bin/" cp "$SHARED_BIN/preload-topling.sh" "$top/bin/" cat > "$top/bin/util.sh" <<'MOCK' # Substitute only utility calls needed by this launcher fixture. diff --git a/hugegraph-store/hg-store-test/pom.xml b/hugegraph-store/hg-store-test/pom.xml index 17dbdb32ae..28ca7102bf 100644 --- a/hugegraph-store/hg-store-test/pom.xml +++ b/hugegraph-store/hg-store-test/pom.xml @@ -200,6 +200,12 @@ org.apache.maven.plugins maven-surefire-plugin + + + @{argLine} + "@${project.basedir}/../hg-store-dist/src/assembly/static/bin/jvm-module.options" + + org.apache.maven.surefire From b2277528e70983ae6c060d586d6d11e0cb32b136 Mon Sep 17 00:00:00 2001 From: imbajin Date: Tue, 13 Oct 2026 22:29:43 +0800 Subject: [PATCH 2/5] fix: simplify Java 17 runtime checks - add verified Server opens for HBase and SOFA RPC - inspect current service stdout and complete cluster logs - remove cluster archives and extra lifecycle changes - cover unreadable logs and required runtime options --- .github/workflows/cluster-test-ci.yml | 25 +--- .github/workflows/pd-store-ci.yml | 40 +++--- docs/ci.md | 4 +- docs/server-runtime.md | 4 + .../apache/hugegraph/ct/env/AbstractEnv.java | 27 +--- .../ct/node/AbstractNodeWrapper.java | 84 ++++++----- .../hugegraph/ct/env/AbstractEnvTest.java | 68 --------- .../ct/node/AbstractNodeWrapperTest.java | 132 +++++------------- .../hugegraph/ct/node/ModuleOptionsTest.java | 120 ---------------- .../BaseMultiClusterTest.java | 9 +- .../SimpleClusterTest/BaseSimpleTest.java | 13 +- .../assembly/static/bin/jvm-module.options | 4 + .../travis/check-java-runtime-logs.sh | 1 + .../travis/test-check-java-runtime-logs.sh | 3 +- .../travis/test-java-security-properties.sh | 2 + .../assembly/travis/test-start-hugegraph.sh | 6 +- .../conf/jvm-test-module.options | 4 +- 17 files changed, 144 insertions(+), 402 deletions(-) delete mode 100644 hugegraph-cluster-test/hugegraph-clustertest-minicluster/src/test/java/org/apache/hugegraph/ct/env/AbstractEnvTest.java delete mode 100644 hugegraph-cluster-test/hugegraph-clustertest-minicluster/src/test/java/org/apache/hugegraph/ct/node/ModuleOptionsTest.java diff --git a/.github/workflows/cluster-test-ci.yml b/.github/workflows/cluster-test-ci.yml index c1cb8f13d6..85c4ef8881 100644 --- a/.github/workflows/cluster-test-ci.yml +++ b/.github/workflows/cluster-test-ci.yml @@ -41,31 +41,17 @@ jobs: run: | set -o pipefail suite_log="$RUNNER_TEMP/simple-cluster-test.log" - node_logs="$RUNNER_TEMP/simple-cluster-node-logs" - test_status=0 timeout 45m mvn test -pl hugegraph-cluster-test/hugegraph-clustertest-test \ - -am -P simple-cluster-test -Dhugegraph.ct.logArchive="$node_logs" \ - 2>&1 | tee "$suite_log" || test_status=$? - runtime_status=0 - hugegraph-server/hugegraph-dist/src/assembly/travis/check-java-runtime-logs.sh \ - "$suite_log" "$node_logs" || runtime_status=$? - if [[ "$test_status" -ne 0 ]]; then exit "$test_status"; fi - exit "$runtime_status" + -am -P simple-cluster-test 2>&1 | tee "$suite_log" + hugegraph-server/hugegraph-dist/src/assembly/travis/check-java-runtime-logs.sh "$suite_log" - name: Run multi cluster test run: | set -o pipefail suite_log="$RUNNER_TEMP/multi-cluster-test.log" - node_logs="$RUNNER_TEMP/multi-cluster-node-logs" - test_status=0 timeout 45m mvn test -pl hugegraph-cluster-test/hugegraph-clustertest-test \ - -am -P multi-cluster-test -Dhugegraph.ct.logArchive="$node_logs" \ - 2>&1 | tee "$suite_log" || test_status=$? - runtime_status=0 - hugegraph-server/hugegraph-dist/src/assembly/travis/check-java-runtime-logs.sh \ - "$suite_log" "$node_logs" || runtime_status=$? - if [[ "$test_status" -ne 0 ]]; then exit "$test_status"; fi - exit "$runtime_status" + -am -P multi-cluster-test 2>&1 | tee "$suite_log" + hugegraph-server/hugegraph-dist/src/assembly/travis/check-java-runtime-logs.sh "$suite_log" - name: Show cluster diagnostics on failure if: failure() @@ -74,8 +60,7 @@ jobs: ps -ef | grep -E "HugeGraph|hg-|java" | grep -v grep || true echo "[ci] listening tcp ports:" (ss -ltnp || netstat -ltnp || true) 2>&1 - find hugegraph-cluster-test "$RUNNER_TEMP/simple-cluster-node-logs" \ - "$RUNNER_TEMP/multi-cluster-node-logs" -type f -name '*.log' | sort | while read -r log; do + find hugegraph-cluster-test -path '*/logs/*' -type f | sort | while read -r log; do echo "--- tail -n 200 $log ---" tail -n 200 "$log" || true done diff --git a/.github/workflows/pd-store-ci.yml b/.github/workflows/pd-store-ci.yml index 5ea47cc470..56edd7a85d 100644 --- a/.github/workflows/pd-store-ci.yml +++ b/.github/workflows/pd-store-ci.yml @@ -267,6 +267,8 @@ jobs: - name: Prepare env and service run: | $TRAVIS_DIR/start-pd.sh + VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout) + echo "PD_DIR=hugegraph-pd/apache-hugegraph-pd-$VERSION" >> "$GITHUB_ENV" - name: Run client test run: | @@ -274,7 +276,7 @@ jobs: RUNTIME_LOG=$(mktemp "$RUNNER_TEMP/java-runtime.XXXXXX") mvn test -pl hugegraph-pd/hg-pd-test -am \ -P pd-client-test -Djacoco.sessionId=pd-client-test 2>&1 | tee "$RUNTIME_LOG" - bash "$TRAVIS_DIR/check-java-runtime-logs.sh" "$RUNTIME_LOG" + bash "$TRAVIS_DIR/check-java-runtime-logs.sh" "$RUNTIME_LOG" "$PD_DIR/logs" - name: Run rest test run: | @@ -282,7 +284,7 @@ jobs: RUNTIME_LOG=$(mktemp "$RUNNER_TEMP/java-runtime.XXXXXX") mvn test -pl hugegraph-pd/hg-pd-test -am \ -P pd-rest-test -Djacoco.sessionId=pd-rest-test 2>&1 | tee "$RUNTIME_LOG" - bash "$TRAVIS_DIR/check-java-runtime-logs.sh" "$RUNTIME_LOG" + bash "$TRAVIS_DIR/check-java-runtime-logs.sh" "$RUNTIME_LOG" "$PD_DIR/logs" - name: Generate aggregate coverage report run: | @@ -439,6 +441,9 @@ jobs: run: | $TRAVIS_DIR/start-pd.sh $TRAVIS_DIR/start-store.sh + VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout) + echo "PD_DIR=hugegraph-pd/apache-hugegraph-pd-$VERSION" >> "$GITHUB_ENV" + echo "STORE_DIR=hugegraph-store/apache-hugegraph-store-$VERSION" >> "$GITHUB_ENV" - name: Run historical Store ingress tests run: | @@ -448,7 +453,7 @@ jobs: -Dtest=StoredRowIngressTest -Dsurefire.failIfNoSpecifiedTests=false \ -Djacoco.sessionId=store-node-test \ -Djacoco.destFile="$PWD/hugegraph-store/hg-store-test/target/store-node-test.exec" 2>&1 | tee "$RUNTIME_LOG" - bash "$TRAVIS_DIR/check-java-runtime-logs.sh" "$RUNTIME_LOG" + bash "$TRAVIS_DIR/check-java-runtime-logs.sh" "$RUNTIME_LOG" "$PD_DIR/logs" "$STORE_DIR/logs" - name: Run common test run: | @@ -456,7 +461,7 @@ jobs: RUNTIME_LOG=$(mktemp "$RUNNER_TEMP/java-runtime.XXXXXX") mvn test -pl hugegraph-store/hg-store-test -am \ -P store-common-test -Djacoco.sessionId=store-common-test 2>&1 | tee "$RUNTIME_LOG" - bash "$TRAVIS_DIR/check-java-runtime-logs.sh" "$RUNTIME_LOG" + bash "$TRAVIS_DIR/check-java-runtime-logs.sh" "$RUNTIME_LOG" "$PD_DIR/logs" "$STORE_DIR/logs" - name: Run client test run: | @@ -464,7 +469,7 @@ jobs: RUNTIME_LOG=$(mktemp "$RUNNER_TEMP/java-runtime.XXXXXX") mvn test -pl hugegraph-store/hg-store-test -am \ -P store-client-test -Djacoco.sessionId=store-client-test 2>&1 | tee "$RUNTIME_LOG" - bash "$TRAVIS_DIR/check-java-runtime-logs.sh" "$RUNTIME_LOG" + bash "$TRAVIS_DIR/check-java-runtime-logs.sh" "$RUNTIME_LOG" "$PD_DIR/logs" "$STORE_DIR/logs" - name: Run rocksdb test run: | @@ -472,7 +477,7 @@ jobs: RUNTIME_LOG=$(mktemp "$RUNNER_TEMP/java-runtime.XXXXXX") mvn test -pl hugegraph-store/hg-store-test -am \ -P store-rocksdb-test -Djacoco.sessionId=store-rocksdb-test 2>&1 | tee "$RUNTIME_LOG" - bash "$TRAVIS_DIR/check-java-runtime-logs.sh" "$RUNTIME_LOG" + bash "$TRAVIS_DIR/check-java-runtime-logs.sh" "$RUNTIME_LOG" "$PD_DIR/logs" "$STORE_DIR/logs" - name: Run raft-core test run: | @@ -480,7 +485,7 @@ jobs: RUNTIME_LOG=$(mktemp "$RUNNER_TEMP/java-runtime.XXXXXX") mvn test -pl hugegraph-store/hg-store-test -am \ -P store-raftcore-test -Djacoco.sessionId=store-raftcore-test 2>&1 | tee "$RUNTIME_LOG" - bash "$TRAVIS_DIR/check-java-runtime-logs.sh" "$RUNTIME_LOG" + bash "$TRAVIS_DIR/check-java-runtime-logs.sh" "$RUNTIME_LOG" "$PD_DIR/logs" "$STORE_DIR/logs" - name: Run core test run: | @@ -488,7 +493,7 @@ jobs: RUNTIME_LOG=$(mktemp "$RUNNER_TEMP/java-runtime.XXXXXX") mvn test -pl hugegraph-store/hg-store-test -am \ -P store-core-test -Djacoco.sessionId=store-core-test 2>&1 | tee "$RUNTIME_LOG" - bash "$TRAVIS_DIR/check-java-runtime-logs.sh" "$RUNTIME_LOG" + bash "$TRAVIS_DIR/check-java-runtime-logs.sh" "$RUNTIME_LOG" "$PD_DIR/logs" "$STORE_DIR/logs" - name: Run server lifecycle and shutdown tests run: | @@ -496,7 +501,7 @@ jobs: RUNTIME_LOG=$(mktemp "$RUNNER_TEMP/java-runtime.XXXXXX") mvn test -pl hugegraph-store/hg-store-test -am \ -P store-server-test -Djacoco.sessionId=store-server-test 2>&1 | tee "$RUNTIME_LOG" - bash "$TRAVIS_DIR/check-java-runtime-logs.sh" "$RUNTIME_LOG" + bash "$TRAVIS_DIR/check-java-runtime-logs.sh" "$RUNTIME_LOG" "$PD_DIR/logs" "$STORE_DIR/logs" - name: Generate aggregate coverage report run: | @@ -644,13 +649,16 @@ jobs: echo "[ci] open-file limit for HStore:" \ "soft=$(ulimit -Sn), hard=$(ulimit -Hn)" $TRAVIS_DIR/install-backend.sh $BACKEND + VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout) + echo "PD_DIR=hugegraph-pd/apache-hugegraph-pd-$VERSION" >> "$GITHUB_ENV" + echo "STORE_DIR=hugegraph-store/apache-hugegraph-store-$VERSION" >> "$GITHUB_ENV" - name: Run unit test run: | set -o pipefail RUNTIME_LOG=$(mktemp "$RUNNER_TEMP/java-runtime.XXXXXX") $TRAVIS_DIR/run-unit-test.sh $BACKEND 2>&1 | tee "$RUNTIME_LOG" - bash "$TRAVIS_DIR/check-java-runtime-logs.sh" "$RUNTIME_LOG" + bash "$TRAVIS_DIR/check-java-runtime-logs.sh" "$RUNTIME_LOG" "$PD_DIR/logs" "$STORE_DIR/logs" - name: Run core test # Bound stuck HStore scans so the diagnostic steps below can run @@ -662,14 +670,14 @@ jobs: set -o pipefail RUNTIME_LOG=$(mktemp "$RUNNER_TEMP/java-runtime.XXXXXX") $TRAVIS_DIR/run-core-test.sh $BACKEND 2>&1 | tee "$RUNTIME_LOG" - bash "$TRAVIS_DIR/check-java-runtime-logs.sh" "$RUNTIME_LOG" + bash "$TRAVIS_DIR/check-java-runtime-logs.sh" "$RUNTIME_LOG" "$PD_DIR/logs" "$STORE_DIR/logs" - name: Run api test run: | set -o pipefail RUNTIME_LOG=$(mktemp "$RUNNER_TEMP/java-runtime.XXXXXX") $TRAVIS_DIR/run-api-test.sh $BACKEND $REPORT_DIR 2>&1 | tee "$RUNTIME_LOG" - bash "$TRAVIS_DIR/check-java-runtime-logs.sh" "$RUNTIME_LOG" + bash "$TRAVIS_DIR/check-java-runtime-logs.sh" "$RUNTIME_LOG" "$PD_DIR/logs" "$STORE_DIR/logs" - name: Run raft test if: ${{ env.BACKEND == 'rocksdb' }} @@ -677,7 +685,7 @@ jobs: set -o pipefail RUNTIME_LOG=$(mktemp "$RUNNER_TEMP/java-runtime.XXXXXX") $TRAVIS_DIR/run-api-test-for-raft.sh $BACKEND $REPORT_DIR 2>&1 | tee "$RUNTIME_LOG" - bash "$TRAVIS_DIR/check-java-runtime-logs.sh" "$RUNTIME_LOG" + bash "$TRAVIS_DIR/check-java-runtime-logs.sh" "$RUNTIME_LOG" "$PD_DIR/logs" "$STORE_DIR/logs" # Include HStore in the upgrade compatibility gate. The dedicated HStore # service setup above makes its structure/process suites exercise the @@ -737,7 +745,7 @@ jobs: result = test.returncode sys.exit(result if result >= 0 else 128 - result) PYTHON - bash "$TRAVIS_DIR/check-java-runtime-logs.sh" "$RUNTIME_LOG" + bash "$TRAVIS_DIR/check-java-runtime-logs.sh" "$RUNTIME_LOG" "$PD_DIR/logs" "$STORE_DIR/logs" - name: Run TinkerPop process standard test if: ${{ env.RUN_TINKERPOP_TESTS == 'true' }} @@ -746,7 +754,7 @@ jobs: set -o pipefail RUNTIME_LOG=$(mktemp "$RUNNER_TEMP/java-runtime.XXXXXX") $TRAVIS_DIR/run-tinkerpop-test.sh $BACKEND process-standard 2>&1 | tee "$RUNTIME_LOG" - bash "$TRAVIS_DIR/check-java-runtime-logs.sh" "$RUNTIME_LOG" + bash "$TRAVIS_DIR/check-java-runtime-logs.sh" "$RUNTIME_LOG" "$PD_DIR/logs" "$STORE_DIR/logs" - name: Run TinkerPop process feature test if: ${{ env.RUN_TINKERPOP_TESTS == 'true' }} @@ -755,7 +763,7 @@ jobs: set -o pipefail RUNTIME_LOG=$(mktemp "$RUNNER_TEMP/java-runtime.XXXXXX") $TRAVIS_DIR/run-tinkerpop-test.sh $BACKEND process-feature 2>&1 | tee "$RUNTIME_LOG" - bash "$TRAVIS_DIR/check-java-runtime-logs.sh" "$RUNTIME_LOG" + bash "$TRAVIS_DIR/check-java-runtime-logs.sh" "$RUNTIME_LOG" "$PD_DIR/logs" "$STORE_DIR/logs" - name: Collect HStore diagnostics on failure or timeout if: ${{ failure() || cancelled() }} diff --git a/docs/ci.md b/docs/ci.md index d84ae3d6bf..d426ac4950 100644 --- a/docs/ci.md +++ b/docs/ci.md @@ -80,8 +80,8 @@ a rerun retains its original commit and event, subject to the preflight freshnes A positive suite passes only when its tests succeed and its current-run output has no Java runtime compatibility errors. Server, PD/Store, Commons, Struct and Cluster test steps capture both stdout and stderr before running `check-java-runtime-logs.sh`. -Startup and Console smoke tests also inspect their output before cleanup; Cluster nodes -archive logs before deletion through the test-only `hugegraph.ct.logArchive` property. +Startup and Console smoke tests also inspect their output before cleanup. Cluster nodes +forward their text runtime logs to the captured suite output before deletion. Missing or unreadable required logs fail the check. The gate rejects denied module access and unknown or removed JVM options, including diff --git a/docs/server-runtime.md b/docs/server-runtime.md index 5483702cb4..b212664afa 100644 --- a/docs/server-runtime.md +++ b/docs/server-runtime.md @@ -19,6 +19,10 @@ each launcher rejects a missing or unreadable file. Required module permissions are independent of caller options, so custom `JAVA_OPTIONS` or the Server launcher's `-j` option do not replace them. Gremlin Console retains its existing `JAVA_OPTIONS` defaults; store dumping retains its existing command-line interface. +The shared file opens `java.nio` for HBase's reflective `java.nio.Bits.unaligned()` +check during `HBasePlatformDependent` initialization, including on x86 Java 17. +It also opens `java.lang` for SOFA RPC's `ProviderInvoker` initialization, which +reflects on `Throwable.cause`. PD and Store use their own `bin/jvm-module.options` for the module permission needed by Hessian serialization on Java 17. Their tests and cluster launchers use the same diff --git a/hugegraph-cluster-test/hugegraph-clustertest-minicluster/src/main/java/org/apache/hugegraph/ct/env/AbstractEnv.java b/hugegraph-cluster-test/hugegraph-clustertest-minicluster/src/main/java/org/apache/hugegraph/ct/env/AbstractEnv.java index f9c5b5e5c0..ccf0582a00 100644 --- a/hugegraph-cluster-test/hugegraph-clustertest-minicluster/src/main/java/org/apache/hugegraph/ct/env/AbstractEnv.java +++ b/hugegraph-cluster-test/hugegraph-clustertest-minicluster/src/main/java/org/apache/hugegraph/ct/env/AbstractEnv.java @@ -147,27 +147,14 @@ private static void sleepBeforeRetry() { } public void stopCluster() { - List nodes = new ArrayList<>(serverNodeWrappers); - nodes.addAll(storeNodeWrappers); - nodes.addAll(pdNodeWrappers); - stopNodes(nodes); - } - - static void stopNodes(List nodes) { - AssertionError failure = null; - for (AbstractNodeWrapper node : nodes) { - try { - node.stop(); - } catch (RuntimeException | AssertionError e) { - if (failure == null) { - failure = new AssertionError("Cluster cleanup failed", e); - } else { - failure.addSuppressed(e); - } - } + for (ServerNodeWrapper serverNodeWrapper : serverNodeWrappers) { + serverNodeWrapper.stop(); } - if (failure != null) { - throw failure; + for (StoreNodeWrapper storeNodeWrapper : storeNodeWrappers) { + storeNodeWrapper.stop(); + } + for (PDNodeWrapper pdNodeWrapper : pdNodeWrappers) { + pdNodeWrapper.stop(); } } diff --git a/hugegraph-cluster-test/hugegraph-clustertest-minicluster/src/main/java/org/apache/hugegraph/ct/node/AbstractNodeWrapper.java b/hugegraph-cluster-test/hugegraph-clustertest-minicluster/src/main/java/org/apache/hugegraph/ct/node/AbstractNodeWrapper.java index 701bae2afa..15efdfc8b4 100644 --- a/hugegraph-cluster-test/hugegraph-clustertest-minicluster/src/main/java/org/apache/hugegraph/ct/node/AbstractNodeWrapper.java +++ b/hugegraph-cluster-test/hugegraph-clustertest-minicluster/src/main/java/org/apache/hugegraph/ct/node/AbstractNodeWrapper.java @@ -21,6 +21,7 @@ import java.io.File; import java.io.IOException; +import java.io.UncheckedIOException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.NoSuchFileException; @@ -167,34 +168,6 @@ protected String moduleOptions(Path options) throws IOException { return "@" + options.toAbsolutePath(); } - private void archiveLogs() throws IOException { - String archive = System.getProperty("hugegraph.ct.logArchive"); - if (archive == null || archive.isEmpty()) { - return; - } - Path logDirectory = Paths.get(getLogPath()).getParent(); - if (!Files.isRegularFile(Paths.get(getLogPath()))) { - throw new IOException("Missing node startup log: " + getLogPath()); - } - Path archiveRoot = Paths.get(archive); - Files.createDirectories(archiveRoot); - Path destination = Files.createTempDirectory(archiveRoot, - "cluster-" + this.clusterIndex + - "-" + getID() + "-"); - try (Stream logs = Files.walk(logDirectory)) { - for (Path log : (Iterable) logs.filter(Files::isRegularFile)::iterator) { - String name = log.getFileName().toString(); - if (!name.endsWith(".log") && !name.endsWith(".out") && !name.endsWith(".err") && - !name.endsWith(".txt") && !name.matches(".*\\.log\\.[0-9]+")) { - continue; - } - Path target = destination.resolve(logDirectory.relativize(log)); - Files.createDirectories(target.getParent()); - Files.copy(log, target); - } - } - } - public void stop() { if (this.instance == null) { return; @@ -202,28 +175,53 @@ public void stop() { if (!this.instance.isAlive()) { System.out.printf("[cluster-test] %s stopped unexpectedly: %s%n", getID(), processStatus()); - dumpLog(); - } else { - this.instance.destroy(); - try { - if (!this.instance.waitFor(20, TimeUnit.SECONDS)) { - this.instance.destroyForcibly().waitFor(10, TimeUnit.SECONDS); - } - } catch (InterruptedException e) { - this.instance.destroyForcibly(); - Thread.currentThread().interrupt(); - LOG.error("Waiting node to shutdown error.", e); + if (dumpRuntimeLogs()) { + deleteDir(); } + return; } + this.instance.destroy(); try { - archiveLogs(); - } catch (IOException e) { - throw new AssertionError("Archive node logs failed: " + getID(), e); - } finally { + if (!this.instance.waitFor(20, TimeUnit.SECONDS)) { + this.instance.destroyForcibly().waitFor(10, TimeUnit.SECONDS); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + LOG.error("Waiting node to shutdown error.", e); + } + if (dumpRuntimeLogs()) { deleteDir(); } } + private boolean dumpRuntimeLogs() { + Path startupLog = Paths.get(getLogPath()); + try { + if (this.instance.isAlive() || !Files.isReadable(startupLog)) { + throw new IOException("Node still running or startup log unavailable: " + getID()); + } + // Stream complete text logs into the suite output before deletion. + // The suite's existing checker validates them without an archive. + try (Stream logs = Files.walk(startupLog.getParent())) { + for (Path log : (Iterable) logs.filter(Files::isRegularFile)::iterator) { + if (!log.getFileName().toString().matches(".*(\\.log(\\.[0-9]+)?|\\.out|\\.err|\\.txt)")) { + continue; + } + System.out.println("[cluster-test] runtime log: " + log); + try (Stream lines = Files.lines(log, StandardCharsets.UTF_8)) { + lines.forEach(System.out::println); + } + } + } + return true; + } catch (IOException | UncheckedIOException e) { + // Keep the original files and continue stopping the other nodes. + // This explicit marker makes the suite's log checker fail. + System.out.println("Unable to inspect cluster runtime logs: " + e.getMessage()); + return false; + } + } + public boolean isAlive() { return this.instance != null && this.instance.isAlive(); } diff --git a/hugegraph-cluster-test/hugegraph-clustertest-minicluster/src/test/java/org/apache/hugegraph/ct/env/AbstractEnvTest.java b/hugegraph-cluster-test/hugegraph-clustertest-minicluster/src/test/java/org/apache/hugegraph/ct/env/AbstractEnvTest.java deleted file mode 100644 index 99cab1a9d5..0000000000 --- a/hugegraph-cluster-test/hugegraph-clustertest-minicluster/src/test/java/org/apache/hugegraph/ct/env/AbstractEnvTest.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * 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. - */ - -package org.apache.hugegraph.ct.env; - -import java.util.Arrays; - -import org.apache.hugegraph.ct.node.AbstractNodeWrapper; -import org.junit.Assert; -import org.junit.Test; - -public class AbstractEnvTest { - - @Test - public void testCleanupFailureDoesNotSkipRemainingNodes() { - TestNode first = new TestNode(true); - TestNode second = new TestNode(false); - TestNode third = new TestNode(true); - AssertionError failure = Assert.assertThrows(AssertionError.class, - () -> AbstractEnv.stopNodes(Arrays.asList(first, second, third))); - Assert.assertTrue(first.stopped); - Assert.assertTrue(second.stopped); - Assert.assertTrue(third.stopped); - Assert.assertEquals(1, failure.getSuppressed().length); - } - - private static class TestNode extends AbstractNodeWrapper { - - private final boolean fail; - private boolean stopped; - - private TestNode(boolean fail) { - this.fail = fail; - } - - @Override - public void start() { - throw new UnsupportedOperationException(); - } - - @Override - public void stop() { - this.stopped = true; - if (this.fail) { - throw new AssertionError("Archive node logs failed"); - } - } - - @Override - public String getID() { - return "test"; - } - } -} diff --git a/hugegraph-cluster-test/hugegraph-clustertest-minicluster/src/test/java/org/apache/hugegraph/ct/node/AbstractNodeWrapperTest.java b/hugegraph-cluster-test/hugegraph-clustertest-minicluster/src/test/java/org/apache/hugegraph/ct/node/AbstractNodeWrapperTest.java index cf52c4347b..485810112b 100644 --- a/hugegraph-cluster-test/hugegraph-clustertest-minicluster/src/test/java/org/apache/hugegraph/ct/node/AbstractNodeWrapperTest.java +++ b/hugegraph-cluster-test/hugegraph-clustertest-minicluster/src/test/java/org/apache/hugegraph/ct/node/AbstractNodeWrapperTest.java @@ -19,15 +19,13 @@ import java.io.ByteArrayOutputStream; import java.io.File; +import java.io.IOException; import java.io.PrintStream; import java.net.URISyntaxException; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.List; -import java.util.stream.Collectors; -import java.util.stream.Stream; import java.util.concurrent.TimeUnit; import org.junit.Assert; @@ -85,108 +83,54 @@ public void testStopDumpsLogOfUnexpectedlyExitedProcess() throws Exception { } @Test - public void testArchiveBeforeDeletingExitedNode() throws Exception { - File nodeDirectory = newNodeDirectory(); - TestNodeWrapper wrapper = new TestNodeWrapper(nodeDirectory); - String failure = "retained startup diagnostics"; - Files.writeString(Paths.get(wrapper.getLogPath()), failure); - Path raftLog = nodeDirectory.toPath().resolve("logs/raft.log"); - Files.writeString(raftLog, "raft diagnostics"); - Path archive = temporaryFolder.newFolder().toPath(); - String originalArchive = System.getProperty("hugegraph.ct.logArchive"); - try { - System.setProperty("hugegraph.ct.logArchive", archive.toString()); - wrapper.attach(startExitedProcess()); - wrapper.stop(); - Assert.assertFalse(nodeDirectory.exists()); - List archived = archivedFiles(archive); - Assert.assertEquals(2, archived.size()); - Assert.assertTrue(archived.stream().anyMatch(path -> path.getFileName().toString() - .equals("raft.log"))); - for (Path path : archived) { - if (path.getFileName().toString().endsWith("-start.log")) { - Assert.assertEquals(failure, Files.readString(path)); - } - } - } finally { - restoreArchiveProperty(originalArchive); - } - } - - @Test - public void testArchiveDoesNotOverwriteRestartsOrClusters() throws Exception { - Path archive = temporaryFolder.newFolder().toPath(); - String originalArchive = System.getProperty("hugegraph.ct.logArchive"); - try { - System.setProperty("hugegraph.ct.logArchive", archive.toString()); - for (int cluster : new int[]{1, 1, 2}) { - TestNodeWrapper wrapper = new TestNodeWrapper(newNodeDirectory()); - wrapper.clusterIndex = cluster; - Files.writeString(Paths.get(wrapper.getLogPath()), "cluster " + cluster); - wrapper.attach(startExitedProcess()); - wrapper.stop(); - } - Assert.assertEquals(3, archivedFiles(archive).size()); - } finally { - restoreArchiveProperty(originalArchive); - } - } - - @Test - public void testArchiveFailureStillStopsAndDeletesNode() throws Exception { + public void testModuleOptionsRejectMissingFile() throws Exception { TestNodeWrapper wrapper = new TestNodeWrapper(newNodeDirectory()); - Files.writeString(Paths.get(wrapper.getLogPath()), START_LINE); - String originalArchive = System.getProperty("hugegraph.ct.logArchive"); - Process process = startSleeperProcess(); - wrapper.attach(process); - try { - System.setProperty("hugegraph.ct.logArchive", temporaryFolder.newFile().toString()); - Assert.assertThrows(AssertionError.class, wrapper::stop); - Assert.assertFalse(process.isAlive()); - Assert.assertFalse(Files.exists(Paths.get(wrapper.getNodePath()))); - } finally { - process.destroyForcibly(); - restoreArchiveProperty(originalArchive); - } + Path options = temporaryFolder.newFile("jvm-module.options").toPath(); + Assert.assertEquals("@" + options.toAbsolutePath(), wrapper.moduleOptions(options)); + Files.delete(options); + Assert.assertThrows(IOException.class, () -> wrapper.moduleOptions(options)); } @Test - public void testStopWithoutArchiveKeepsDefaultCleanup() throws Exception { - TestNodeWrapper wrapper = new TestNodeWrapper(newNodeDirectory()); - Files.writeString(Paths.get(wrapper.getLogPath()), START_LINE); - String originalArchive = System.getProperty("hugegraph.ct.logArchive"); + public void testStopForwardsStartupLogBeforeCleanup() throws Exception { + File nodeDirectory = newNodeDirectory(); + TestNodeWrapper wrapper = new TestNodeWrapper(nodeDirectory); + Files.writeString(Paths.get(wrapper.getLogPath()), + "startup diagnostic\n" + "operation succeeded\n".repeat(250)); + wrapper.attach(startSleeperProcess()); + PrintStream originalOut = System.out; + ByteArrayOutputStream output = new ByteArrayOutputStream(); try { - System.clearProperty("hugegraph.ct.logArchive"); - wrapper.attach(startExitedProcess()); + System.setOut(new PrintStream(output, true, StandardCharsets.UTF_8.name())); wrapper.stop(); - Assert.assertFalse(Files.exists(Paths.get(wrapper.getNodePath()))); } finally { - restoreArchiveProperty(originalArchive); + System.setOut(originalOut); } + Assert.assertTrue(output.toString(StandardCharsets.UTF_8.name()).contains("startup diagnostic")); + Assert.assertFalse(nodeDirectory.exists()); } @Test - public void testModuleOptionsRequireReadableFile() throws Exception { - TestNodeWrapper wrapper = new TestNodeWrapper(newNodeDirectory()); - Path options = temporaryFolder.newFile("jvm-module.options").toPath(); - Assert.assertEquals("@" + options.toAbsolutePath(), wrapper.moduleOptions(options)); - Files.delete(options); - Assert.assertThrows(java.io.IOException.class, () -> wrapper.moduleOptions(options)); - Assert.assertThrows(java.io.IOException.class, - () -> wrapper.moduleOptions(temporaryFolder.getRoot().toPath())); - } - - private static List archivedFiles(Path archive) throws Exception { - try (Stream files = Files.walk(archive)) { - return files.filter(Files::isRegularFile).collect(Collectors.toList()); - } - } - - private static void restoreArchiveProperty(String original) { - if (original == null) { - System.clearProperty("hugegraph.ct.logArchive"); - } else { - System.setProperty("hugegraph.ct.logArchive", original); + public void testStopRetainsDirectoryWhenLogIsUnavailable() throws Exception { + for (boolean malformed : new boolean[]{false, true}) { + File nodeDirectory = newNodeDirectory(); + TestNodeWrapper wrapper = new TestNodeWrapper(nodeDirectory); + if (malformed) { + Files.write(Paths.get(wrapper.getLogPath()), new byte[]{(byte) 0xff}); + } + wrapper.attach(startSleeperProcess()); + PrintStream originalOut = System.out; + ByteArrayOutputStream output = new ByteArrayOutputStream(); + try { + System.setOut(new PrintStream(output, true, StandardCharsets.UTF_8.name())); + wrapper.stop(); + } finally { + System.setOut(originalOut); + } + Assert.assertFalse(wrapper.isAlive()); + Assert.assertTrue(nodeDirectory.exists()); + Assert.assertTrue(output.toString(StandardCharsets.UTF_8.name()) + .contains("Unable to inspect cluster runtime logs:")); } } diff --git a/hugegraph-cluster-test/hugegraph-clustertest-minicluster/src/test/java/org/apache/hugegraph/ct/node/ModuleOptionsTest.java b/hugegraph-cluster-test/hugegraph-clustertest-minicluster/src/test/java/org/apache/hugegraph/ct/node/ModuleOptionsTest.java deleted file mode 100644 index 432cfa74df..0000000000 --- a/hugegraph-cluster-test/hugegraph-clustertest-minicluster/src/test/java/org/apache/hugegraph/ct/node/ModuleOptionsTest.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * 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. - */ - -package org.apache.hugegraph.ct.node; - -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.List; -import java.util.concurrent.TimeUnit; - -import org.junit.Assert; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; - -public class ModuleOptionsTest { - - @Rule - public TemporaryFolder temporaryFolder = new TemporaryFolder(); - - @Test - public void testPDUsesPackagedModuleOptions() throws Exception { - CapturingPD wrapper = new CapturingPD(); - Path options = prepareDistribution(wrapper); - wrapper.start(); - Assert.assertTrue(wrapper.command.contains("@" + options.toAbsolutePath())); - Assert.assertFalse(wrapper.command.stream().anyMatch(arg -> arg.startsWith("--add-opens"))); - Assert.assertTrue(wrapper.instance.waitFor(10, TimeUnit.SECONDS)); - Files.delete(options); - AssertionError error = Assert.assertThrows(AssertionError.class, wrapper::start); - Assert.assertTrue(error.getMessage().contains("JVM module options")); - } - - @Test - public void testStoreUsesPackagedModuleOptions() throws Exception { - CapturingStore wrapper = new CapturingStore(); - Path options = prepareDistribution(wrapper); - wrapper.start(); - Assert.assertTrue(wrapper.command.contains("@" + options.toAbsolutePath())); - Assert.assertFalse(wrapper.command.stream().anyMatch(arg -> arg.startsWith("--add-opens"))); - Assert.assertTrue(wrapper.instance.waitFor(10, TimeUnit.SECONDS)); - Files.delete(options); - AssertionError error = Assert.assertThrows(AssertionError.class, wrapper::start); - Assert.assertTrue(error.getMessage().contains("JVM module options")); - } - - private Path prepareDistribution(AbstractNodeWrapper wrapper) throws Exception { - Path distribution = temporaryFolder.newFolder().toPath(); - Files.createDirectory(distribution.resolve("lib")); - Files.createDirectory(distribution.resolve("bin")); - Path options = distribution.resolve("bin/jvm-module.options"); - Files.writeString(options, "--add-opens=java.base/java.lang=ALL-UNNAMED\n"); - wrapper.updateWorkPath(distribution.resolve("lib").toString() + File.separator); - return options; - } - - private static ProcessBuilder harmlessProcess() { - return new ProcessBuilder(Paths.get(System.getProperty("java.home"), "bin", "java").toString(), - "-version"); - } - - private static class CapturingPD extends PDNodeWrapper { - - private List command; - - @Override - public void createNodeDir(Path source, String destination) { - // Distribution files are provided by the test. - } - - @Override - public void createLogDir() { - // No service process is launched. - } - - @Override - protected ProcessBuilder runCmd(List command, File stdout) throws IOException { - this.command = command; - return harmlessProcess(); - } - } - - private static class CapturingStore extends StoreNodeWrapper { - - private List command; - - @Override - public void createNodeDir(Path source, String destination) { - // Distribution files are provided by the test. - } - - @Override - public void createLogDir() { - // No service process is launched. - } - - @Override - protected ProcessBuilder runCmd(List command, File stdout) throws IOException { - this.command = command; - return harmlessProcess(); - } - } -} diff --git a/hugegraph-cluster-test/hugegraph-clustertest-test/src/main/java/org/apache/hugegraph/MultiClusterTest/BaseMultiClusterTest.java b/hugegraph-cluster-test/hugegraph-clustertest-test/src/main/java/org/apache/hugegraph/MultiClusterTest/BaseMultiClusterTest.java index 25be1f637d..0f2b0d492a 100644 --- a/hugegraph-cluster-test/hugegraph-clustertest-test/src/main/java/org/apache/hugegraph/MultiClusterTest/BaseMultiClusterTest.java +++ b/hugegraph-cluster-test/hugegraph-clustertest-test/src/main/java/org/apache/hugegraph/MultiClusterTest/BaseMultiClusterTest.java @@ -63,12 +63,9 @@ public static void initEnv() { @AfterClass public static void clearEnv() { - try { - env.stopCluster(); - } finally { - for (RestClient client : clients) { - client.close(); - } + env.stopCluster(); + for (RestClient client : clients) { + client.close(); } } diff --git a/hugegraph-cluster-test/hugegraph-clustertest-test/src/main/java/org/apache/hugegraph/SimpleClusterTest/BaseSimpleTest.java b/hugegraph-cluster-test/hugegraph-clustertest-test/src/main/java/org/apache/hugegraph/SimpleClusterTest/BaseSimpleTest.java index 08419ae061..996856f86c 100644 --- a/hugegraph-cluster-test/hugegraph-clustertest-test/src/main/java/org/apache/hugegraph/SimpleClusterTest/BaseSimpleTest.java +++ b/hugegraph-cluster-test/hugegraph-clustertest-test/src/main/java/org/apache/hugegraph/SimpleClusterTest/BaseSimpleTest.java @@ -72,14 +72,11 @@ public static void initEnv() { @AfterClass public static void clearEnv() throws InterruptedException { - try { - env.stopCluster(); - Thread.sleep(2000); - } finally { - if (client != null) { - client.close(); - client = null; - } + env.stopCluster(); + Thread.sleep(2000); + if (client != null) { + client.close(); + client = null; } } diff --git a/hugegraph-server/hugegraph-dist/src/assembly/static/bin/jvm-module.options b/hugegraph-server/hugegraph-dist/src/assembly/static/bin/jvm-module.options index eeb6114c5c..9da05e1d8e 100644 --- a/hugegraph-server/hugegraph-dist/src/assembly/static/bin/jvm-module.options +++ b/hugegraph-server/hugegraph-dist/src/assembly/static/bin/jvm-module.options @@ -18,3 +18,7 @@ --add-exports=java.base/jdk.internal.reflect=ALL-UNNAMED --add-modules=jdk.unsupported --add-exports=java.base/sun.nio.ch=ALL-UNNAMED +# HBasePlatformDependent reflects on java.nio.Bits.unaligned() during initialization. +--add-opens=java.base/java.nio=ALL-UNNAMED +# SOFA RPC ProviderInvoker reflects on Throwable.cause during initialization. +--add-opens=java.base/java.lang=ALL-UNNAMED diff --git a/hugegraph-server/hugegraph-dist/src/assembly/travis/check-java-runtime-logs.sh b/hugegraph-server/hugegraph-dist/src/assembly/travis/check-java-runtime-logs.sh index bd309a9766..8255743f29 100755 --- a/hugegraph-server/hugegraph-dist/src/assembly/travis/check-java-runtime-logs.sh +++ b/hugegraph-server/hugegraph-dist/src/assembly/travis/check-java-runtime-logs.sh @@ -24,6 +24,7 @@ PATTERN='java\.lang\.reflect\.InaccessibleObjectException:' PATTERN+='|java\.lang\.(IllegalAccessError|IllegalAccessException):.*module .*does not' PATTERN+='|Unrecognized VM option|Unrecognized option:' PATTERN+='|Ignoring option .*support was removed|VM option .*is (not supported|obsolete|removed)' +PATTERN+='|Unable to inspect cluster runtime logs:' if [[ "${1:-}" == --native ]]; then # Keep native-linkage checks out of suites with intentional missing classes. PATTERN+='|undefined symbol|UnsatisfiedLinkError|UnsupportedClassVersionError|NoClassDefFoundError' diff --git a/hugegraph-server/hugegraph-dist/src/assembly/travis/test-check-java-runtime-logs.sh b/hugegraph-server/hugegraph-dist/src/assembly/travis/test-check-java-runtime-logs.sh index 4220cd259f..f7536c0050 100755 --- a/hugegraph-server/hugegraph-dist/src/assembly/travis/test-check-java-runtime-logs.sh +++ b/hugegraph-server/hugegraph-dist/src/assembly/travis/test-check-java-runtime-logs.sh @@ -46,7 +46,8 @@ for MESSAGE in \ 'java.lang.IllegalAccessException: module java.base does not export p to unnamed module' \ "Unrecognized VM option 'OldGCOption'" \ 'Unrecognized option: --unknown-option' \ - 'OpenJDK warning: Ignoring option --illegal-access=permit; support was removed in 17.0'; do + 'OpenJDK warning: Ignoring option --illegal-access=permit; support was removed in 17.0' \ + 'Unable to inspect cluster runtime logs: Node still running or startup log unavailable'; do # A child returning success after logging only to stderr reproduces the # failure mode that result assertions and exit-code checks cannot catch. bash -c 'printf "%s\n" "$1" >&2' bash "$MESSAGE" >"$WORK_DIR/positive.log" 2>&1 diff --git a/hugegraph-server/hugegraph-dist/src/assembly/travis/test-java-security-properties.sh b/hugegraph-server/hugegraph-dist/src/assembly/travis/test-java-security-properties.sh index fc496f8835..b5d74bc928 100755 --- a/hugegraph-server/hugegraph-dist/src/assembly/travis/test-java-security-properties.sh +++ b/hugegraph-server/hugegraph-dist/src/assembly/travis/test-java-security-properties.sh @@ -141,6 +141,8 @@ assert_argument "--add-exports=java.base/jdk.internal.reflect=ALL-UNNAMED" \ assert_argument "--add-modules=jdk.unsupported" "$JVM_MODULE_OPTIONS" assert_argument "--add-exports=java.base/sun.nio.ch=ALL-UNNAMED" \ "$JVM_MODULE_OPTIONS" +assert_argument "--add-opens=java.base/java.nio=ALL-UNNAMED" "$JVM_MODULE_OPTIONS" +assert_argument "--add-opens=java.base/java.lang=ALL-UNNAMED" "$JVM_MODULE_OPTIONS" if [[ -n "$SOURCE_ROOT_INPUT" ]]; then if [[ ! -d "$SOURCE_ROOT_INPUT" ]]; then diff --git a/hugegraph-server/hugegraph-dist/src/assembly/travis/test-start-hugegraph.sh b/hugegraph-server/hugegraph-dist/src/assembly/travis/test-start-hugegraph.sh index f06d2dc24a..1f54b81f90 100755 --- a/hugegraph-server/hugegraph-dist/src/assembly/travis/test-start-hugegraph.sh +++ b/hugegraph-server/hugegraph-dist/src/assembly/travis/test-start-hugegraph.sh @@ -107,7 +107,8 @@ cleanup() { if [[ "$RUNTIME_STARTED" == true ]]; then local logs=("$STARTUP_LOG") if [[ "${STDOUT_MODE:-false}" != true ]]; then - logs+=("$HUGEGRAPH_ROOT/logs/hugegraph-server.log") + logs+=("$HUGEGRAPH_ROOT/logs/hugegraph-server.log" + "$HUGEGRAPH_ROOT/logs/hugegraph-server-stdout.log") fi if bash "$SCRIPT_DIR/check-java-runtime-logs.sh" "${logs[@]}"; then pass "startup has no Java runtime compatibility errors" @@ -118,7 +119,8 @@ cleanup() { fi # Each positive start must be checked independently of previous runs. mkdir -p "$HUGEGRAPH_ROOT/logs" - rm -f "$STARTUP_LOG" "$HUGEGRAPH_ROOT/logs/hugegraph-server.log" + rm -f "$STARTUP_LOG" "$HUGEGRAPH_ROOT/logs/hugegraph-server.log" \ + "$HUGEGRAPH_ROOT/logs/hugegraph-server-stdout.log" } # Wait until server HTTP endpoint responds (200 or 401) or timeout diff --git a/hugegraph-server/hugegraph-test/conf/jvm-test-module.options b/hugegraph-server/hugegraph-test/conf/jvm-test-module.options index 32b2b8b036..ab1bbb0162 100644 --- a/hugegraph-server/hugegraph-test/conf/jvm-test-module.options +++ b/hugegraph-server/hugegraph-test/conf/jvm-test-module.options @@ -13,8 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Test-only access required by TinkerPop and its Kryo dependencies on -# Java 17. Keep these permissions out of the Server runtime argument file. +# Access required by TinkerPop and its Kryo dependencies in Java 17 tests. +# Runtime argument files contain only the subset required by production dependencies. --add-exports=java.base/sun.security.x509=ALL-UNNAMED --add-exports=java.base/sun.security.action=ALL-UNNAMED --add-opens=java.base/java.io=ALL-UNNAMED From 9e6053722bab4e9b366733045055850ae23f8cab Mon Sep 17 00:00:00 2001 From: imbajin Date: Wed, 14 Oct 2026 04:22:31 +0800 Subject: [PATCH 3/5] fix: preserve runtime checks after CI rebase - route log checker contracts to their executing suites - keep test-only changes out of post-merge smoke - retain native fixtures and avoid extra RISC-V JVM launch --- .github/scripts/ci-policy.py | 8 ++++++-- .github/scripts/test_ci_policy.py | 14 +++++++++++++- .github/workflows/riscv64-ci.yml | 8 ++++---- .../assembly/travis/test-riscv64-java-runtime.sh | 16 +++++++++++++--- 4 files changed, 36 insertions(+), 10 deletions(-) diff --git a/.github/scripts/ci-policy.py b/.github/scripts/ci-policy.py index dd2fd27331..5b6f961e08 100644 --- a/.github/scripts/ci-policy.py +++ b/.github/scripts/ci-policy.py @@ -90,6 +90,9 @@ TRAVIS = "hugegraph-server/hugegraph-dist/src/assembly/travis/" # Contract tests follow their executing suites, without runtime consumer fan-out. CONTRACT_SUITES = { + TRAVIS + "test-check-java-runtime-logs.sh": {"struct"}, + TRAVIS + "check-java-runtime-logs.sh": {"server", "commons", "struct", "pd", "store", "hstore", "cluster", + "macos", "macos_full", "hbase", "riscv64"}, TRAVIS + "test-stop-hugegraph-store.sh": {"struct"}, TRAVIS + "test-check-jacoco-report.sh": {"struct"}, TRAVIS + "test-codecov-upload-config.sh": {"struct"}, @@ -238,13 +241,14 @@ def event_selection(project, paths, event, event_name): if preset not in PRESETS: raise ValueError("unknown test-set: " + str(preset)) return set(PRESETS[preset]) - selected = select(project, paths) if event_name == "push" and event.get("ref") == "refs/heads/master": + # Test contracts do not change deployed runtime inputs. + selected = select(project, [path for path in paths if path not in CONTRACT_SUITES]) smoke = selected.intersection({"docker", "helm", "dependency_license", "macos", "distributed_smoke"}) if selected.intersection({"server", "commons", "struct", "pd", "store", "hstore", "cluster"}): smoke.add("distributed_smoke") return smoke - return selected + return select(project, paths) def suites(project, selected): diff --git a/.github/scripts/test_ci_policy.py b/.github/scripts/test_ci_policy.py index a60486ebec..25067c20c7 100644 --- a/.github/scripts/test_ci_policy.py +++ b/.github/scripts/test_ci_policy.py @@ -373,6 +373,7 @@ def test_legacy_memory_bridge_requires_real_results_and_retires_without_a_runner def test_contract_scripts_reach_their_actual_inner_jobs_and_steps(self): workflows = Path(__file__).resolve().parents[1] / "workflows" cases = [("test-stop-hugegraph-store.sh", {"struct"}), + ("test-check-java-runtime-logs.sh", {"struct"}), ("test-check-jacoco-report.sh", {"struct"}), ("test-codecov-upload-config.sh", {"struct"}), ("test-riscv64-java-runtime.sh", {"server", "riscv64"})] @@ -397,7 +398,7 @@ def test_contract_scripts_reach_their_actual_inner_jobs_and_steps(self): condition = condition.replace("inputs.run-struct", str(inputs["struct"])) condition = condition.replace("inputs.run-store", str(inputs["store"])) self.assertTrue(eval(condition.replace("||", "or"), {"__builtins__": {}})) - step = next(step for step in struct.split("\n - ") if script in step) + step = next(step for step in struct.split("\n - ") if Path(script).name in step) self.assertNotRegex(step, r"(?m)^ if:") else: self.assertIn("server-rocksdb", callers) @@ -413,6 +414,17 @@ def test_contract_scripts_reach_their_actual_inner_jobs_and_steps(self): {"__builtins__": {}})) mixed = self.plan_for([policy.TRAVIS + "test-stop-hugegraph-store.sh", "hugegraph-pd/src/A.java"]) self.assertTrue({"struct", "pd", "store", "hstore", "cluster"}.issubset(mixed["selected"])) + checker = self.plan_for([policy.TRAVIS + "check-java-runtime-logs.sh"]) + self.assertEqual({"build-server", "server-rocksdb", "commons", "pd_store", "cluster", "compatibility"}, + self.coordinator_jobs(checker)) + self.assertTrue(checker["hbase"] and checker["riscv64"] and checker["macos_full"]) + master = {"ref": "refs/heads/master"} + for script in policy.CONTRACT_SUITES: + self.assertEqual([], self.plan_for([script], "push", master)["expected"]) + runtime = "hugegraph-server/hugegraph-core/src/main/A.java" + self.assertEqual(self.plan_for([runtime], "push", master)["selected"], + self.plan_for([runtime, policy.TRAVIS + "check-java-runtime-logs.sh"], + "push", master)["selected"]) def test_coordinator_reads_and_validates_real_java_input(self): workflow = (Path(__file__).resolve().parents[1] / "workflows/server-ci.yml").read_text() diff --git a/.github/workflows/riscv64-ci.yml b/.github/workflows/riscv64-ci.yml index 634251fb4e..9b8bce79b0 100644 --- a/.github/workflows/riscv64-ci.yml +++ b/.github/workflows/riscv64-ci.yml @@ -106,10 +106,10 @@ jobs: -P riscv64-protobuf-tools \ -pl hugegraph-server/hugegraph-dist -am \ -Dmaven.test.skip=true -Dmaven.javadoc.skip=true - VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout) - SERVER_DIR="hugegraph-server/apache-hugegraph-server-$VERSION" - hugegraph-server/hugegraph-dist/src/assembly/travis/check-rocksdb-only-dist.sh "$SERVER_DIR" - hugegraph-server/hugegraph-dist/src/assembly/travis/run-native-runtime-smoke-test.sh "$SERVER_DIR" + hugegraph-server/hugegraph-dist/src/assembly/travis/check-rocksdb-only-dist.sh \ + hugegraph-server/apache-hugegraph-server-*/ + hugegraph-server/hugegraph-dist/src/assembly/travis/run-native-runtime-smoke-test.sh \ + hugegraph-server/apache-hugegraph-server-*/ ' - name: Show server log on failure diff --git a/hugegraph-server/hugegraph-dist/src/assembly/travis/test-riscv64-java-runtime.sh b/hugegraph-server/hugegraph-dist/src/assembly/travis/test-riscv64-java-runtime.sh index cc33c98d17..f84bb084ac 100755 --- a/hugegraph-server/hugegraph-dist/src/assembly/travis/test-riscv64-java-runtime.sh +++ b/hugegraph-server/hugegraph-dist/src/assembly/travis/test-riscv64-java-runtime.sh @@ -190,12 +190,13 @@ START_LOG="$WORK_DIR/start.log" STOP_LOG="$WORK_DIR/stop.log" PHASE_LOG="$WORK_DIR/phase.log" RUN_ID_FILE="$WORK_DIR/run-id" -mkdir -p "$RUNTIME_BIN" "$RUNTIME_TRAVIS" "$WORK_DIR/tmp" -for command in dirname date mktemp rm tee grep awk cut getconf; do +mkdir -p "$RUNTIME_BIN" "$RUNTIME_TRAVIS" "$WORK_DIR/tmp" "$SERVER_DIR/logs" +printf '%s\n' 'mock service log' > "$SERVER_DIR/logs/server.log" +for command in dirname date mktemp rm tee grep awk cut getconf find; do ln -s "$(command -v "$command")" "$RUNTIME_BIN/$command" done cp "$MOCK_BIN/uname" "$RUNTIME_BIN/uname" -cp "$NATIVE_SMOKE_SCRIPT" "$SMOKE_SCRIPT" "$RUNTIME_TRAVIS/" +cp "$NATIVE_SMOKE_SCRIPT" "$SMOKE_SCRIPT" "$TRAVIS_DIR/check-java-runtime-logs.sh" "$RUNTIME_TRAVIS/" cat > "$RUNTIME_BIN/timeout" <<'EOF' #!/bin/bash @@ -221,6 +222,7 @@ set -euo pipefail printf 'arch=%s' "$EXPECTED_ARCH" >> "$START_LOG" printf ' <%s>' "$@" >> "$START_LOG" printf '\n' >> "$START_LOG" +printf '%s\n' "${MOCK_START_OUTPUT:-}" EOF cat > "$SERVER_DIR/bin/stop-hugegraph.sh" <<'EOF' @@ -283,6 +285,14 @@ grep -q '^native-runtime-smoke-ok$' <<< "$ARM_OUTPUT" [[ $(grep -c '^gtimeout ' "$RUNTIME_LOG") -eq 3 ]] [[ $(cat "$PHASE_LOG") == $'create\nverify' ]] +if COMPATIBILITY_OUTPUT=$(run_restart_smoke MOCK_ARCH=arm64 EXPECTED_ARCH=arm64 \ + 'MOCK_START_OUTPUT=java.lang.reflect.InaccessibleObjectException: denied' 2>&1); then + echo "$COMPATIBILITY_OUTPUT" >&2 + echo "Native smoke accepted a caught Java compatibility error" >&2 + exit 1 +fi +grep -Fq 'Java runtime compatibility error' <<< "$COMPATIBILITY_OUTPUT" + set +e TIMEOUT_OUTPUT=$(run_restart_smoke MOCK_ARCH=arm64 EXPECTED_ARCH=arm64 \ MOCK_START_TIMEOUT=true 2>&1) From abda50a933b2ec928384d0da133ab2d8b62ec8ca Mon Sep 17 00:00:00 2001 From: imbajin Date: Wed, 14 Oct 2026 04:46:24 +0800 Subject: [PATCH 4/5] fix: inspect runtime logs with nul bytes scan nul-containing text logs for runtime errors retain archive and heap-dump exclusions cover rejected and benign nul logs in regression checks --- .../src/assembly/travis/check-java-runtime-logs.sh | 3 ++- .../assembly/travis/test-check-java-runtime-logs.sh | 13 +++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/hugegraph-server/hugegraph-dist/src/assembly/travis/check-java-runtime-logs.sh b/hugegraph-server/hugegraph-dist/src/assembly/travis/check-java-runtime-logs.sh index 8255743f29..1d51d0ccaa 100755 --- a/hugegraph-server/hugegraph-dist/src/assembly/travis/check-java-runtime-logs.sh +++ b/hugegraph-server/hugegraph-dist/src/assembly/travis/check-java-runtime-logs.sh @@ -43,7 +43,8 @@ check_file() { STATUS=2 return fi - matches=$(LC_ALL=C grep -EinI -- "$PATTERN" "$file" 2>&1) + # Scan text logs even when logged keys contain NUL bytes. + matches=$(LC_ALL=C grep -Eina -- "$PATTERN" "$file" 2>&1) code=$? if [[ $code -eq 0 ]]; then echo "Java runtime compatibility error in $file:" >&2 diff --git a/hugegraph-server/hugegraph-dist/src/assembly/travis/test-check-java-runtime-logs.sh b/hugegraph-server/hugegraph-dist/src/assembly/travis/test-check-java-runtime-logs.sh index f7536c0050..7fb8efa0e1 100755 --- a/hugegraph-server/hugegraph-dist/src/assembly/travis/test-check-java-runtime-logs.sh +++ b/hugegraph-server/hugegraph-dist/src/assembly/travis/test-check-java-runtime-logs.sh @@ -70,7 +70,20 @@ rm "$WORK_DIR/logs with spaces/nested/application.log.1" for SUFFIX in hprof log.gz log.zip log.bz2 log.xz; do cp "$WORK_DIR/positive.log" "$WORK_DIR/logs with spaces/heap.$SUFFIX" done +check_case 0 "$WORK_DIR/logs with spaces" +# NUL bytes from logged keys must not hide a denied-access line elsewhere in +# the same text log. Keep the NUL-free control and explicit/directory inputs. +printf '%s\n' 'java.lang.reflect.InaccessibleObjectException: module java.base does not opens p' \ + >"$WORK_DIR/logs with spaces/binary.log" +check_case 1 "$WORK_DIR/logs with spaces/binary.log" +printf 'INFO key=\0\0\1 written\n' >>"$WORK_DIR/logs with spaces/binary.log" +check_case 1 "$WORK_DIR/logs with spaces/binary.log" +grep -q '^1:' "$WORK_DIR/result" +check_case 1 "$WORK_DIR/logs with spaces" printf '\0java.lang.reflect.InaccessibleObjectException:\0' >"$WORK_DIR/logs with spaces/binary.log" +check_case 1 "$WORK_DIR/logs with spaces" +printf 'INFO key=\0\0\1 written\n' >"$WORK_DIR/logs with spaces/binary.log" +check_case 0 "$WORK_DIR/logs with spaces/binary.log" check_case 0 "$WORK_DIR/logs with spaces" check_case 2 "$WORK_DIR/no-logs" check_case 2 "$WORK_DIR/missing.log" From a9398b8b4bd69b4507e825a31a76d1369dcb58b4 Mon Sep 17 00:00:00 2001 From: imbajin Date: Wed, 14 Oct 2026 09:23:11 +0800 Subject: [PATCH 5/5] fix: isolate launcher fixture module options - copy each component module options into its fixture - remove the server-only copy that masked missing files - preserve existing launcher and gc assertions --- .../hugegraph-dist/src/assembly/travis/test-launcher-gc.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/hugegraph-server/hugegraph-dist/src/assembly/travis/test-launcher-gc.sh b/hugegraph-server/hugegraph-dist/src/assembly/travis/test-launcher-gc.sh index 4e4addf512..4d277596fc 100644 --- a/hugegraph-server/hugegraph-dist/src/assembly/travis/test-launcher-gc.sh +++ b/hugegraph-server/hugegraph-dist/src/assembly/travis/test-launcher-gc.sh @@ -51,7 +51,6 @@ for component in server pd store; do server) source_bin="$SOURCE_ROOT/hugegraph-server/hugegraph-dist/src/assembly/static/bin" launcher=hugegraph-server.sh - cp "$source_bin/jvm-module.options" "$FIXTURE/bin/" ;; pd) source_bin="$SOURCE_ROOT/hugegraph-pd/hg-pd-dist/src/assembly/static/bin" @@ -65,7 +64,7 @@ for component in server pd store; do touch "$FIXTURE/lib/hg-store-node-test.jar" ;; esac - cp "$source_bin/$launcher" "$source_bin/util.sh" "$FIXTURE/bin/" + cp "$source_bin/$launcher" "$source_bin/util.sh" "$source_bin/jvm-module.options" "$FIXTURE/bin/" # Keep the computed-heap/-j path deterministic without probing host memory. printf '\nfree_memory() { echo 2048; }\n' >> "$FIXTURE/bin/util.sh" cp "$SOURCE_ROOT/hugegraph-server/hugegraph-dist/src/assembly/static/bin/preload-topling.sh" "$FIXTURE/bin/"