From 10408c3569bc7f33cb30595794a2ddbbdc4da315 Mon Sep 17 00:00:00 2001 From: contrueCT Date: Mon, 24 Aug 2026 17:26:45 +0800 Subject: [PATCH 1/4] fix(ci): modernize Codecov uploads Fixes #3168 --- .github/workflows/commons-ci.yml | 7 +- .github/workflows/pd-store-ci.yml | 20 ++- .github/workflows/server-ci.yml | 7 +- .../travis/test-codecov-upload-config.sh | 129 ++++++++++++++++++ 4 files changed, 154 insertions(+), 9 deletions(-) create mode 100755 hugegraph-server/hugegraph-dist/src/assembly/travis/test-codecov-upload-config.sh diff --git a/.github/workflows/commons-ci.yml b/.github/workflows/commons-ci.yml index 5311ebeee0..a03aaf09ff 100644 --- a/.github/workflows/commons-ci.yml +++ b/.github/workflows/commons-ci.yml @@ -58,6 +58,9 @@ jobs: mvn test -pl hugegraph-commons/hugegraph-rpc -Dtest=UnitTestSuite -DskipCommonsTests=false - name: Upload coverage to Codecov - uses: codecov/codecov-action@v3.0.0 + uses: codecov/codecov-action@v5 with: - file: target/jacoco.xml + token: ${{ secrets.CODECOV_TOKEN }} + files: hugegraph-commons/target/jacoco.xml + disable_search: true + fail_ci_if_error: false diff --git a/.github/workflows/pd-store-ci.yml b/.github/workflows/pd-store-ci.yml index 2ad45bd648..d20f4ccb41 100644 --- a/.github/workflows/pd-store-ci.yml +++ b/.github/workflows/pd-store-ci.yml @@ -36,6 +36,9 @@ jobs: - name: Run JaCoCo report validator tests run: hugegraph-server/hugegraph-dist/src/assembly/travis/test-check-jacoco-report.sh + - name: Run Codecov upload configuration tests + run: hugegraph-server/hugegraph-dist/src/assembly/travis/test-codecov-upload-config.sh + - name: Use staged maven repo settings run: | cp $HOME/.m2/settings.xml /tmp/settings.xml || true @@ -184,9 +187,12 @@ jobs: hg-pd-grpc hg-pd-common hg-pd-client hg-pd-core hg-pd-service hg-pd-dist - name: Upload coverage to Codecov - uses: codecov/codecov-action@v3.0.0 + uses: codecov/codecov-action@v5 with: + token: ${{ secrets.CODECOV_TOKEN }} files: ${{ env.REPORT_FILE }} + disable_search: true + fail_ci_if_error: false store: needs: struct @@ -316,9 +322,12 @@ jobs: hg-store-grpc hg-store-common hg-store-client hg-store-rocksdb - name: Upload coverage to Codecov - uses: codecov/codecov-action@v3.0.0 + uses: codecov/codecov-action@v5 with: + token: ${{ secrets.CODECOV_TOKEN }} files: ${{ env.REPORT_FILE }} + disable_search: true + fail_ci_if_error: false hstore: needs: struct @@ -387,6 +396,9 @@ jobs: $TRAVIS_DIR/run-tinkerpop-test.sh $BACKEND tinkerpop - name: Upload coverage to Codecov - uses: codecov/codecov-action@v3.0.0 + uses: codecov/codecov-action@v5 with: - file: ${{ env.REPORT_DIR }}/*.xml + token: ${{ secrets.CODECOV_TOKEN }} + files: ${{ env.REPORT_DIR }}/*.xml + disable_search: true + fail_ci_if_error: false diff --git a/.github/workflows/server-ci.yml b/.github/workflows/server-ci.yml index 9c4e577d85..3c631fd38e 100644 --- a/.github/workflows/server-ci.yml +++ b/.github/workflows/server-ci.yml @@ -216,11 +216,12 @@ jobs: $TRAVIS_DIR/run-tinkerpop-test.sh $BACKEND tinkerpop - name: Upload coverage to Codecov - # TODO: update to v5 later - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v5 with: token: ${{ secrets.CODECOV_TOKEN }} - file: ${{ env.REPORT_DIR }}/*.xml + files: ${{ env.REPORT_DIR }}/*.xml + disable_search: true + fail_ci_if_error: false build-server-macos-rocksdb: runs-on: ${{ matrix.os }} diff --git a/hugegraph-server/hugegraph-dist/src/assembly/travis/test-codecov-upload-config.sh b/hugegraph-server/hugegraph-dist/src/assembly/travis/test-codecov-upload-config.sh new file mode 100755 index 0000000000..19e1bcff59 --- /dev/null +++ b/hugegraph-server/hugegraph-dist/src/assembly/travis/test-codecov-upload-config.sh @@ -0,0 +1,129 @@ +#!/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) +REPO_ROOT=$(cd "${SCRIPT_DIR}/../../../../.." && pwd) + +python3 - "${REPO_ROOT}" <<'PY' +import pathlib +import re +import sys + +repo_root = pathlib.Path(sys.argv[1]) +expected_uploads = { + ".github/workflows/commons-ci.yml": 1, + ".github/workflows/pd-store-ci.yml": 3, + ".github/workflows/server-ci.yml": 1, +} +action_pattern = re.compile( + r"^(?P\s*)(?P-\s+)?uses:\s*" + r"codecov/codecov-action@(?P\S+)\s*$" +) +version_pattern = re.compile(r"^v(?P\d+)(?:[.-].*)?$") +errors = [] +workflow_dir = repo_root / ".github/workflows" +workflow_paths = sorted( + set(workflow_dir.glob("*.yml")) | set(workflow_dir.glob("*.yaml")) +) +checked_expected_workflows = set() + + +def indentation(line): + return len(line) - len(line.lstrip()) + + +for workflow_path in workflow_paths: + relative_path = str(workflow_path.relative_to(repo_root)) + lines = workflow_path.read_text(encoding="utf-8").splitlines() + uploads = [] + + for line_number, line in enumerate(lines, start=1): + match = action_pattern.match(line) + if match is None: + continue + + uses_indent = len(match.group("indent")) + if match.group("dash") is not None: + uses_indent += len(match.group("dash")) + step_indent = uses_indent - 2 + block = [] + for candidate in lines[line_number:]: + if candidate.strip() and indentation(candidate) <= step_indent: + break + block.append(candidate) + uploads.append((line_number, match.group("version"), block)) + + expected_count = expected_uploads.get(relative_path) + if expected_count is not None and len(uploads) != expected_count: + errors.append( + f"{relative_path}: expected {expected_count} Codecov uploads, " + f"found {len(uploads)}" + ) + if expected_count is not None: + checked_expected_workflows.add(relative_path) + + for line_number, version, block in uploads: + version_match = version_pattern.match(version) + if version_match is None or int(version_match.group("major")) < 5: + errors.append( + f"{relative_path}:{line_number}: Codecov action {version} " + "uses the legacy uploader" + ) + + inputs = {} + in_with_block = False + for candidate in block: + candidate_indent = indentation(candidate) + if candidate_indent == uses_indent and candidate.strip() == "with:": + in_with_block = True + continue + if (in_with_block and candidate.strip() and + candidate_indent <= uses_indent): + break + if not in_with_block or candidate_indent != uses_indent + 2: + continue + candidate_match = re.match( + r"^\s*(?P[a-zA-Z_]+):\s*(?P.*?)\s*$", + candidate, + ) + if candidate_match is not None: + inputs[candidate_match.group("key")] = candidate_match.group("value") + + if not inputs.get("files"): + errors.append( + f"{relative_path}:{line_number}: Codecov upload must use " + "the files input" + ) + if inputs.get("token") != "${{ secrets.CODECOV_TOKEN }}": + errors.append( + f"{relative_path}:{line_number}: Codecov upload must pass " + "secrets.CODECOV_TOKEN for trusted runs" + ) + +for relative_path in expected_uploads.keys() - checked_expected_workflows: + errors.append(f"{relative_path}: expected workflow file is missing") + +if errors: + for error in errors: + print(f"ERROR: {error}", file=sys.stderr) + sys.exit(1) + +print("PASS: Codecov upload configuration contract") +PY From e77c70198d6503cec8491cf1f12713649b01fdce Mon Sep 17 00:00:00 2001 From: contrueCT Date: Mon, 24 Aug 2026 18:53:38 +0800 Subject: [PATCH 2/4] fix(ci): preserve Codecov upload indentation --- .../travis/test-codecov-upload-config.sh | 101 +++++++++++++----- 1 file changed, 77 insertions(+), 24 deletions(-) diff --git a/hugegraph-server/hugegraph-dist/src/assembly/travis/test-codecov-upload-config.sh b/hugegraph-server/hugegraph-dist/src/assembly/travis/test-codecov-upload-config.sh index 19e1bcff59..d0cd556828 100755 --- a/hugegraph-server/hugegraph-dist/src/assembly/travis/test-codecov-upload-config.sh +++ b/hugegraph-server/hugegraph-dist/src/assembly/travis/test-codecov-upload-config.sh @@ -49,9 +49,7 @@ def indentation(line): return len(line) - len(line.lstrip()) -for workflow_path in workflow_paths: - relative_path = str(workflow_path.relative_to(repo_root)) - lines = workflow_path.read_text(encoding="utf-8").splitlines() +def find_uploads(lines): uploads = [] for line_number, line in enumerate(lines, start=1): @@ -68,7 +66,81 @@ for workflow_path in workflow_paths: if candidate.strip() and indentation(candidate) <= step_indent: break block.append(candidate) - uploads.append((line_number, match.group("version"), block)) + uploads.append( + (line_number, match.group("version"), block, uses_indent) + ) + + return uploads + + +def read_inputs(block, uses_indent): + inputs = {} + in_with_block = False + for candidate in block: + candidate_indent = indentation(candidate) + if candidate_indent == uses_indent and candidate.strip() == "with:": + in_with_block = True + continue + if (in_with_block and candidate.strip() and + candidate_indent <= uses_indent): + break + if not in_with_block or candidate_indent != uses_indent + 2: + continue + candidate_match = re.match( + r"^\s*(?P[a-zA-Z_]+):\s*(?P.*?)\s*$", + candidate, + ) + if candidate_match is not None: + inputs[candidate_match.group("key")] = candidate_match.group("value") + return inputs + + +def parse_uploads(lines): + return [ + (line_number, version, read_inputs(block, uses_indent)) + for line_number, version, block, uses_indent in find_uploads(lines) + ] + + +def check_mixed_upload_indentation(): + lines = [ + "jobs:", + " first:", + " steps:", + " - uses: codecov/codecov-action@v5", + " with:", + " token: ${{ secrets.CODECOV_TOKEN }}", + " files: first.xml", + " second:", + " steps:", + " - uses: codecov/codecov-action@v5", + " with:", + " token: ${{ secrets.CODECOV_TOKEN }}", + " files: second.xml", + ] + expected_inputs = [ + { + "token": "${{ secrets.CODECOV_TOKEN }}", + "files": "first.xml", + }, + { + "token": "${{ secrets.CODECOV_TOKEN }}", + "files": "second.xml", + }, + ] + actual_inputs = [inputs for _, _, inputs in parse_uploads(lines)] + if actual_inputs != expected_inputs: + return ["Codecov uploads with mixed indentation were parsed incorrectly"] + return [] + + +errors.extend(check_mixed_upload_indentation()) + + +for workflow_path in workflow_paths: + relative_path = str(workflow_path.relative_to(repo_root)) + lines = workflow_path.read_text(encoding="utf-8").splitlines() + uploads = parse_uploads(lines) expected_count = expected_uploads.get(relative_path) if expected_count is not None and len(uploads) != expected_count: @@ -79,7 +151,7 @@ for workflow_path in workflow_paths: if expected_count is not None: checked_expected_workflows.add(relative_path) - for line_number, version, block in uploads: + for line_number, version, inputs in uploads: version_match = version_pattern.match(version) if version_match is None or int(version_match.group("major")) < 5: errors.append( @@ -87,25 +159,6 @@ for workflow_path in workflow_paths: "uses the legacy uploader" ) - inputs = {} - in_with_block = False - for candidate in block: - candidate_indent = indentation(candidate) - if candidate_indent == uses_indent and candidate.strip() == "with:": - in_with_block = True - continue - if (in_with_block and candidate.strip() and - candidate_indent <= uses_indent): - break - if not in_with_block or candidate_indent != uses_indent + 2: - continue - candidate_match = re.match( - r"^\s*(?P[a-zA-Z_]+):\s*(?P.*?)\s*$", - candidate, - ) - if candidate_match is not None: - inputs[candidate_match.group("key")] = candidate_match.group("value") - if not inputs.get("files"): errors.append( f"{relative_path}:{line_number}: Codecov upload must use " From 325948db6d46902752428aa9512f2d1396e5d184 Mon Sep 17 00:00:00 2001 From: imbajin Date: Tue, 25 Aug 2026 01:29:52 +0800 Subject: [PATCH 3/4] fix(ci): pin Codecov report selection --- .../travis/test-codecov-upload-config.sh | 43 +++++++++++++++++-- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/hugegraph-server/hugegraph-dist/src/assembly/travis/test-codecov-upload-config.sh b/hugegraph-server/hugegraph-dist/src/assembly/travis/test-codecov-upload-config.sh index d0cd556828..e4561e6b20 100755 --- a/hugegraph-server/hugegraph-dist/src/assembly/travis/test-codecov-upload-config.sh +++ b/hugegraph-server/hugegraph-dist/src/assembly/travis/test-codecov-upload-config.sh @@ -32,6 +32,19 @@ expected_uploads = { ".github/workflows/pd-store-ci.yml": 3, ".github/workflows/server-ci.yml": 1, } +expected_files = { + ".github/workflows/commons-ci.yml": [ + "hugegraph-commons/target/jacoco.xml", + ], + ".github/workflows/pd-store-ci.yml": [ + "${{ env.REPORT_FILE }}", + "${{ env.REPORT_FILE }}", + "${{ env.REPORT_DIR }}/*.xml", + ], + ".github/workflows/server-ci.yml": [ + "${{ env.REPORT_DIR }}/*.xml", + ], +} action_pattern = re.compile( r"^(?P\s*)(?P-\s+)?uses:\s*" r"codecov/codecov-action@(?P\S+)\s*$" @@ -150,8 +163,14 @@ for workflow_path in workflow_paths: ) if expected_count is not None: checked_expected_workflows.add(relative_path) + expected_workflow_files = expected_files.get(relative_path) + if uploads and expected_workflow_files is None: + errors.append( + f"{relative_path}: unexpected Codecov upload workflow" + ) + expected_workflow_files = [] - for line_number, version, inputs in uploads: + for upload_index, (line_number, version, inputs) in enumerate(uploads): version_match = version_pattern.match(version) if version_match is None or int(version_match.group("major")) < 5: errors.append( @@ -159,16 +178,32 @@ for workflow_path in workflow_paths: "uses the legacy uploader" ) - if not inputs.get("files"): + if upload_index >= len(expected_workflow_files): + errors.append( + f"{relative_path}:{line_number}: unexpected Codecov upload" + ) + continue + expected_file = expected_workflow_files[upload_index] + if inputs.get("files") != expected_file: errors.append( - f"{relative_path}:{line_number}: Codecov upload must use " - "the files input" + f"{relative_path}:{line_number}: expected files input " + f"{expected_file!r}, found {inputs.get('files')!r}" ) if inputs.get("token") != "${{ secrets.CODECOV_TOKEN }}": errors.append( f"{relative_path}:{line_number}: Codecov upload must pass " "secrets.CODECOV_TOKEN for trusted runs" ) + if inputs.get("disable_search") != "true": + errors.append( + f"{relative_path}:{line_number}: Codecov upload must set " + "disable_search: true" + ) + if inputs.get("fail_ci_if_error") != "false": + errors.append( + f"{relative_path}:{line_number}: Codecov upload must keep " + "fail_ci_if_error: false" + ) for relative_path in expected_uploads.keys() - checked_expected_workflows: errors.append(f"{relative_path}: expected workflow file is missing") From 424cbc92135623938d99ee7a209f9956aff9a386 Mon Sep 17 00:00:00 2001 From: imbajin Date: Tue, 25 Aug 2026 02:01:56 +0800 Subject: [PATCH 4/4] fix(ci): harden Codecov config checks - accept quoted action refs and inline comments - bind report paths to workflow job IDs - allow harmless job declaration reordering --- .../travis/test-codecov-upload-config.sh | 81 +++++++++---------- 1 file changed, 38 insertions(+), 43 deletions(-) diff --git a/hugegraph-server/hugegraph-dist/src/assembly/travis/test-codecov-upload-config.sh b/hugegraph-server/hugegraph-dist/src/assembly/travis/test-codecov-upload-config.sh index e4561e6b20..505b0cb36a 100755 --- a/hugegraph-server/hugegraph-dist/src/assembly/travis/test-codecov-upload-config.sh +++ b/hugegraph-server/hugegraph-dist/src/assembly/travis/test-codecov-upload-config.sh @@ -25,30 +25,29 @@ python3 - "${REPO_ROOT}" <<'PY' import pathlib import re import sys +from collections import Counter repo_root = pathlib.Path(sys.argv[1]) -expected_uploads = { - ".github/workflows/commons-ci.yml": 1, - ".github/workflows/pd-store-ci.yml": 3, - ".github/workflows/server-ci.yml": 1, -} expected_files = { - ".github/workflows/commons-ci.yml": [ - "hugegraph-commons/target/jacoco.xml", - ], - ".github/workflows/pd-store-ci.yml": [ - "${{ env.REPORT_FILE }}", - "${{ env.REPORT_FILE }}", - "${{ env.REPORT_DIR }}/*.xml", - ], - ".github/workflows/server-ci.yml": [ - "${{ env.REPORT_DIR }}/*.xml", - ], + ".github/workflows/commons-ci.yml": { + "build-commons": "hugegraph-commons/target/jacoco.xml", + }, + ".github/workflows/pd-store-ci.yml": { + "pd": "${{ env.REPORT_FILE }}", + "store": "${{ env.REPORT_FILE }}", + "hstore": "${{ env.REPORT_DIR }}/*.xml", + }, + ".github/workflows/server-ci.yml": { + "build-server": "${{ env.REPORT_DIR }}/*.xml", + }, } action_pattern = re.compile( r"^(?P\s*)(?P-\s+)?uses:\s*" - r"codecov/codecov-action@(?P\S+)\s*$" + r"(?:(?P['\"])codecov/codecov-action@" + r"(?P[^'\"\s]+)(?P=quote)\s*(?:#.*)?|" + r"codecov/codecov-action@(?P\S+?)(?:\s+#.*)?\s*)$" ) +job_pattern = re.compile(r"^ (?P[a-zA-Z0-9_-]+):\s*(?:#.*)?$") version_pattern = re.compile(r"^v(?P\d+)(?:[.-].*)?$") errors = [] workflow_dir = repo_root / ".github/workflows" @@ -64,8 +63,12 @@ def indentation(line): def find_uploads(lines): uploads = [] + current_job = None for line_number, line in enumerate(lines, start=1): + job_match = job_pattern.match(line) + if job_match is not None: + current_job = job_match.group("job") match = action_pattern.match(line) if match is None: continue @@ -80,7 +83,8 @@ def find_uploads(lines): break block.append(candidate) uploads.append( - (line_number, match.group("version"), block, uses_indent) + (line_number, current_job, + match.group("quoted") or match.group("plain"), block, uses_indent) ) return uploads @@ -110,11 +114,18 @@ def read_inputs(block, uses_indent): def parse_uploads(lines): return [ - (line_number, version, read_inputs(block, uses_indent)) - for line_number, version, block, uses_indent in find_uploads(lines) + (line_number, job, version, read_inputs(block, uses_indent)) + for line_number, job, version, block, uses_indent in find_uploads(lines) ] +def files_match(uploads, expected): + actual = Counter( + (job, inputs.get("files")) for _, job, _, inputs in uploads + ) + return actual == Counter(expected.items()) + + def check_mixed_upload_indentation(): lines = [ "jobs:", @@ -141,7 +152,7 @@ def check_mixed_upload_indentation(): "files": "second.xml", }, ] - actual_inputs = [inputs for _, _, inputs in parse_uploads(lines)] + actual_inputs = [inputs for _, _, _, inputs in parse_uploads(lines)] if actual_inputs != expected_inputs: return ["Codecov uploads with mixed indentation were parsed incorrectly"] return [] @@ -155,22 +166,17 @@ for workflow_path in workflow_paths: lines = workflow_path.read_text(encoding="utf-8").splitlines() uploads = parse_uploads(lines) - expected_count = expected_uploads.get(relative_path) - if expected_count is not None and len(uploads) != expected_count: - errors.append( - f"{relative_path}: expected {expected_count} Codecov uploads, " - f"found {len(uploads)}" - ) - if expected_count is not None: - checked_expected_workflows.add(relative_path) expected_workflow_files = expected_files.get(relative_path) if uploads and expected_workflow_files is None: errors.append( f"{relative_path}: unexpected Codecov upload workflow" ) - expected_workflow_files = [] + elif expected_workflow_files is not None: + checked_expected_workflows.add(relative_path) + if not files_match(uploads, expected_workflow_files): + errors.append(f"{relative_path}: unexpected Codecov files inputs") - for upload_index, (line_number, version, inputs) in enumerate(uploads): + for line_number, _, version, inputs in uploads: version_match = version_pattern.match(version) if version_match is None or int(version_match.group("major")) < 5: errors.append( @@ -178,17 +184,6 @@ for workflow_path in workflow_paths: "uses the legacy uploader" ) - if upload_index >= len(expected_workflow_files): - errors.append( - f"{relative_path}:{line_number}: unexpected Codecov upload" - ) - continue - expected_file = expected_workflow_files[upload_index] - if inputs.get("files") != expected_file: - errors.append( - f"{relative_path}:{line_number}: expected files input " - f"{expected_file!r}, found {inputs.get('files')!r}" - ) if inputs.get("token") != "${{ secrets.CODECOV_TOKEN }}": errors.append( f"{relative_path}:{line_number}: Codecov upload must pass " @@ -205,7 +200,7 @@ for workflow_path in workflow_paths: "fail_ci_if_error: false" ) -for relative_path in expected_uploads.keys() - checked_expected_workflows: +for relative_path in expected_files.keys() - checked_expected_workflows: errors.append(f"{relative_path}: expected workflow file is missing") if errors: