Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 83 additions & 25 deletions .github/workflows/pr-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,61 @@ jobs:
echo "base_xmls=$BASE_XMLS" >> "$GITHUB_OUTPUT"
echo "pr_xmls=$PR_XMLS" >> "$GITHUB_OUTPUT"

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'

- name: Changed-line coverage (diff-cover)
id: diff-cover
env:
BASE_REF: ${{ github.event.pull_request.base.ref }}
run: |
set -euo pipefail
pip install --quiet 'diff-cover==9.2.0'

# Ensure the base branch ref is available locally for diff-cover.
git fetch --no-tags origin "+refs/heads/${BASE_REF}:refs/remotes/origin/${BASE_REF}"

PR_XMLS=$(find coverage/pr -name "jacocoTestReport.xml" | sort)
SRC_ROOTS=$(find . -type d -path '*/src/main/java' \
-not -path './coverage/*' -not -path './.git/*' | sort)

set +e
diff-cover $PR_XMLS \
--compare-branch="origin/${BASE_REF}" \
--src-roots $SRC_ROOTS \
--fail-under=0 \
--json-report=diff-cover.json \
--markdown-report=diff-cover.md
DIFF_RC=$?
set -e

TOTAL_NUM_LINES=$(jq -r '.total_num_lines // 0' diff-cover.json)
if [ "${TOTAL_NUM_LINES}" = "0" ]; then
echo "No changed Java source lines; skipping changed-line gate."
echo "changed_line_coverage=NA" >> "$GITHUB_OUTPUT"
else
CHANGED_LINE_COVERAGE=$(jq -r '.total_percent_covered // empty' diff-cover.json)
if [ -z "$CHANGED_LINE_COVERAGE" ]; then
echo "Unable to parse changed-line coverage from diff-cover.json."
exit 1
fi
echo "changed_line_coverage=${CHANGED_LINE_COVERAGE}" >> "$GITHUB_OUTPUT"
fi

{
echo "### Changed-line Coverage (diff-cover)"
echo ""
if [ -f diff-cover.md ] && [ -s diff-cover.md ]; then
cat diff-cover.md
else
echo "_diff-cover produced no report._"
fi
} >> "$GITHUB_STEP_SUMMARY"

echo "diff-cover exit code: ${DIFF_RC}"

- name: Aggregate base coverage
id: jacoco-base
uses: madrapps/jacoco-report@v1.7.2
Expand All @@ -288,6 +343,7 @@ jobs:
min-coverage-overall: 0
min-coverage-changed-files: 0
skip-if-no-changes: true
comment-type: summary
title: '## Base Coverage Snapshot'
update-comment: false

Expand All @@ -300,14 +356,15 @@ jobs:
min-coverage-overall: 0
min-coverage-changed-files: 0
skip-if-no-changes: true
comment-type: summary
title: '## PR Code Coverage Report'
update-comment: false

- name: Enforce coverage gates
env:
BASE_OVERALL_RAW: ${{ steps.jacoco-base.outputs.coverage-overall }}
PR_OVERALL_RAW: ${{ steps.jacoco-pr.outputs.coverage-overall }}
PR_CHANGED_RAW: ${{ steps.jacoco-pr.outputs.coverage-changed-files }}
CHANGED_LINE_RAW: ${{ steps.diff-cover.outputs.changed_line_coverage }}
run: |
set -euo pipefail

Expand All @@ -329,7 +386,7 @@ jobs:
# 1) Parse metrics from jacoco-report outputs
BASE_OVERALL="$(sanitize "$BASE_OVERALL_RAW")"
PR_OVERALL="$(sanitize "$PR_OVERALL_RAW")"
PR_CHANGED="$(sanitize "$PR_CHANGED_RAW")"
CHANGED_LINE="$(sanitize "$CHANGED_LINE_RAW")"

if ! is_number "$BASE_OVERALL" || ! is_number "$PR_OVERALL"; then
echo "Failed to parse coverage values: base='${BASE_OVERALL}', pr='${PR_OVERALL}'."
Expand All @@ -340,18 +397,18 @@ jobs:
DELTA=$(awk -v pr="$PR_OVERALL" -v base="$BASE_OVERALL" 'BEGIN { printf "%.4f", pr - base }')
DELTA_OK=$(compare_float "${DELTA} >= ${MAX_DROP}")

