diff --git a/.github/workflows/integration-test-multinode.yml b/.github/workflows/integration-test-multinode.yml new file mode 100644 index 00000000000..3d8ac20d507 --- /dev/null +++ b/.github/workflows/integration-test-multinode.yml @@ -0,0 +1,117 @@ +name: Integration Test Multinode (Full) + +on: + pull_request: + branches: [ 'develop', 'release_**' ] + types: [ opened, synchronize, reopened ] + paths-ignore: [ '**/*.md', '.gitignore', '**/.gitignore', '.editorconfig', + '.gitattributes', 'docs/**', 'CHANGELOG', '.github/ISSUE_TEMPLATE/**', + '.github/PULL_REQUEST_TEMPLATE/**', '.github/CODEOWNERS' ] + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + multinode-full: + name: Integration Test Multinode Full (JDK 8 / x86_64) + runs-on: ubuntu-latest + timeout-minutes: 60 + + steps: + - name: Checkout java-tron + uses: actions/checkout@v5 + + - name: Set up JDK 8 + uses: actions/setup-java@v5 + with: + java-version: '8' + distribution: 'temurin' + + - name: Cache Gradle packages + uses: actions/cache@v4 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-multinode-${{ hashFiles('**/*.gradle', '**/gradle-wrapper.properties') }} + restore-keys: ${{ runner.os }}-gradle-multinode- + + - name: Build FullNode.jar + run: ./gradlew clean build -x test --no-daemon + + - name: Build local java-tron Docker image (wraps PR-built FullNode.jar) + run: | + mkdir -p /tmp/tron-image + cp build/libs/FullNode.jar /tmp/tron-image/ + cat > /tmp/tron-image/Dockerfile <<'EOF' + FROM tronprotocol/java-tron:latest + COPY FullNode.jar /java-tron/lib/FullNode.jar + EOF + docker build -t java-tron-local:pr /tmp/tron-image + + - name: Pull integration-test image + run: docker pull troninfra/troninfra-ci:latest + + - name: Extract compose configs to host (for DinD path-alignment) + run: | + # start-multinode.sh builds HOST_COMPOSE_DIR as: + # ${HOST_WORKDIR}/docker/multi-node + # so the files must live at $HOST_WORKDIR/docker/multi-node/ on the + # host. Set HOST_WORKDIR to the workspace root and extract + # /app/docker/ 1:1 into workspace/docker/ — the subdirectories + # (multi-node/, single-node/) don't collide with java-tron's own + # docker/ files. + docker create --name it-extract troninfra/troninfra-ci:latest + docker cp it-extract:/app/docker/. "${{ github.workspace }}/docker/" + docker rm -f it-extract + + - name: Run multinode full tests + run: | + # --network host: multinode tests talk to nodes via 127.0.0.1:50051 etc. + # DinD socket + HOST_WORKDIR path-alignment lets the container orchestrate + # the 3-witness compose stack via the host daemon. + # Don't override --workdir so the container's default /app entrypoint works. + docker run --name integration-multinode \ + --network host \ + -v /var/run/docker.sock:/var/run/docker.sock \ + -v "${{ github.workspace }}:${{ github.workspace }}" \ + -v "${{ github.workspace }}/docker/multi-node:/app/docker/multi-node" \ + -e HOST_WORKDIR="${{ github.workspace }}" \ + -e TRON_IMAGE=java-tron-local:pr \ + -e JAVA_HOME=/usr/lib/jvm/temurin-8 \ + -e JAVA_HOME_17=/opt/java/openjdk \ + troninfra/troninfra-ci:latest \ + --multinode --clean + + - name: Extract test reports from container + if: always() + run: | + mkdir -p integration-reports + docker cp integration-multinode:/app/build/reports/. integration-reports/reports/ 2>/dev/null || true + docker cp integration-multinode:/app/build/test-results/. integration-reports/test-results/ 2>/dev/null || true + docker cp integration-multinode:/app/build/test-output.log integration-reports/ 2>/dev/null || true + + - name: Collect witness node logs + if: always() + run: | + mkdir -p integration-reports/node-logs + for c in tron-mn-node1 tron-mn-node2 tron-mn-node3 tron-mn-mongodb; do + docker logs "$c" > "integration-reports/node-logs/${c}.log" 2>&1 || true + done + + - name: Tear down compose stack + if: always() + run: | + docker rm -f tron-mn-node1 tron-mn-node2 tron-mn-node3 tron-mn-mongodb 2>/dev/null || true + docker network rm multi-node_tron-net 2>/dev/null || true + docker rm -f integration-multinode 2>/dev/null || true + + - name: Upload test reports + if: always() + uses: actions/upload-artifact@v6 + with: + name: integration-multinode-report + path: integration-reports/ + if-no-files-found: warn diff --git a/.github/workflows/integration-test-single-node.yml b/.github/workflows/integration-test-single-node.yml new file mode 100644 index 00000000000..874ffb82cdd --- /dev/null +++ b/.github/workflows/integration-test-single-node.yml @@ -0,0 +1,78 @@ +name: Integration Test Single Node (Full) + +on: + pull_request: + branches: [ 'develop', 'release_**' ] + types: [ opened, synchronize, reopened ] + paths-ignore: [ '**/*.md', '.gitignore', '**/.gitignore', '.editorconfig', + '.gitattributes', 'docs/**', 'CHANGELOG', '.github/ISSUE_TEMPLATE/**', + '.github/PULL_REQUEST_TEMPLATE/**', '.github/CODEOWNERS' ] + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + integration: + name: Integration Test Single Node Full (JDK 8 / x86_64) + runs-on: ubuntu-latest + timeout-minutes: 45 + + steps: + - name: Checkout java-tron + uses: actions/checkout@v5 + + - name: Set up JDK 8 + uses: actions/setup-java@v5 + with: + java-version: '8' + distribution: 'temurin' + + - name: Cache Gradle packages + uses: actions/cache@v4 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-integration-test-${{ hashFiles('**/*.gradle', '**/gradle-wrapper.properties') }} + restore-keys: ${{ runner.os }}-gradle-integration-test- + + - name: Build FullNode.jar + run: ./gradlew clean build -x test --no-daemon + + - name: Pull integration-test image + run: docker pull troninfra/troninfra-ci:latest + + - name: Run integration tests + run: | + # JAVA_HOME=JDK 8 so FullNode runs on the same JVM family as + # production (a few assertions check `java.version` starts with + # "1.8"). JAVA_HOME_17 keeps Gradle on JDK 17 for the test + # tooling, which requires Java 17. + docker run --name integration-test \ + -e FULLNODE_JAR=/javatron/FullNode.jar \ + -e JAVA_HOME=/usr/lib/jvm/temurin-8 \ + -e JAVA_HOME_17=/opt/java/openjdk \ + -v "${{ github.workspace }}/build/libs/FullNode.jar:/javatron/FullNode.jar:ro" \ + troninfra/troninfra-ci:latest \ + --clean + + - name: Extract test reports from container + if: always() + run: | + mkdir -p integration-reports + docker cp integration-test:/app/build/reports/. integration-reports/reports/ 2>/dev/null || true + docker cp integration-test:/app/build/test-results/. integration-reports/test-results/ 2>/dev/null || true + docker cp integration-test:/app/build/test-output.log integration-reports/ 2>/dev/null || true + docker cp integration-test:/app/node/node.log integration-reports/ 2>/dev/null || true + docker cp integration-test:/app/node/data/logs/tron.log integration-reports/ 2>/dev/null || true + docker rm -f integration-test 2>/dev/null || true + + - name: Upload test reports + if: always() + uses: actions/upload-artifact@v6 + with: + name: integration-test-report + path: integration-reports/ + if-no-files-found: warn diff --git a/.github/workflows/pr-cancel.yml b/.github/workflows/pr-cancel.yml index bbd0e68c235..3213026d3f9 100644 --- a/.github/workflows/pr-cancel.yml +++ b/.github/workflows/pr-cancel.yml @@ -13,43 +13,55 @@ jobs: if: github.event.pull_request.merged == false runs-on: ubuntu-latest steps: - - name: Cancel PR Build and System Test + - name: Cancel PR Build and Integration Tests uses: actions/github-script@v8 with: script: | - const workflows = ['pr-build.yml', 'system-test.yml', 'codeql.yml']; + const workflows = [ + 'pr-build.yml', + 'codeql.yml', + 'integration-test-single-node.yml', + 'integration-test-multinode.yml', + ]; const headSha = context.payload.pull_request.head.sha; const prNumber = context.payload.pull_request.number; for (const workflowId of workflows) { - for (const status of ['in_progress', 'queued']) { - const runs = await github.paginate( - github.rest.actions.listWorkflowRuns, - { - owner: context.repo.owner, - repo: context.repo.repo, - workflow_id: workflowId, - status, - event: 'pull_request', - per_page: 100, - }, - (response) => response.data.workflow_runs - ); - - for (const run of runs) { - if (!run) { - continue; - } - const prs = Array.isArray(run.pull_requests) ? run.pull_requests : []; - const isTargetPr = prs.length === 0 || prs.some((pr) => pr.number === prNumber); - if (run.head_sha === headSha && isTargetPr) { - await github.rest.actions.cancelWorkflowRun({ + // Wrap each workflow iteration so a missing / renamed file + // doesn't take down the whole cancel job — other workflows + // in the list still get processed. + try { + for (const status of ['in_progress', 'queued']) { + const runs = await github.paginate( + github.rest.actions.listWorkflowRuns, + { owner: context.repo.owner, repo: context.repo.repo, - run_id: run.id, - }); - console.log(`Cancelled ${workflowId} run #${run.id} (${status})`); + workflow_id: workflowId, + status, + event: 'pull_request', + per_page: 100, + }, + (response) => response.data.workflow_runs + ); + + for (const run of runs) { + if (!run) { + continue; + } + const prs = Array.isArray(run.pull_requests) ? run.pull_requests : []; + const isTargetPr = prs.length === 0 || prs.some((pr) => pr.number === prNumber); + if (run.head_sha === headSha && isTargetPr) { + await github.rest.actions.cancelWorkflowRun({ + owner: context.repo.owner, + repo: context.repo.repo, + run_id: run.id, + }); + console.log(`Cancelled ${workflowId} run #${run.id} (${status})`); + } } } + } catch (err) { + console.log(`Skipping ${workflowId}: ${err.message}`); } } diff --git a/.github/workflows/system-test.yml b/.github/workflows/system-test.yml deleted file mode 100644 index f6184fb0efc..00000000000 --- a/.github/workflows/system-test.yml +++ /dev/null @@ -1,95 +0,0 @@ -name: System Test - -on: - push: - branches: [ 'master', 'release_**' ] - pull_request: - branches: [ 'develop', 'release_**' ] - types: [ opened, synchronize, reopened ] - paths-ignore: [ '**/*.md', '.gitignore', '**/.gitignore', '.editorconfig', - '.gitattributes', 'docs/**', 'CHANGELOG', '.github/ISSUE_TEMPLATE/**', - '.github/PULL_REQUEST_TEMPLATE/**', '.github/CODEOWNERS' ] - -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true - -jobs: - system-test: - name: System Test (JDK 8 / x86_64) - runs-on: ubuntu-latest - timeout-minutes: 60 - - steps: - - name: Set up JDK 8 - uses: actions/setup-java@v5 - with: - java-version: '8' - distribution: 'temurin' - - - name: Clone system-test - uses: actions/checkout@v5 - with: - repository: tronprotocol/system-test - ref: release_workflow - path: system-test - - - name: Checkout java-tron - uses: actions/checkout@v5 - with: - path: java-tron - - - name: Cache Gradle packages - uses: actions/cache@v4 - with: - path: | - ~/.gradle/caches - ~/.gradle/wrapper - key: ${{ runner.os }}-gradle-system-test-${{ hashFiles('java-tron/**/*.gradle', 'java-tron/**/gradle-wrapper.properties') }} - restore-keys: ${{ runner.os }}-gradle-system-test- - - - name: Build java-tron - working-directory: java-tron - run: ./gradlew clean build -x test --no-daemon - - - name: Copy config and start FullNode - run: | - cp system-test/testcase/src/test/resources/config-system-test.conf java-tron/ - cd java-tron - nohup java -jar build/libs/FullNode.jar --witness -c config-system-test.conf > fullnode.log 2>&1 & - echo "FullNode started, waiting for it to be ready..." - - MAX_ATTEMPTS=60 - INTERVAL=5 - for i in $(seq 1 $MAX_ATTEMPTS); do - if curl -s --fail "http://localhost:8090/wallet/getblockbynum?num=1" > /dev/null 2>&1; then - echo "FullNode is ready! (attempt $i)" - exit 0 - fi - echo "Waiting... (attempt $i/$MAX_ATTEMPTS)" - sleep $INTERVAL - done - - echo "FullNode failed to start within $((MAX_ATTEMPTS * INTERVAL)) seconds." - echo "=== FullNode log (last 50 lines) ===" - tail -50 fullnode.log || true - exit 1 - - - name: Run system tests - working-directory: system-test - run: | - if [ ! -f solcDIR/solc-linux-0.8.6 ]; then - echo "ERROR: solc binary not found at solcDIR/solc-linux-0.8.6" - exit 1 - fi - cp solcDIR/solc-linux-0.8.6 solcDIR/solc - ./gradlew clean --no-daemon - ./gradlew --info stest --no-daemon - - - name: Upload FullNode log - if: always() - uses: actions/upload-artifact@v6 - with: - name: fullnode-log - path: java-tron/fullnode.log - if-no-files-found: warn