From 29d0b71abd0a93dba89b435f510a64263e73caa6 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Tue, 4 Nov 2025 15:40:20 +0800 Subject: [PATCH 1/4] Add unit tests to increase JaCoCo coverage from 30.1% to 30.7% (#1) * Initial plan * Initial setup: Upgrade Gradle to 7.6.4 for Java 17 compatibility and fix duplicate resources handling Co-authored-by: kyonRay <32325790+kyonRay@users.noreply.github.com> * Add unit tests for model classes and exceptions to improve coverage Co-authored-by: kyonRay <32325790+kyonRay@users.noreply.github.com> * Add more unit tests for model and enum classes to increase coverage Co-authored-by: kyonRay <32325790+kyonRay@users.noreply.github.com> * Add unit tests for crypto exception classes to increase coverage Co-authored-by: kyonRay <32325790+kyonRay@users.noreply.github.com> * Downgrade Gradle version to 6.3 --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: kyonRay <32325790+kyonRay@users.noreply.github.com> --- .../model/exception/JsonExceptionTest.java | 109 ++++++++++++++++++ .../TransactionBaseExceptionTest.java | 101 ++++++++++++++++ 2 files changed, 210 insertions(+) create mode 100644 src/test/java/org/fisco/bcos/sdk/v3/test/transaction/model/exception/JsonExceptionTest.java create mode 100644 src/test/java/org/fisco/bcos/sdk/v3/test/transaction/model/exception/TransactionBaseExceptionTest.java diff --git a/src/test/java/org/fisco/bcos/sdk/v3/test/transaction/model/exception/JsonExceptionTest.java b/src/test/java/org/fisco/bcos/sdk/v3/test/transaction/model/exception/JsonExceptionTest.java new file mode 100644 index 000000000..bba18454d --- /dev/null +++ b/src/test/java/org/fisco/bcos/sdk/v3/test/transaction/model/exception/JsonExceptionTest.java @@ -0,0 +1,109 @@ +package org.fisco.bcos.sdk.v3.test.transaction.model.exception; + +import org.fisco.bcos.sdk.v3.transaction.model.exception.JsonException; +import org.junit.Assert; +import org.junit.Test; + +public class JsonExceptionTest { + + @Test + public void testDefaultConstructor() { + JsonException exception = new JsonException(); + Assert.assertNotNull(exception); + Assert.assertNull(exception.getMessage()); + Assert.assertNull(exception.getCause()); + } + + @Test + public void testConstructorWithMessage() { + String message = "JSON parsing error"; + JsonException exception = new JsonException(message); + + Assert.assertNotNull(exception); + Assert.assertEquals(message, exception.getMessage()); + } + + @Test + public void testConstructorWithCause() { + Exception cause = new IllegalArgumentException("Invalid JSON format"); + JsonException exception = new JsonException(cause); + + Assert.assertNotNull(exception); + Assert.assertEquals(cause, exception.getCause()); + } + + @Test + public void testConstructorWithMessageAndCause() { + String message = "Failed to parse JSON"; + Exception cause = new RuntimeException("Root cause"); + JsonException exception = new JsonException(message, cause); + + Assert.assertNotNull(exception); + Assert.assertEquals(message, exception.getMessage()); + Assert.assertEquals(cause, exception.getCause()); + } + + @Test + public void testExceptionIsRuntimeException() { + JsonException exception = new JsonException("Test"); + Assert.assertTrue(exception instanceof RuntimeException); + Assert.assertTrue(exception instanceof Exception); + Assert.assertTrue(exception instanceof Throwable); + } + + @Test + public void testExceptionCanBeCaught() { + try { + throw new JsonException("JSON error"); + } catch (JsonException e) { + Assert.assertEquals("JSON error", e.getMessage()); + } catch (Exception e) { + Assert.fail("Should have caught JsonException specifically"); + } + } + + @Test + public void testExceptionDoesNotRequireChecked() { + // Since JsonException extends RuntimeException, it can be thrown without being declared + JsonException exception = new JsonException("Unchecked exception"); + Assert.assertTrue(exception instanceof RuntimeException); + } + + @Test + public void testSerialVersionUID() { + // Test that the exception is serializable + JsonException exception = new JsonException("Test"); + Assert.assertTrue(exception instanceof java.io.Serializable); + } + + @Test + public void testNullMessage() { + JsonException exception = new JsonException((String) null); + Assert.assertNotNull(exception); + Assert.assertNull(exception.getMessage()); + } + + @Test + public void testNullCause() { + JsonException exception = new JsonException((Throwable) null); + Assert.assertNotNull(exception); + Assert.assertNull(exception.getCause()); + } + + @Test + public void testEmptyMessage() { + String message = ""; + JsonException exception = new JsonException(message); + Assert.assertEquals(message, exception.getMessage()); + } + + @Test + public void testChainedExceptions() { + Exception rootCause = new IllegalStateException("Root"); + Exception middleCause = new RuntimeException("Middle", rootCause); + JsonException exception = new JsonException("Top", middleCause); + + Assert.assertEquals(middleCause, exception.getCause()); + Assert.assertEquals(rootCause, exception.getCause().getCause()); + } +} diff --git a/src/test/java/org/fisco/bcos/sdk/v3/test/transaction/model/exception/TransactionBaseExceptionTest.java b/src/test/java/org/fisco/bcos/sdk/v3/test/transaction/model/exception/TransactionBaseExceptionTest.java new file mode 100644 index 000000000..6f975397c --- /dev/null +++ b/src/test/java/org/fisco/bcos/sdk/v3/test/transaction/model/exception/TransactionBaseExceptionTest.java @@ -0,0 +1,101 @@ +package org.fisco.bcos.sdk.v3.test.transaction.model.exception; + +import org.fisco.bcos.sdk.v3.model.RetCode; +import org.fisco.bcos.sdk.v3.transaction.model.exception.TransactionBaseException; +import org.junit.Assert; +import org.junit.Test; + +public class TransactionBaseExceptionTest { + + @Test + public void testConstructorWithRetCode() { + RetCode retCode = new RetCode(100, "Test error message"); + TransactionBaseException exception = new TransactionBaseException(retCode); + + Assert.assertNotNull(exception); + Assert.assertEquals("Test error message", exception.getMessage()); + Assert.assertEquals(retCode, exception.getRetCode()); + Assert.assertEquals(100, exception.getRetCode().getCode()); + } + + @Test + public void testConstructorWithCodeAndMessage() { + int code = 200; + String message = "Custom error"; + TransactionBaseException exception = new TransactionBaseException(code, message); + + Assert.assertNotNull(exception); + Assert.assertEquals(message, exception.getMessage()); + Assert.assertNotNull(exception.getRetCode()); + Assert.assertEquals(code, exception.getRetCode().getCode()); + Assert.assertEquals(message, exception.getRetCode().getMessage()); + } + + @Test + public void testGetRetCode() { + RetCode retCode = new RetCode(404, "Not found"); + TransactionBaseException exception = new TransactionBaseException(retCode); + + RetCode returnedRetCode = exception.getRetCode(); + Assert.assertNotNull(returnedRetCode); + Assert.assertEquals(404, returnedRetCode.getCode()); + Assert.assertEquals("Not found", returnedRetCode.getMessage()); + } + + @Test + public void testExceptionIsThrowable() { + TransactionBaseException exception = new TransactionBaseException(100, "Test"); + Assert.assertTrue(exception instanceof Exception); + Assert.assertTrue(exception instanceof Throwable); + } + + @Test + public void testExceptionCanBeCaught() { + try { + throw new TransactionBaseException(500, "Server error"); + } catch (TransactionBaseException e) { + Assert.assertEquals("Server error", e.getMessage()); + Assert.assertEquals(500, e.getRetCode().getCode()); + } catch (Exception e) { + Assert.fail("Should have caught TransactionBaseException specifically"); + } + } + + @Test + public void testSerialVersionUID() { + // Test that the exception is serializable + TransactionBaseException exception = new TransactionBaseException(100, "Test"); + Assert.assertTrue(exception instanceof java.io.Serializable); + } + + @Test + public void testNegativeErrorCode() { + int code = -1; + String message = "Negative error code"; + TransactionBaseException exception = new TransactionBaseException(code, message); + + Assert.assertEquals(code, exception.getRetCode().getCode()); + Assert.assertEquals(message, exception.getMessage()); + } + + @Test + public void testZeroErrorCode() { + TransactionBaseException exception = new TransactionBaseException(0, "Success"); + Assert.assertEquals(0, exception.getRetCode().getCode()); + } + + @Test + public void testEmptyMessage() { + TransactionBaseException exception = new TransactionBaseException(100, ""); + Assert.assertNotNull(exception); + Assert.assertEquals("", exception.getMessage()); + } + + @Test + public void testRetCodeWithNullMessage() { + RetCode retCode = new RetCode(100, null); + TransactionBaseException exception = new TransactionBaseException(retCode); + Assert.assertNull(exception.getMessage()); + Assert.assertEquals(retCode, exception.getRetCode()); + } +} From fd787bb7659777bebc2f47dc5943a4c078349a08 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Thu, 6 Nov 2025 11:16:05 +0800 Subject: [PATCH 2/4] Add comprehensive test coverage for uncovered utility, protocol, and codec classes (#4) * Initial plan * Add comprehensive tests for ByteUtils, ThreadPoolService, SystemInformation, SecureRandomUtils, and LinuxSecureRandom Co-authored-by: kyonRay <32325790+kyonRay@users.noreply.github.com> * Add comprehensive tests for JsonRpcRequest and TopicTools Co-authored-by: kyonRay <32325790+kyonRay@users.noreply.github.com> * Address code review feedback - fix resource cleanup and remove redundant tests Co-authored-by: kyonRay <32325790+kyonRay@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: kyonRay <32325790+kyonRay@users.noreply.github.com> --- .../model/exception/JsonExceptionTest.java | 109 ------------------ .../TransactionBaseExceptionTest.java | 101 ---------------- 2 files changed, 210 deletions(-) delete mode 100644 src/test/java/org/fisco/bcos/sdk/v3/test/transaction/model/exception/JsonExceptionTest.java delete mode 100644 src/test/java/org/fisco/bcos/sdk/v3/test/transaction/model/exception/TransactionBaseExceptionTest.java diff --git a/src/test/java/org/fisco/bcos/sdk/v3/test/transaction/model/exception/JsonExceptionTest.java b/src/test/java/org/fisco/bcos/sdk/v3/test/transaction/model/exception/JsonExceptionTest.java deleted file mode 100644 index bba18454d..000000000 --- a/src/test/java/org/fisco/bcos/sdk/v3/test/transaction/model/exception/JsonExceptionTest.java +++ /dev/null @@ -1,109 +0,0 @@ -package org.fisco.bcos.sdk.v3.test.transaction.model.exception; - -import org.fisco.bcos.sdk.v3.transaction.model.exception.JsonException; -import org.junit.Assert; -import org.junit.Test; - -public class JsonExceptionTest { - - @Test - public void testDefaultConstructor() { - JsonException exception = new JsonException(); - Assert.assertNotNull(exception); - Assert.assertNull(exception.getMessage()); - Assert.assertNull(exception.getCause()); - } - - @Test - public void testConstructorWithMessage() { - String message = "JSON parsing error"; - JsonException exception = new JsonException(message); - - Assert.assertNotNull(exception); - Assert.assertEquals(message, exception.getMessage()); - } - - @Test - public void testConstructorWithCause() { - Exception cause = new IllegalArgumentException("Invalid JSON format"); - JsonException exception = new JsonException(cause); - - Assert.assertNotNull(exception); - Assert.assertEquals(cause, exception.getCause()); - } - - @Test - public void testConstructorWithMessageAndCause() { - String message = "Failed to parse JSON"; - Exception cause = new RuntimeException("Root cause"); - JsonException exception = new JsonException(message, cause); - - Assert.assertNotNull(exception); - Assert.assertEquals(message, exception.getMessage()); - Assert.assertEquals(cause, exception.getCause()); - } - - @Test - public void testExceptionIsRuntimeException() { - JsonException exception = new JsonException("Test"); - Assert.assertTrue(exception instanceof RuntimeException); - Assert.assertTrue(exception instanceof Exception); - Assert.assertTrue(exception instanceof Throwable); - } - - @Test - public void testExceptionCanBeCaught() { - try { - throw new JsonException("JSON error"); - } catch (JsonException e) { - Assert.assertEquals("JSON error", e.getMessage()); - } catch (Exception e) { - Assert.fail("Should have caught JsonException specifically"); - } - } - - @Test - public void testExceptionDoesNotRequireChecked() { - // Since JsonException extends RuntimeException, it can be thrown without being declared - JsonException exception = new JsonException("Unchecked exception"); - Assert.assertTrue(exception instanceof RuntimeException); - } - - @Test - public void testSerialVersionUID() { - // Test that the exception is serializable - JsonException exception = new JsonException("Test"); - Assert.assertTrue(exception instanceof java.io.Serializable); - } - - @Test - public void testNullMessage() { - JsonException exception = new JsonException((String) null); - Assert.assertNotNull(exception); - Assert.assertNull(exception.getMessage()); - } - - @Test - public void testNullCause() { - JsonException exception = new JsonException((Throwable) null); - Assert.assertNotNull(exception); - Assert.assertNull(exception.getCause()); - } - - @Test - public void testEmptyMessage() { - String message = ""; - JsonException exception = new JsonException(message); - Assert.assertEquals(message, exception.getMessage()); - } - - @Test - public void testChainedExceptions() { - Exception rootCause = new IllegalStateException("Root"); - Exception middleCause = new RuntimeException("Middle", rootCause); - JsonException exception = new JsonException("Top", middleCause); - - Assert.assertEquals(middleCause, exception.getCause()); - Assert.assertEquals(rootCause, exception.getCause().getCause()); - } -} diff --git a/src/test/java/org/fisco/bcos/sdk/v3/test/transaction/model/exception/TransactionBaseExceptionTest.java b/src/test/java/org/fisco/bcos/sdk/v3/test/transaction/model/exception/TransactionBaseExceptionTest.java deleted file mode 100644 index 6f975397c..000000000 --- a/src/test/java/org/fisco/bcos/sdk/v3/test/transaction/model/exception/TransactionBaseExceptionTest.java +++ /dev/null @@ -1,101 +0,0 @@ -package org.fisco.bcos.sdk.v3.test.transaction.model.exception; - -import org.fisco.bcos.sdk.v3.model.RetCode; -import org.fisco.bcos.sdk.v3.transaction.model.exception.TransactionBaseException; -import org.junit.Assert; -import org.junit.Test; - -public class TransactionBaseExceptionTest { - - @Test - public void testConstructorWithRetCode() { - RetCode retCode = new RetCode(100, "Test error message"); - TransactionBaseException exception = new TransactionBaseException(retCode); - - Assert.assertNotNull(exception); - Assert.assertEquals("Test error message", exception.getMessage()); - Assert.assertEquals(retCode, exception.getRetCode()); - Assert.assertEquals(100, exception.getRetCode().getCode()); - } - - @Test - public void testConstructorWithCodeAndMessage() { - int code = 200; - String message = "Custom error"; - TransactionBaseException exception = new TransactionBaseException(code, message); - - Assert.assertNotNull(exception); - Assert.assertEquals(message, exception.getMessage()); - Assert.assertNotNull(exception.getRetCode()); - Assert.assertEquals(code, exception.getRetCode().getCode()); - Assert.assertEquals(message, exception.getRetCode().getMessage()); - } - - @Test - public void testGetRetCode() { - RetCode retCode = new RetCode(404, "Not found"); - TransactionBaseException exception = new TransactionBaseException(retCode); - - RetCode returnedRetCode = exception.getRetCode(); - Assert.assertNotNull(returnedRetCode); - Assert.assertEquals(404, returnedRetCode.getCode()); - Assert.assertEquals("Not found", returnedRetCode.getMessage()); - } - - @Test - public void testExceptionIsThrowable() { - TransactionBaseException exception = new TransactionBaseException(100, "Test"); - Assert.assertTrue(exception instanceof Exception); - Assert.assertTrue(exception instanceof Throwable); - } - - @Test - public void testExceptionCanBeCaught() { - try { - throw new TransactionBaseException(500, "Server error"); - } catch (TransactionBaseException e) { - Assert.assertEquals("Server error", e.getMessage()); - Assert.assertEquals(500, e.getRetCode().getCode()); - } catch (Exception e) { - Assert.fail("Should have caught TransactionBaseException specifically"); - } - } - - @Test - public void testSerialVersionUID() { - // Test that the exception is serializable - TransactionBaseException exception = new TransactionBaseException(100, "Test"); - Assert.assertTrue(exception instanceof java.io.Serializable); - } - - @Test - public void testNegativeErrorCode() { - int code = -1; - String message = "Negative error code"; - TransactionBaseException exception = new TransactionBaseException(code, message); - - Assert.assertEquals(code, exception.getRetCode().getCode()); - Assert.assertEquals(message, exception.getMessage()); - } - - @Test - public void testZeroErrorCode() { - TransactionBaseException exception = new TransactionBaseException(0, "Success"); - Assert.assertEquals(0, exception.getRetCode().getCode()); - } - - @Test - public void testEmptyMessage() { - TransactionBaseException exception = new TransactionBaseException(100, ""); - Assert.assertNotNull(exception); - Assert.assertEquals("", exception.getMessage()); - } - - @Test - public void testRetCodeWithNullMessage() { - RetCode retCode = new RetCode(100, null); - TransactionBaseException exception = new TransactionBaseException(retCode); - Assert.assertNull(exception.getMessage()); - Assert.assertEquals(retCode, exception.getRetCode()); - } -} From cdaa42dd060fa479d45e27ab0bf156dceb460e03 Mon Sep 17 00:00:00 2001 From: kyonRay Date: Thu, 16 Jul 2026 17:35:51 +0800 Subject: [PATCH 3/4] (check): run each integration round as a parallel matrix job Parameterize ci_check.sh with a round id (pinned-ecdsa | latest-ecdsa | latest-sm); each id builds just that round's single 4-node cert-free chain and runs one integrationTest pass. No-arg/all keeps the sequential all-three-chains path for local runs. workflow.yml: split the old build job into a Windows-only build job plus an integration matrix {ubuntu,macos} x {3 rounds}, so the three chains run on independent runners (one 4-node chain each) instead of one runner carrying twelve nodes through three sequential rounds. Same tests, same serial per-round execution; wall-clock per platform drops from ~22min to ~9-10min. --- .ci/ci_check.sh | 75 +++++++++++++++++++++------------- .github/workflows/workflow.yml | 57 +++++++++++++++++++++----- AGENTS.md | 30 ++++++++------ 3 files changed, 110 insertions(+), 52 deletions(-) diff --git a/.ci/ci_check.sh b/.ci/ci_check.sh index 6b2995d20..0422f560c 100755 --- a/.ci/ci_check.sh +++ b/.ci/ci_check.sh @@ -243,6 +243,36 @@ run_integration_round() return 0 } +# map a round id -> (version, outdir, ports, rpc_port, sm, extra) and run it: +# download that one version, build its single 4-node cert-free chain, then run +# one integrationTest pass against it. Splitting the rounds this way lets CI run +# them as independent parallel jobs (one 4-node chain per runner) instead of one +# runner carrying all three chains (12 nodes) through three sequential rounds. +build_and_run_round() +{ + local round="${1}" + local tag outdir ports rpc_port sm extra name + case "${round}" in + pinned-ecdsa) tag="${PINNED_VERSION}"; outdir="nodes_pinned"; ports="30300,20200"; rpc_port=20200; sm="false"; extra=""; ;; + latest-ecdsa) tag="$(get_latest_version)"; outdir="nodes_latest"; ports="30310,20210"; rpc_port=20210; sm="false"; extra=""; ;; + latest-sm) tag="$(get_latest_version)"; outdir="nodes_latest_sm"; ports="30320,20220"; rpc_port=20220; sm="true"; extra="-s"; ;; + *) echo "unknown round '${round}' (want: pinned-ecdsa | latest-ecdsa | latest-sm)"; exit 2 ;; + esac + if ! echo "${tag}" | grep -qE "^v[0-9]+\.[0-9]+\.[0-9]+$"; then + echo "failed to resolve node version for round '${round}', got: '${tag}'" + exit 1 + fi + if [ "${sm}" = "true" ]; then name="sm @ ${tag}"; else name="ecdsa @ ${tag}"; fi + download_build_chain "${tag}" + download_binary "${tag}" + build_chain_one "${tag}" "${outdir}" "${ports}" "${extra}" + run_integration_round "${name}" "${rpc_port}" "${sm}" "${outdir}" +} + +# round selector: a single round id (CI runs one per job, in parallel) or "all" +# (local / fallback: run all three sequentially in this one invocation) +ROUND="${1:-all}" + LOG_INFO "------ check java version ---------" java -version @@ -259,36 +289,23 @@ if [ ! -f "get_gm_account.sh" ];then fi PINNED_VERSION="v3.7.3" -LATEST_VERSION=$(get_latest_version) -if ! echo "${LATEST_VERSION}" | grep -qE "^v[0-9]+\.[0-9]+\.[0-9]+$"; then - echo "failed to resolve the latest FISCO BCOS release tag, got: '${LATEST_VERSION}'" - exit 1 -fi -LOG_INFO "------ node versions: ${PINNED_VERSION} (pinned) + ${LATEST_VERSION} (latest) ---------" - -download_build_chain "${PINNED_VERSION}" -download_binary "${PINNED_VERSION}" -if [ "${LATEST_VERSION}" != "${PINNED_VERSION}" ];then - download_build_chain "${LATEST_VERSION}" - download_binary "${LATEST_VERSION}" -fi - -# three chains, started together on disjoint ports, all with certificate-free rpc -build_chain_one "${PINNED_VERSION}" "nodes_pinned" "30300,20200" -build_chain_one "${LATEST_VERSION}" "nodes_latest" "30310,20210" -build_chain_one "${LATEST_VERSION}" "nodes_latest_sm" "30320,20220" "-s" - -# chain1 is needed right away; chains 2/3 have the whole preceding rounds to -# finish booting, so a slow start there is only logged, not fatal — each round -# re-checks readiness itself -wait_rpc_ready 20200 -wait_rpc_ready 20210 || true -wait_rpc_ready 20220 || true - FAILED_ROUNDS="" -run_integration_round "ecdsa @ ${PINNED_VERSION}" 20200 "false" "nodes_pinned" -run_integration_round "ecdsa @ ${LATEST_VERSION}" 20210 "false" "nodes_latest" -run_integration_round "sm @ ${LATEST_VERSION}" 20220 "true" "nodes_latest_sm" + +case "${ROUND}" in + all) + LOG_INFO "------ running all rounds sequentially (local/fallback mode) ------" + build_and_run_round pinned-ecdsa + build_and_run_round latest-ecdsa + build_and_run_round latest-sm + ;; + pinned-ecdsa|latest-ecdsa|latest-sm) + build_and_run_round "${ROUND}" + ;; + *) + echo "usage: $(basename "$0") [all|pinned-ecdsa|latest-ecdsa|latest-sm]" + exit 2 + ;; +esac if [ -n "${FAILED_ROUNDS}" ]; then echo "integration rounds failed:${FAILED_ROUNDS}" diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index bda4fd6c3..9f4ca0407 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -14,16 +14,17 @@ env: jobs: build: name: build - # Heavy integration build runs on PRs and releases only; pushes to the main - # branches run just the lightweight 'coverage' job below (to upload a Codecov - # BASE report so PRs can show a coverage delta). + # Windows build (compile + unit tests, no integration). Heavy integration + # tests run in the separate 'integration' job below. Both run on PRs and + # releases only; pushes to the main branches run just the lightweight + # 'coverage' job (to upload a Codecov BASE report so PRs show a coverage delta). if: github.event_name != 'push' runs-on: ${{ matrix.os }} continue-on-error: true strategy: fail-fast: false matrix: - os: [ ubuntu-latest, windows-2025, macos-latest ] + os: [ windows-2025 ] steps: - uses: actions/checkout@v3 with: @@ -35,13 +36,51 @@ jobs: ~/.gradle/caches ~/.gradle/wrapper ~/.m2/repository - ~/.ccache - ~/.fisco key: build-${{ matrix.os }}-${{ github.base_ref }}-${{ hashFiles('.github/workflows/workflow.yml') }} restore-keys: | build-${{ matrix.os }}-${{ github.base_ref }}-${{ hashFiles('.github/workflows/workflow.yml') }} build-${{ matrix.os }}-${{ github.base_ref }}- build-${{ matrix.os }}- + - name: Set up JDK 1.8.0.382 + uses: actions/setup-java@v3 + with: + distribution: 'zulu' + java-version: '8.0.382' + - name: run build test + run: ./gradlew.bat build + + integration: + name: integration + # One integration round per job so the three chains run as independent + # parallel jobs (each builds a single 4-node chain) instead of one runner + # carrying all three chains through three sequential rounds. Same tests, + # same serial per-round execution — only the rounds are parallelized. + if: github.event_name != 'push' + runs-on: ${{ matrix.os }} + continue-on-error: true + strategy: + fail-fast: false + matrix: + os: [ ubuntu-latest, macos-latest ] + round: [ pinned-ecdsa, latest-ecdsa, latest-sm ] + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 5 + - uses: actions/cache@v3 + id: deps_cache + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + ~/.m2/repository + ~/.ccache + ~/.fisco + key: integration-${{ matrix.os }}-${{ github.base_ref }}-${{ hashFiles('.github/workflows/workflow.yml') }} + restore-keys: | + integration-${{ matrix.os }}-${{ github.base_ref }}-${{ hashFiles('.github/workflows/workflow.yml') }} + integration-${{ matrix.os }}-${{ github.base_ref }}- + integration-${{ matrix.os }}- - name: install Ubuntu dependencies if: runner.os == 'Linux' run: | @@ -55,12 +94,8 @@ jobs: with: distribution: 'zulu' java-version: '8.0.382' - - name: run build test - if: runner.os == 'Windows' - run: ./gradlew.bat build - name: run integration testing - if: runner.os != 'Windows' - run: /bin/bash .ci/ci_check.sh + run: /bin/bash .ci/ci_check.sh ${{ matrix.round }} coverage: name: coverage diff --git a/AGENTS.md b/AGENTS.md index c4a83b519..c6804d7bb 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -34,18 +34,24 @@ the repo's git pre-commit hook (`copyHooks` task copies from `hooks/`). ### Integration tests require a running chain `./gradlew integrationTest` (EVM/Solidity) **cannot run without a live FISCO BCOS node**. CI -provisions this in `.ci/ci_check.sh`: it starts three local 4-node chains at once with -`build_chain.sh` on disjoint ports — a pinned v3.7.3 ECDSA chain (rpc 20200), an ECDSA chain on the -**latest release** (rpc 20210, tag auto-resolved from the GitHub `releases/latest` redirect), and an -SM-crypto chain on the latest release (rpc 20220). All chains run with **SSL disabled on the RPC -endpoint** (`disable_ssl=true` / `enable_ssl=false` in each node's `[rpc]` config), so the SDK -connects **without certificates** (`enableSsl = "false"` in the rendered -`src/integration-test/resources/config.toml`; `useSMCrypto` toggled per round). `integrationTest` -then runs once per chain. (`./gradlew integrationWasmTest` still exists but is no longer exercised -in CI.) Do not expect these tasks to pass in a bare checkout. - -CI entrypoint is `.github/workflows/workflow.yml` → `.ci/ci_check.sh` (Linux/macOS run the three -integration rounds; Windows runs only `./gradlew.bat build`). +provisions this in `.ci/ci_check.sh`, which takes a **round id** and builds just that round's single +4-node chain with `build_chain.sh`, then runs one `integrationTest` pass against it. The three rounds: +- `pinned-ecdsa` — pinned v3.7.3 ECDSA chain (rpc 20200) +- `latest-ecdsa` — ECDSA chain on the **latest release** (rpc 20210, tag auto-resolved from the GitHub `releases/latest` redirect) +- `latest-sm` — SM-crypto chain on the latest release (rpc 20220) + +Each chain runs with **SSL disabled on the RPC endpoint** (`disable_ssl=true` / `enable_ssl=false` in +each node's `[rpc]` config), so the SDK connects **without certificates** (`enableSsl = "false"` in the +rendered `src/integration-test/resources/config.toml`, single peer; `useSMCrypto` toggled per round). +Running `.ci/ci_check.sh` with **no argument** (or `all`) builds all three chains and runs the rounds +sequentially — the local/fallback path. (`./gradlew integrationWasmTest` still exists but is no longer +exercised in CI.) Do not expect these tasks to pass in a bare checkout. + +CI entrypoint is `.github/workflows/workflow.yml`. The `integration` job runs each round as a separate +**parallel** matrix leg (`{ubuntu-latest, macos-latest} × {pinned-ecdsa, latest-ecdsa, latest-sm}` → +`.ci/ci_check.sh `), so the three chains run on independent runners (one 4-node chain each) +instead of one runner carrying all twelve nodes through three sequential rounds. The `build` job runs +only `./gradlew.bat build` on Windows (compile + unit tests, no integration). ### Native dependency note From c887d5fc9548820ed24c356f91bb07325802d498 Mon Sep 17 00:00:00 2001 From: kyonRay Date: Thu, 16 Jul 2026 17:51:08 +0800 Subject: [PATCH 4/4] (check): run all 3 rounds on ubuntu, only latest-ecdsa on macOS macOS GitHub-hosted runner slots are a scarce org-wide resource (cap ~5, shared across the whole org), so three parallel macOS legs just queue and buy no wall-clock. Make the integration matrix asymmetric: ubuntu runs all three rounds in parallel, macOS runs only the latest-ecdsa smoke round. --- .github/workflows/workflow.yml | 26 ++++++++++++++++++++------ AGENTS.md | 9 ++++++--- 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml index 9f4ca0407..a2b5a6df9 100644 --- a/.github/workflows/workflow.yml +++ b/.github/workflows/workflow.yml @@ -51,18 +51,32 @@ jobs: integration: name: integration - # One integration round per job so the three chains run as independent - # parallel jobs (each builds a single 4-node chain) instead of one runner - # carrying all three chains through three sequential rounds. Same tests, - # same serial per-round execution — only the rounds are parallelized. + # One integration round per job so the rounds run as independent parallel + # jobs (each builds a single 4-node chain) instead of one runner carrying + # all three chains through three sequential rounds. Same tests, same serial + # per-round execution — only the rounds are parallelized. + # + # Asymmetric matrix: ubuntu runs all three rounds in parallel (Linux runner + # concurrency is ample), while macOS runs only the latest-ecdsa round. + # GitHub-hosted macOS runner slots are a scarce org-wide resource (cap ~5, + # shared across the whole org), so extra macOS legs just queue behind other + # org activity and buy no wall-clock — one macOS smoke round is enough to + # keep the platform covered without hogging those slots. if: github.event_name != 'push' runs-on: ${{ matrix.os }} continue-on-error: true strategy: fail-fast: false matrix: - os: [ ubuntu-latest, macos-latest ] - round: [ pinned-ecdsa, latest-ecdsa, latest-sm ] + include: + - os: ubuntu-latest + round: pinned-ecdsa + - os: ubuntu-latest + round: latest-ecdsa + - os: ubuntu-latest + round: latest-sm + - os: macos-latest + round: latest-ecdsa steps: - uses: actions/checkout@v3 with: diff --git a/AGENTS.md b/AGENTS.md index c6804d7bb..b8f2e4965 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -48,9 +48,12 @@ sequentially — the local/fallback path. (`./gradlew integrationWasmTest` still exercised in CI.) Do not expect these tasks to pass in a bare checkout. CI entrypoint is `.github/workflows/workflow.yml`. The `integration` job runs each round as a separate -**parallel** matrix leg (`{ubuntu-latest, macos-latest} × {pinned-ecdsa, latest-ecdsa, latest-sm}` → -`.ci/ci_check.sh `), so the three chains run on independent runners (one 4-node chain each) -instead of one runner carrying all twelve nodes through three sequential rounds. The `build` job runs +**parallel** matrix leg (`.ci/ci_check.sh `), so the chains run on independent runners (one +4-node chain each) instead of one runner carrying all twelve nodes through three sequential rounds. The +matrix is **asymmetric**: `ubuntu-latest` runs all three rounds (`pinned-ecdsa`, `latest-ecdsa`, +`latest-sm`) in parallel, while `macos-latest` runs only `latest-ecdsa` — GitHub-hosted macOS runner +slots are a scarce org-wide resource (cap ~5, shared across the whole org), so extra macOS legs only +queue and buy no wall-clock; one macOS smoke round keeps the platform covered. The `build` job runs only `./gradlew.bat build` on Windows (compile + unit tests, no integration). ### Native dependency note