CHANGED_STATUS="SKIPPED (no changed coverage value)"
CHANGED_OK=1
if [ -n "$PR_CHANGED" ] && [ "$PR_CHANGED" != "NaN" ]; then
if ! is_number "$PR_CHANGED"; then
echo "Failed to parse changed-files coverage: changed='${PR_CHANGED}'."
exit 1
fi
CHANGED_OK=$(compare_float "${PR_CHANGED} > ${MIN_CHANGED}")
if [ "$CHANGED_OK" -eq 1 ]; then
CHANGED_STATUS="PASS (> ${MIN_CHANGED}%)"
if [ "$CHANGED_LINE" = "NA" ]; then
CHANGED_LINE_OK=1
CHANGED_LINE_STATUS="SKIPPED (no changed Java source lines)"
elif [ -z "$CHANGED_LINE" ] || [ "$CHANGED_LINE" = "NaN" ] || ! is_number "$CHANGED_LINE"; then
echo "Failed to parse changed-line coverage: changed-line='${CHANGED_LINE}'."
exit 1
else
CHANGED_LINE_OK=$(compare_float "${CHANGED_LINE} > ${MIN_CHANGED}")
if [ "$CHANGED_LINE_OK" -eq 1 ]; then
CHANGED_LINE_STATUS="PASS (> ${MIN_CHANGED}%)"
else
CHANGED_STATUS="FAIL (<= ${MIN_CHANGED}%)"
CHANGED_LINE_STATUS="FAIL (<= ${MIN_CHANGED}%)"
fi
fi

Expand All @@ -361,12 +418,18 @@ jobs:
OVERALL_STATUS="FAIL (< ${MAX_DROP}%)"
fi

if [ "$CHANGED_LINE" = "NA" ]; then
CHANGED_LINE_DISPLAY="NA"
else
CHANGED_LINE_DISPLAY="${CHANGED_LINE}%"
fi

METRICS_TEXT=$(cat <<EOF
Changed Files Coverage: ${PR_CHANGED}%
Changed-line Coverage: ${CHANGED_LINE_DISPLAY}
PR Overall Coverage: ${PR_OVERALL}%
Base Overall Coverage: ${BASE_OVERALL}%
Delta (PR - Base): ${DELTA}%
Changed Files Gate: ${CHANGED_STATUS}
Changed-line Gate: ${CHANGED_LINE_STATUS}
Overall Delta Gate: ${OVERALL_STATUS}
EOF
)
Expand All @@ -376,11 +439,11 @@ jobs:
{
echo "### Coverage Gate Metrics"
echo ""
echo "- Changed Files Coverage: ${PR_CHANGED}%"
echo "- Changed-line Coverage: ${CHANGED_LINE_DISPLAY}"
echo "- PR Overall Coverage: ${PR_OVERALL}%"
echo "- Base Overall Coverage: ${BASE_OVERALL}%"
echo "- Delta (PR - Base): ${DELTA}%"
echo "- Changed Files Gate: ${CHANGED_STATUS}"
echo "- Changed-line Gate: ${CHANGED_LINE_STATUS}"
echo "- Overall Delta Gate: ${OVERALL_STATUS}"
} >> "$GITHUB_STEP_SUMMARY"

Expand All @@ -391,14 +454,9 @@ jobs:
exit 1
fi

if [ -z "$PR_CHANGED" ] || [ "$PR_CHANGED" = "NaN" ]; then
echo "No changed-files coverage value detected, skip changed-files gate."
exit 0
fi

if [ "$CHANGED_OK" -ne 1 ]; then
echo "Coverage gate failed: changed files coverage must be > 60%."
echo "changed=${PR_CHANGED}%"
if [ "$CHANGED_LINE_OK" -ne 1 ]; then
echo "Coverage gate failed: changed-line coverage must be > 60%."
echo "changed-line=${CHANGED_LINE}%"
exit 1
fi

Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -220,3 +220,5 @@ Thank you for considering to help out with the source code! If you'd like to con
# License

