diff --git a/.github/workflows/codecov-upload.yml b/.github/workflows/codecov-upload.yml new file mode 100644 index 0000000000..755e897a38 --- /dev/null +++ b/.github/workflows/codecov-upload.yml @@ -0,0 +1,194 @@ +name: Codecov Upload + +on: + workflow_run: + workflows: ["PR Check"] + types: [completed] + +jobs: + coverage-gate: + name: Coverage Gate + if: github.event.workflow_run.conclusion == 'success' + runs-on: ubuntu-latest + + permissions: + contents: read + actions: read + + defaults: + run: + shell: bash + + steps: + - name: Get PR info from triggering workflow + id: pr-info + uses: actions/github-script@v7 + with: + script: | + const run_id = context.payload.workflow_run.id; + const prs = context.payload.workflow_run.pull_requests; + if (!prs || prs.length === 0) { + core.setFailed('No pull request associated with this workflow run.'); + return; + } + const pr = prs[0]; + core.setOutput('pr_number', pr.number); + core.setOutput('head_sha', pr.head.sha); + core.setOutput('head_ref', pr.head.ref); + core.setOutput('base_ref', pr.base.ref); + core.setOutput('run_id', run_id); + + - name: Checkout code (needed by codecov-action for git context) + uses: actions/checkout@v4 + with: + ref: ${{ steps.pr-info.outputs.head_sha }} + + - name: Download JaCoCo artifacts from PR Check + uses: actions/download-artifact@v4 + with: + name: jacoco-rockylinux + path: artifacts/jacoco-rockylinux + run-id: ${{ steps.pr-info.outputs.run_id }} + github-token: ${{ secrets.GITHUB_TOKEN }} + + - name: List downloaded reports + run: | + set -eux + echo "JaCoCo XML reports found:" + find artifacts/jacoco-rockylinux -name jacocoTestReport.xml + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v5 + with: + token: ${{ secrets.CODECOV_TOKEN }} + directory: artifacts/jacoco-rockylinux + override_commit: ${{ steps.pr-info.outputs.head_sha }} + override_branch: ${{ steps.pr-info.outputs.head_ref }} + override_pr: ${{ steps.pr-info.outputs.pr_number }} + verbose: true + fail_ci_if_error: true + + - name: Install tools + run: sudo apt-get update && sudo apt-get install -y jq bc curl + + - name: Wait for Codecov processing + env: + CODECOV_API_TOKEN: ${{ secrets.CODECOV_API_TOKEN }} + CODECOV_OWNER: ${{ github.repository_owner }} + CODECOV_REPO: ${{ github.event.repository.name }} + COMMIT_ID: ${{ steps.pr-info.outputs.head_sha }} + run: | + set -euxo pipefail + + API_URL="https://api.codecov.io/api/v2/github/${CODECOV_OWNER}/repos/${CODECOV_REPO}/commits/${COMMIT_ID}" + MAX_ATTEMPTS=20 + INTERVAL=30 + + for i in $(seq 1 $MAX_ATTEMPTS); do + echo "=== Polling attempt $i / $MAX_ATTEMPTS ===" + + http_code=$(curl -sS -o /tmp/poll.json -w '%{http_code}' \ + -H "Authorization: Bearer ${CODECOV_API_TOKEN}" \ + "$API_URL") + + if [ "$http_code" = "200" ]; then + state=$(jq -r '.state // "unknown"' /tmp/poll.json) + echo "Commit processing state: $state" + if [ "$state" = "complete" ]; then + echo "Codecov has finished processing." + exit 0 + fi + else + echo "HTTP $http_code — commit not yet available." + cat /tmp/poll.json 2>/dev/null || true + fi + + if [ "$i" -lt "$MAX_ATTEMPTS" ]; then + sleep "$INTERVAL" + fi + done + + echo "Timed out waiting for Codecov (${MAX_ATTEMPTS} x ${INTERVAL}s)." + exit 1 + + - name: Coverage gate via Codecov REST API + env: + CODECOV_API_TOKEN: ${{ secrets.CODECOV_API_TOKEN }} + CODECOV_OWNER: ${{ github.repository_owner }} + CODECOV_REPO: ${{ github.event.repository.name }} + COMMIT_ID: ${{ steps.pr-info.outputs.head_sha }} + BASE_BRANCH: ${{ steps.pr-info.outputs.base_ref }} + PR_NUMBER: ${{ steps.pr-info.outputs.pr_number }} + run: | + set -euxo pipefail + + API_BASE="https://api.codecov.io/api/v2/github/${CODECOV_OWNER}/repos/${CODECOV_REPO}" + AUTH="Authorization: Bearer ${CODECOV_API_TOKEN}" + + # Helper: GET with error handling + api_get() { + local url="$1" + local http_code + http_code=$(curl -sS -o /tmp/api_out.json -w '%{http_code}' \ + -H "$AUTH" "$url") + if [ "$http_code" != "200" ]; then + echo "ERROR: GET $url => HTTP $http_code" >&2 + cat /tmp/api_out.json >&2 + return 1 + fi + cat /tmp/api_out.json + } + + # 1) Current commit coverage + echo "=== 1. Current commit coverage (sha: ${COMMIT_ID}) ===" + commit_resp=$(api_get "${API_BASE}/totals/?sha=${COMMIT_ID}") + self_cov=$(echo "$commit_resp" | jq -r '.totals.coverage // 0') + echo "self_cov = ${self_cov}%" + + # 2) Base branch head coverage + echo "=== 2. Base branch coverage (branch: ${BASE_BRANCH}) ===" + base_resp=$(api_get "${API_BASE}/totals/?branch=${BASE_BRANCH}") + base_branch_cov=$(echo "$base_resp" | jq -r '.totals.coverage // 0') + echo "base_branch_cov = ${base_branch_cov}%" + + # 3) PR comparison — patch coverage + echo "=== 3. PR #${PR_NUMBER} comparison ===" + compare_resp=$(api_get "${API_BASE}/compare/?pullid=${PR_NUMBER}") + patch_cov=$(echo "$compare_resp" | jq -r '.totals.patch.coverage // 0') + impacted_files=$(echo "$compare_resp" | jq -r '(.files // []) | length') + echo "patch_cov = ${patch_cov}%" + echo "impacted_files = ${impacted_files}" + + # ===== Gate Rules ===== + + # Rule 1: current commit must have valid coverage + if [ "$(echo "$self_cov <= 0" | bc)" -eq 1 ]; then + echo "FAIL: Could not retrieve valid coverage for commit ${COMMIT_ID}." + exit 1 + fi + + # Rule 2: overall coverage must not decrease vs base branch + if [ "$(echo "$self_cov < $base_branch_cov" | bc)" -eq 1 ]; then + echo "FAIL: Overall coverage decreased!" + echo " Current commit : ${self_cov}%" + echo " Base branch : ${base_branch_cov}%" + echo "Please add unit tests to maintain coverage." + exit 1 + fi + + # Rule 3: patch coverage on changed files >= 80% + # if [ "$impacted_files" -gt 0 ] && [ "$(echo "$patch_cov > 0" | bc)" -eq 1 ]; then + # if [ "$(echo "$patch_cov < 80" | bc)" -eq 1 ]; then + # echo "FAIL: Patch coverage is ${patch_cov}% (minimum 80%)." + # echo "Please add tests for new/changed code." + # exit 1 + # fi + # else + # echo "No impacted files or no patch data; skipping patch coverage check." + # fi + + echo "" + echo "All coverage gates passed!" + echo " Current commit : ${self_cov}%" + echo " Base branch : ${base_branch_cov}%" + echo " Patch coverage : ${patch_cov}%" diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 5a0f120e11..95f78c50a6 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -29,36 +29,16 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v3 + uses: github/codeql-action/init@v4 with: languages: ${{ matrix.language }} - # If you wish to specify custom queries, you can do so here or in a config file. - # By default, queries listed here will override any specified in a config file. - # Prefix the list here with "+" to use these queries and those in the config file. - - # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs - # queries: security-extended,security-and-quality - - # Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift). - # If this step fails, then you should remove it and run the build manually (see below) - - name: Autobuild - uses: github/codeql-action/autobuild@v3 - - # â„šī¸ Command-line programs to run using the OS shell. - # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun - - # If the Autobuild fails above, remove it and uncomment the following three lines. - # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. - - # - run: | - # echo "Run, Build Application using script" - # ./location_of_script_within_repo/buildscript.sh + build-mode: none - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 + uses: github/codeql-action/analyze@v4 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/pr-check.yml b/.github/workflows/pr-check.yml index f4593f64d4..d4fd3f3b7e 100644 --- a/.github/workflows/pr-check.yml +++ b/.github/workflows/pr-check.yml @@ -93,53 +93,17 @@ jobs: core.info('PR lint passed.'); } - build: - name: Build (JDK ${{ matrix.java }} / ${{ matrix.arch }}) - needs: pr-lint - runs-on: ${{ matrix.runner }} - strategy: - fail-fast: false - matrix: - include: - - java: '8' - runner: ubuntu-latest - arch: x86_64 - - java: '17' - runner: ubuntu-24.04-arm - arch: aarch64 - - steps: - - uses: actions/checkout@v4 - - - name: Set up JDK ${{ matrix.java }} - uses: actions/setup-java@v4 - with: - java-version: ${{ matrix.java }} - distribution: 'temurin' - - - name: Cache Gradle packages - uses: actions/cache@v4 - with: - path: | - ~/.gradle/caches - ~/.gradle/wrapper - key: ${{ runner.os }}-${{ matrix.arch }}-gradle-${{ hashFiles('**/*.gradle', '**/gradle-wrapper.properties') }} - restore-keys: ${{ runner.os }}-${{ matrix.arch }}-gradle- - - - name: Build - run: ./gradlew clean build -x test - checkstyle: name: Checkstyle - runs-on: ubuntu-latest + runs-on: ubuntu-24.04-arm steps: - uses: actions/checkout@v4 - - name: Set up JDK 8 + - name: Set up JDK 17 uses: actions/setup-java@v4 with: - java-version: '8' + java-version: '17' distribution: 'temurin' - name: Cache Gradle packages @@ -163,20 +127,29 @@ jobs: framework/build/reports/checkstyle/ plugins/build/reports/checkstyle/ - test: - name: Unit Tests (JDK ${{ matrix.java }} / ${{ matrix.arch }}) + build: + name: Build ${{ matrix.os-name }}(JDK ${{ matrix.java }} / ${{ matrix.arch }}) + needs: [pr-lint, checkstyle] runs-on: ${{ matrix.runner }} - needs: build - timeout-minutes: 60 strategy: fail-fast: false matrix: include: - java: '8' runner: ubuntu-latest + os-name: ubuntu arch: x86_64 - java: '17' runner: ubuntu-24.04-arm + os-name: ubuntu + arch: aarch64 + - java: '8' + runner: macos-26-intel + os-name: macos + arch: x86_64 + - java: '17' + runner: macos-latest + os-name: macos arch: aarch64 steps: @@ -197,13 +170,115 @@ jobs: key: ${{ runner.os }}-${{ matrix.arch }}-gradle-${{ hashFiles('**/*.gradle', '**/gradle-wrapper.properties') }} restore-keys: ${{ runner.os }}-${{ matrix.arch }}-gradle- - - name: Run tests - run: ./gradlew test + - name: Build + run: ./gradlew clean build --no-daemon - - name: Upload test reports - if: failure() + docker-build-rockylinux: + name: Build rockylinux (JDK 8 / x86_64) + needs: [pr-lint, checkstyle] + runs-on: ubuntu-latest + + container: + image: rockylinux:8 + + env: + GRADLE_USER_HOME: /github/home/.gradle + LANG: en_US.UTF-8 + LC_ALL: en_US.UTF-8 + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Install dependencies (Rocky 8 + JDK8) + run: | + set -euxo pipefail + dnf -y install java-1.8.0-openjdk-devel git wget unzip which jq bc curl glibc-langpack-en + dnf -y groupinstall "Development Tools" + + - name: Check Java version + run: java -version + + - name: Cache Gradle + uses: actions/cache@v4 + with: + path: | + /github/home/.gradle/caches + /github/home/.gradle/wrapper + key: ${{ runner.os }}-rockylinux-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} + restore-keys: | + ${{ runner.os }}-rockylinux-gradle- + + - name: Prepare checkstyle config copy + run: | + set -euxo pipefail + cp -f config/checkstyle/checkStyle.xml config/checkstyle/checkStyleAll.xml || true + + - name: Grant execute permission + run: chmod +x gradlew + + - name: Stop Gradle daemon + run: ./gradlew --stop || true + + - name: Build + run: ./gradlew clean build --no-daemon --no-build-cache + #run: | + # ./gradlew clean build -x test --no-daemon --no-build-cache + # ./gradlew framework:test --tests org.tron.core.zksnark.ShieldedReceiveTest + + - name: Generate JaCoCo report + run: ./gradlew jacocoTestReport --no-daemon --no-build-cache + + - name: Upload JaCoCo artifacts uses: actions/upload-artifact@v4 with: - name: test-reports-${{ matrix.arch }} + name: jacoco-rockylinux + path: | + **/build/reports/jacoco/test/jacocoTestReport.xml + **/build/reports/** + **/build/test-results/** + if-no-files-found: error + + docker-build-debian11: + name: Build debian11 (JDK 8 / x86_64) + needs: [pr-lint, checkstyle] + runs-on: ubuntu-latest + + container: + image: eclipse-temurin:8-jdk # base image is Debian 11 (Bullseye) + + defaults: + run: + shell: bash + + env: + GRADLE_USER_HOME: /github/home/.gradle + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Install dependencies (Debian + build tools) + run: | + set -euxo pipefail + apt-get update + apt-get install -y git wget unzip build-essential curl jq + + - name: Check Java version + run: java -version + + - name: Cache Gradle + uses: actions/cache@v4 + with: path: | - **/build/reports/tests/ + /github/home/.gradle/caches + /github/home/.gradle/wrapper + key: ${{ runner.os }}-debian11-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} + restore-keys: | + ${{ runner.os }}-debian11-gradle- + + - name: Grant execute permission + run: chmod +x gradlew + + - name: Build + run: ./gradlew clean build --no-daemon --no-build-cache diff --git a/.github/workflows/sonar-check.yml b/.github/workflows/sonar-check.yml new file mode 100644 index 0000000000..3c2a4b6e6f --- /dev/null +++ b/.github/workflows/sonar-check.yml @@ -0,0 +1,72 @@ +name: SonarCloud Analysis + +on: + pull_request: + branches: [ 'develop', 'release_**' ] + types: [ opened, edited, synchronize, reopened ] + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.ref }} + cancel-in-progress: true + +permissions: + contents: read # don't comment in PR after check + +jobs: + sonar-check: + name: SonarCloud Analysis + runs-on: ubuntu-24.04-arm + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'temurin' + + - name: Cache SonarCloud packages + uses: actions/cache@v4 + with: + path: ~/.sonar/cache + key: ${{ runner.os }}-sonar + restore-keys: ${{ runner.os }}-sonar + + - name: Cache Gradle packages + uses: actions/cache@v4 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle', '**/gradle-wrapper.properties') }} + restore-keys: ${{ runner.os }}-gradle- + + - name: Build and analyze + env: + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + run: | + cat > /tmp/sonar-init.gradle << 'EOF' + initscript { + repositories { + maven { url 'https://plugins.gradle.org/m2/' } + } + dependencies { + classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:5.1.0.4882' + } + } + rootProject { + apply plugin: org.sonarqube.gradle.SonarQubePlugin + } + EOF + + ./gradlew clean classes testClasses sonar --info \ + --init-script /tmp/sonar-init.gradle \ + -PskipJdkCheck \ + -Dsonar.host.url=https://sonarcloud.io \ + -Dsonar.organization=tron-zhaohong \ + -Dsonar.projectKey=java-tron \ + -Dsonar.qualitygate.wait=false \ + -Dsonar.pullrequest.github.summary_comment=false diff --git a/.github/workflows/system-test.yml b/.github/workflows/system-test.yml new file mode 100644 index 0000000000..36bef7a460 --- /dev/null +++ b/.github/workflows/system-test.yml @@ -0,0 +1,71 @@ +name: System Test + +on: + pull_request: + branches: [ 'develop', 'release_**' ] + types: [ opened, edited, synchronize, reopened ] + +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.ref }} + cancel-in-progress: true + +jobs: + system-test: + name: System Test (JDK 8 / x86_64) + runs-on: ubuntu-latest + + steps: + - name: Set up JDK 8 + uses: actions/setup-java@v4 + with: + java-version: '8' + distribution: 'temurin' + + - name: Clone system-test + run: | + git clone https://github.com/tronprotocol/system-test.git + cd system-test + git checkout release_workflow + + - name: Checkout java-tron + uses: actions/checkout@v4 + 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 30 seconds..." + sleep 30 + echo "=== FullNode log (last 30 lines) ===" + tail -30 fullnode.log || true + + - name: Run system tests + working-directory: system-test + run: | + 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@v4 + with: + name: fullnode-log + path: java-tron/fullnode.log + if-no-files-found: warn diff --git a/build.gradle b/build.gradle index 12a0622db9..6f00196994 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,6 @@ import org.gradle.nativeplatform.platform.internal.Architectures import org.gradle.internal.os.OperatingSystem + allprojects { version = "1.0.0" apply plugin: "java-library" @@ -40,7 +41,7 @@ ext.archInfo = [ VMOptions: isArm64 ? "${rootDir}/gradle/jdk17/java-tron.vmoptions" : "${rootDir}/gradle/java-tron.vmoptions" ] -if (!archInfo.java.is(archInfo.requires.JavaVersion)) { +if (!archInfo.java.is(archInfo.requires.JavaVersion) && !project.hasProperty('skipJdkCheck')) { throw new GradleException("Java ${archInfo.requires.JavaVersion} is required for ${archInfo.name}. Detected version ${archInfo.java}") } @@ -165,3 +166,26 @@ gradle.buildFinished { } } } + +// SonarQube configuration — only activates when the plugin is applied (via CI init script) +pluginManager.withPlugin('org.sonarqube') { + sonar { + properties { + property "sonar.sourceEncoding", "UTF-8" + } + } + + // Skip these projects + ["protocol", "platform", "example:actuator-example"].each { name -> + project(":${name}").sonar.skipProject = true + } + + // Only analyze main sources, exclude test sources + subprojects { + sonar { + properties { + property "sonar.tests", "" + } + } + } +} diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 0000000000..69cb76019a --- /dev/null +++ b/codecov.yml @@ -0,0 +1 @@ +comment: false diff --git a/framework/build.gradle b/framework/build.gradle index 59d070e066..42e905fda6 100644 --- a/framework/build.gradle +++ b/framework/build.gradle @@ -1,6 +1,6 @@ plugins { id "org.gradle.test-retry" version "1.5.9" - id "org.sonarqube" version "2.6" + id "com.gorylenko.gradle-git-properties" version "2.4.1" } diff --git a/framework/src/main/java/org/tron/core/config/args/Args.java b/framework/src/main/java/org/tron/core/config/args/Args.java index 38f1fe4f95..b99b7f54b7 100644 --- a/framework/src/main/java/org/tron/core/config/args/Args.java +++ b/framework/src/main/java/org/tron/core/config/args/Args.java @@ -344,7 +344,7 @@ private static String getCommitIdAbbrev() { InputStream in = Thread.currentThread() .getContextClassLoader().getResourceAsStream("git.properties"); properties.load(in); - } catch (IOException e) { + } catch (Exception e) { logger.warn("Load resource failed,git.properties {}", e.getMessage()); } return properties.getProperty("git.commit.id.abbrev"); diff --git a/gradle.properties b/gradle.properties index 031a8cddc8..09532810ad 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,2 +1,4 @@ org.gradle.parallel=true org.gradle.jvmargs=-Xms1g +org.gradle.caching=true +org.gradle.daemon=false \ No newline at end of file diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index 4d0bf1013d..3dd82f7e74 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -2262,6 +2262,19 @@ + + + + + + + + + + + + + @@ -2270,6 +2283,11 @@ + + + + + @@ -2283,6 +2301,14 @@ + + + + + + + + diff --git a/plugins/build.gradle b/plugins/build.gradle index e03e9a7c49..6f18f2b9d4 100644 --- a/plugins/build.gradle +++ b/plugins/build.gradle @@ -1,5 +1,4 @@ plugins { - id "org.sonarqube" version "2.6" } apply plugin: 'application'