java-tron is released under the [LGPLv3 license](https://github.com/tronprotocol/java-tron/blob/master/LICENSE).

<!-- intentional non-Java change to verify Coverage Gate G1.4 (mixed PR ignores docs lines) -->
15 changes: 15 additions & 0 deletions common/src/main/java/org/tron/common/utils/DecodeUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,19 @@ public static boolean addressValid(byte[] address) {
return true;
}

/**
* Intentional uncovered helper used to exercise the Coverage Gate FAIL path
* (changed-line coverage below 60%). No unit test is added on purpose.
* Revert before merging.
*/
public static int hexLengthForAddress(int byteLength) {
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: Temporary coverage-gate helper was added as a public production API; remove it or move it to test-only code before merge.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At common/src/main/java/org/tron/common/utils/DecodeUtil.java, line 40:

<comment>Temporary coverage-gate helper was added as a public production API; remove it or move it to test-only code before merge.</comment>

<file context>
@@ -32,4 +32,19 @@ public static boolean addressValid(byte[] address) {
+   * (changed-line coverage below 60%). No unit test is added on purpose.
+   * Revert before merging.
+   */
+  public static int hexLengthForAddress(int byteLength) {
+    if (byteLength < 0) {
+      throw new IllegalArgumentException("byteLength must be non-negative");
</file context>
Fix with Cubic

if (byteLength < 0) {
throw new IllegalArgumentException("byteLength must be non-negative");
}
if (byteLength == 0) {
return 0;
}
return byteLength * 2;
}
Comment on lines +35 to +48
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Sandbox-only change — ensure this is reverted before merge.

Per the PR description, this method is intentionally added to exercise the Coverage Gate FAIL path and must not be merged. The Javadoc correctly flags this, but it's worth double-checking that once the gate behavior is observed, this PR is closed/reverted and hexLengthForAddress is not left behind as unused public API in common. Since the method has no callers in the tree (existing address-length checks use ADDRESS_SIZE / ADDRESS_SIZE / 2 directly in JsonRpcApiUtil, ValidateAddressServlet, etc.), landing it would just add dead public surface.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@common/src/main/java/org/tron/common/utils/DecodeUtil.java` around lines 35 -
48, Remove the sandbox-only helper method hexLengthForAddress from DecodeUtil to
avoid introducing dead public API; revert the added method (public static int
hexLengthForAddress(int byteLength)) from org.tron.common.utils.DecodeUtil so
callers continue to use existing ADDRESS_SIZE/ADDRESS_SIZE/2 logic and ensure no
tests or usages reference hexLengthForAddress before merging.


}
58 changes: 58 additions & 0 deletions framework/src/test/java/org/tron/common/utils/DecodeUtilTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package org.tron.common.utils;

import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.tron.core.Constant;

public class DecodeUtilTest {

private static byte savedPrefixByte;

@BeforeClass
public static void saveAddressPrefix() {
savedPrefixByte = DecodeUtil.addressPreFixByte;
DecodeUtil.addressPreFixByte = Constant.ADD_PRE_FIX_BYTE_MAINNET;
}

@AfterClass
public static void restoreAddressPrefix() {
DecodeUtil.addressPreFixByte = savedPrefixByte;
}

@Test
public void addressValidRejectsNull() {
Assert.assertFalse(DecodeUtil.addressValid(null));
}

@Test
public void addressValidRejectsEmptyArray() {
Assert.assertFalse(DecodeUtil.addressValid(new byte[0]));
}

@Test
public void addressValidRejectsWrongLength() {
byte[] tooShort = new byte[DecodeUtil.ADDRESS_SIZE / 2 - 1];
tooShort[0] = Constant.ADD_PRE_FIX_BYTE_MAINNET;
Assert.assertFalse(DecodeUtil.addressValid(tooShort));

byte[] tooLong = new byte[DecodeUtil.ADDRESS_SIZE / 2 + 1];
tooLong[0] = Constant.ADD_PRE_FIX_BYTE_MAINNET;
Assert.assertFalse(DecodeUtil.addressValid(tooLong));
}

@Test
public void addressValidRejectsWrongPrefix() {
byte[] address = new byte[DecodeUtil.ADDRESS_SIZE / 2];
address[0] = (byte) (Constant.ADD_PRE_FIX_BYTE_MAINNET + 1);
Assert.assertFalse(DecodeUtil.addressValid(address));
}

@Test
public void addressValidAcceptsCanonicalAddress() {
byte[] address = new byte[DecodeUtil.ADDRESS_SIZE / 2];
address[0] = Constant.ADD_PRE_FIX_BYTE_MAINNET;
Assert.assertTrue(DecodeUtil.addressValid(address));
}
}
Loading