This is an automated email from the ASF dual-hosted git repository.
terrymanu pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/shardingsphere.git
The following commit(s) were added to refs/heads/master by this push:
new fb7512753fc Reuse native JaCoCo coverage checks for gen-ut (#40222)
fb7512753fc is described below
commit fb7512753fc66c57225e4e9e58391d6234c549de
Author: Liang Zhang <[email protected]>
AuthorDate: Sun Oct 18 21:44:20 2026 +0800
Reuse native JaCoCo coverage checks for gen-ut (#40222)
---
.codex/harness/agents/cases.toml | 4 +-
.codex/skills/gen-ut/SKILL.md | 4 +-
.codex/skills/gen-ut/references/verification.md | 54 ++++---
.../gen-ut/scripts/collect_quality_baseline.py | 159 ---------------------
.../gen-ut/tests/test_collect_quality_baseline.py | 105 --------------
pom.xml | 4 +-
6 files changed, 42 insertions(+), 288 deletions(-)
diff --git a/.codex/harness/agents/cases.toml b/.codex/harness/agents/cases.toml
index 5759c54c5ca..dc27f98be5d 100644
--- a/.codex/harness/agents/cases.toml
+++ b/.codex/harness/agents/cases.toml
@@ -3527,7 +3527,7 @@ required_summary_prefix = "[CLASS]=met; [LINE]=met;
[BRANCH]=unmet; [INNER]=unme
max_summary_chars = 76
critical = true
policy_binding_profile = "gen-ut"
-policy_assertions = [{source = "gen-ut", statements = ["`R10-A`: scope is
clean; focused tests execute at least one test for the effective candidate;
declared target coverage is met for every target and inner class; repository
formatting/checkstyle gates pass; the full mechanical scan has no blocking
violations under `R11`; semantic reviews and `R8` decisions are complete.", "By
default, cover the requested behavior and every affected SUT-owned branch.",
"Apply a numeric CLASS, LINE, or [...]
+policy_assertions = [{source = "gen-ut", statements = ["`R10-A`: scope is
clean; focused tests execute at least one test for the effective candidate;
declared target coverage is met for every target and inner class; repository
formatting/checkstyle gates pass; the full mechanical scan has no blocking
violations under `R11`; semantic reviews and `R8` decisions are complete.", "By
default, cover the requested behavior and every affected SUT-owned branch.",
"Apply a numeric CLASS, LINE, or [...]
[[cases]]
id = "gen_ut_validates_coverage_data_and_all_metric_contract"
@@ -3548,7 +3548,7 @@ required_summary_prefix = "[MISSING_TARGET]=block;
[MISSING_LINE]=block; [BRANCH
max_summary_chars = 105
critical = true
policy_binding_profile = "gen-ut"
-policy_assertions = [{source = "gen-ut", statements = ["`R10-A`: scope is
clean; focused tests execute at least one test for the effective candidate;
declared target coverage is met for every target and inner class; repository
formatting/checkstyle gates pass; the full mechanical scan has no blocking
violations under `R11`; semantic reviews and `R8` decisions are complete.", "By
default, cover the requested behavior and every affected SUT-owned branch.",
"Apply a numeric CLASS, LINE, or [...]
+policy_assertions = [{source = "gen-ut", statements = ["`R10-A`: scope is
clean; focused tests execute at least one test for the effective candidate;
declared target coverage is met for every target and inner class; repository
formatting/checkstyle gates pass; the full mechanical scan has no blocking
violations under `R11`; semantic reviews and `R8` decisions are complete.", "By
default, cover the requested behavior and every affected SUT-owned branch.",
"Apply a numeric CLASS, LINE, or [...]
[[cases]]
id = "gen_ut_uses_check_before_scoped_apply"
diff --git a/.codex/skills/gen-ut/SKILL.md b/.codex/skills/gen-ut/SKILL.md
index aa7524d2fe5..ce2afee0135 100644
--- a/.codex/skills/gen-ut/SKILL.md
+++ b/.codex/skills/gen-ut/SKILL.md
@@ -161,7 +161,7 @@ Do not use boolean `assertEquals`, boolean literals inside
Hamcrest `is`, or con
1. Complete the `R1` source loading, then read this Skill's [verification
commands](references/verification.md) through EOF.
2. Resolve targets, related tests, editable files, modules, and input-blocked
state.
3. Create a task-specific temporary directory and capture `Task scope
baseline` for the resolved file set.
-4. Run focused baseline coverage through the repository command wrapper, then
use `collect_quality_baseline.py` to report coverage and mechanical risks.
+4. Run focused baseline coverage and the full scanner through the repository
command wrapper, retaining native JaCoCo reports and scanner diagnostics under
the verification commands.
5. Decide `R12`; otherwise record the SUT/collaborator ownership boundary and
`R4` branch map.
6. Review scanner candidates against every `R8` condition and record
candidate-level decisions.
7. Check `R9`, then implement the smallest tests allowed by `R2-R7`.
@@ -178,7 +178,7 @@ Final coverage must include executed tests for the
effective candidate; use the
Include:
- `R10=<state>`;
-- aggregated CLASS/LINE/BRANCH counters and ratios for every target plus inner
classes;
+- native JaCoCo report paths and raw CLASS/LINE/BRANCH counters for every
target and inner class, with per-class ratios for each requested numeric metric;
- `R8-CANDIDATES` with candidate decisions and evidence;
- branch/ownership mapping, or the `R12` exemption;
- executed commands, exit codes, and bounded log paths/summaries;
diff --git a/.codex/skills/gen-ut/references/verification.md
b/.codex/skills/gen-ut/references/verification.md
index 16342ee1594..5d300e9b4ea 100644
--- a/.codex/skills/gen-ut/references/verification.md
+++ b/.codex/skills/gen-ut/references/verification.md
@@ -38,28 +38,35 @@ It still rejects tracked files in those directories and
other ignored files outs
## Baseline
-Run focused tests with a task-specific JaCoCo data file:
+Run focused tests separately for each resolved test module so one module
cannot overwrite another module's execution data.
+Create a module-specific task directory before running Maven through the
command wrapper:
```bash
-./mvnw -pl <ResolvedTestModules> -DskipITs -Dspotless.skip=true \
+module_dir="$task_dir/<ResolvedTestModule>"
+mkdir -p "$module_dir"
+```
+
+Use one task-specific JaCoCo data file for both the test agent and report:
+
+```bash
+./mvnw -pl <ResolvedTestModule> -DskipITs -Dspotless.skip=true \
-Dtest=<ResolvedTestClass> -Dsurefire.failIfNoSpecifiedTests=false \
-Djacoco.skip=false -Djacoco.append=false \
- -Djacoco.destFile="$task_dir/baseline.exec" test jacoco:report \
- -Djacoco.dataFile="$task_dir/baseline.exec"
+ -Djacoco.destFile="$module_dir/baseline.exec" test jacoco:report \
+ -Djacoco.dataFile="$module_dir/baseline.exec"
```
-Then print the baseline without executing another shell command:
+Retain each module's baseline XML report and command log in its task directory
before final reports overwrite the report path.
+Run the full scanner and retain its JSON output, exit code, diagnostics,
parameterization candidates, and semantic review inputs:
```bash
-python3 .codex/skills/gen-ut/scripts/collect_quality_baseline.py \
- --jacoco-xml-path <JacocoXmlPath> \
- --target-classes <ResolvedTargetClasses> \
+python3 .codex/skills/gen-ut/scripts/scan_quality_rules.py --json \
--scope-baseline "$task_dir/scope-baseline.json" \
<ResolvedTestFileSet>
```
-`coverageStatus=missing` means required target, CLASS, or LINE evidence does
not exist; it never means 100%.
-For a present target with no BRANCH counter, report `covered=0 missed=0
ratio=100%` because JaCoCo omits zero-total branch counters.
+Apply `R11` to baseline diagnostics rather than treating every nonzero scanner
exit as a new blocking failure.
+Read coverage counters and missed branch locations from the native JaCoCo XML
and HTML reports.
## Edit-loop checks
@@ -80,31 +87,40 @@ Run focused tests after each coherent edit:
Then run the mechanical rules and scope guard:
```bash
-python3 .codex/skills/gen-ut/scripts/scan_quality_rules.py \
+python3 .codex/skills/gen-ut/scripts/scan_quality_rules.py --json \
--scope-baseline "$task_dir/scope-baseline.json" \
<ResolvedTestFileSet>
```
The scanner labels semantic or partly semantic rules as
`semanticReviewRequired=true`. Resolve them through source inspection and
record evidence; zero mechanical violations alone is not `R10-A`.
+In JSON output, review the `manual` and `hybrid` rules and their `reviews`
entries.
## Final coverage
Use current focused coverage evidence for the effective candidate.
-When that evidence is missing or invalid, use the module's configured JaCoCo
check when present; otherwise run `test jacoco:report` and collect target
ratios with the bundled reporter:
+When that evidence is missing or invalid, repeat the baseline coverage command
for each module with `final.exec` instead of `baseline.exec`.
+Confirm that the data file is nonempty and was produced by the current
accepted test execution, and that the agent, report, and check all use that
same file.
+For an explicit numeric coverage target, run the existing `coverage-check`
profile against that data:
```bash
-python3 .codex/skills/gen-ut/scripts/collect_quality_baseline.py \
- --jacoco-xml-path <JacocoXmlPath> \
- --target-classes <ResolvedTargetClasses> \
- --scope-baseline "$task_dir/scope-baseline.json" \
- <ResolvedTestFileSet>
+./mvnw -pl <ResolvedTestModule> -Pcoverage-check \
+ -Djacoco.skip=false -Djacoco.dataFile="$module_dir/final.exec" \
+ '-Djacoco.check.class.pattern=org.example.Target:org.example.Target.*' \
+ -Djacoco.check.counter=<CLASS|LINE|BRANCH> \
+ -Djacoco.minimum.coverage=<TargetRatio> jacoco:check@jacoco-check
```
+Replace the example filter with the exact resolved target families using
JaCoCo's dot-separated class names, including dots for inner-class separators,
and separate families with `:`.
+Do not use a target-name prefix wildcard that also matches sibling classes.
+Set `<TargetRatio>` between zero and one and use the accepted current data
path if coverage evidence was reused instead of regenerated as `final.exec`.
+Inspect the check log for actual execution; a skipped check or unmatched class
filter is not a pass.
+Verify every resolved target and compiled inner class in the current XML
report and retain their raw per-class counters; aggregate-only evidence is
insufficient.
For an explicit numeric CLASS, LINE, or BRANCH target, compare only that
metric against the requested threshold using the raw JaCoCo counters for every
resolved target and inner class.
Use covered and missed counts rather than rounded displayed percentages; a
target's aggregate must not compensate for an unmet class, inner class, or
another target.
-Add `--minimum-ratio <TargetRatioPercent>` only when the user requests the
same threshold for all three metrics; retain the reporter's existing aggregate
checks, which do not replace per-class acceptance.
+When several numeric metrics are requested, run the configured check
separately for each requested metric and threshold against the same valid
execution data.
Without an explicit numeric target, decide completion from the
requested-behavior and affected-SUT-owned-branch map while still reporting the
available counters.
-A missing target or mandatory CLASS or LINE counter blocks completion even
without `--minimum-ratio`; the reporter's zero exit code alone is not coverage
proof.
+Do not run the profile's default 95% check when no numeric target was
requested.
+A missing target or mandatory CLASS or LINE counter blocks completion even
without a numeric target; a zero Maven exit code alone is not coverage proof.
A valid branchless class satisfies BRANCH coverage with zero missed branches,
including a zero/zero counter.
## Repository completion gates
diff --git a/.codex/skills/gen-ut/scripts/collect_quality_baseline.py
b/.codex/skills/gen-ut/scripts/collect_quality_baseline.py
deleted file mode 100644
index 405347a5a38..00000000000
--- a/.codex/skills/gen-ut/scripts/collect_quality_baseline.py
+++ /dev/null
@@ -1,159 +0,0 @@
-#
-# 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.
-#
-
-#!/usr/bin/env python3
-"""
-Collect a baseline quality summary for gen-ut before editing begins.
-"""
-
-import argparse
-import sys
-import xml.etree.ElementTree as ET
-from pathlib import Path
-
-
-SCRIPT_DIR = Path(__file__).resolve().parent
-if str(SCRIPT_DIR) not in sys.path:
- sys.path.insert(0, str(SCRIPT_DIR))
-
-import scan_quality_rules as quality_rules
-
-
-def parse_target_classes(raw: str) -> list[str]:
- result = [each.strip() for each in raw.split(",") if each.strip()]
- if result:
- return result
- raise ValueError("target-classes must not be empty")
-
-
-def find_sourcefile_node(root: ET.Element, fqcn: str) -> ET.Element | None:
- package_name, _, simple_name = fqcn.rpartition(".")
- package_path = package_name.replace(".", "/")
- source_name = f"{simple_name}.java"
- for package in root.findall("package"):
- if package.get("name") != package_path:
- continue
- for sourcefile in package.findall("sourcefile"):
- if sourcefile.get("name") == source_name:
- return sourcefile
- return None
-
-
-def summarize_target_coverage(root: ET.Element, fqcn: str) -> tuple[bool,
dict[str, tuple[int, int, float] | None], list[int]]:
- class_name = fqcn.replace(".", "/")
- matched_nodes = [each for each in root.iter("class") if each.get("name")
== class_name or each.get("name", "").startswith(class_name + "$")]
- counters: dict[str, tuple[int, int, float] | None] = {}
- for counter_type in ("CLASS", "LINE", "BRANCH"):
- covered = 0
- missed = 0
- found_counter = False
- for each in matched_nodes:
- counter = next((item for item in each.findall("counter") if
item.get("type") == counter_type), None)
- if counter is None:
- continue
- found_counter = True
- covered += int(counter.get("covered"))
- missed += int(counter.get("missed"))
- total = covered + missed
- if found_counter:
- counters[counter_type] = (covered, missed, 100.0 if 0 == total
else covered * 100.0 / total)
- else:
- counters[counter_type] = (0, 0, 100.0) if matched_nodes and
"BRANCH" == counter_type else None
- sourcefile = find_sourcefile_node(root, fqcn)
- missed_branch_lines = []
- if sourcefile is not None:
- missed_branch_lines = [int(each.get("nr")) for each in
sourcefile.findall("line") if int(each.get("mb", "0")) > 0]
- return bool(matched_nodes), counters, missed_branch_lines
-
-
-def meets_target(found: bool, counters: dict[str, tuple[int, int, float] |
None], minimum_ratio: float) -> bool:
- return found and all(counters[each] is not None and counters[each][2] +
1e-9 >= minimum_ratio for each in ("CLASS", "LINE", "BRANCH"))
-
-
-def print_rule_baseline(scan_result: dict) -> None:
- print(f"[baseline] javaFiles={scan_result['java_file_count']}")
- if scan_result["candidates"]:
- print("[R8-CANDIDATES]")
- for each in scan_result["candidates"]:
- print(quality_rules.describe_candidate(each))
- else:
- print("[R8-CANDIDATES] no candidates")
- quality_rules.print_rule_summary(scan_result)
- quality_rules.print_precheck_summary(scan_result)
-
-
-def print_coverage_baseline(jacoco_xml_path: Path, target_classes: list[str],
minimum_ratio: float | None) -> bool:
- root = ET.parse(jacoco_xml_path).getroot()
- result = True
- for fqcn in target_classes:
- found, counters, missed_branch_lines = summarize_target_coverage(root,
fqcn)
- if not found:
- print(f"[baseline] {fqcn} coverageStatus=missing")
- result = False
- continue
- for counter_type in ("CLASS", "LINE", "BRANCH"):
- counter = counters[counter_type]
- if counter is None:
- print(f"[baseline] {fqcn} (+inner) {counter_type}
coverageStatus=missing")
- result = False
- continue
- covered, missed, ratio = counter
- print(f"[baseline] {fqcn} (+inner) {counter_type}
covered={covered} missed={missed} ratio={ratio:.2f}%")
- if missed_branch_lines:
- line_text = ",".join(str(each) for each in missed_branch_lines)
- print(f"[baseline] {fqcn} branchMissLines={line_text}")
- else:
- print(f"[baseline] {fqcn} branchMissLines=none")
- if minimum_ratio is not None and not meets_target(found, counters,
minimum_ratio):
- result = False
- return result
-
-
-def main() -> int:
- parser = argparse.ArgumentParser(description="Collect baseline coverage
and quality-rule diagnostics for gen-ut.")
- parser.add_argument("--jacoco-xml-path", required=True, help="Path to the
jacoco.xml generated by the coverage command.")
- parser.add_argument("--target-classes", required=True,
help="Comma-separated target production classes.")
- parser.add_argument("--scope-baseline", help="Structured task scope
baseline created by scan_quality_rules.py.")
- parser.add_argument("--minimum-ratio", type=float, help="Fail unless every
target CLASS, LINE, and BRANCH ratio meets this percentage.")
- parser.add_argument("paths", nargs="+", help="Resolved test file set.")
- args = parser.parse_args()
-
- try:
- target_classes = parse_target_classes(args.target_classes)
- if args.minimum_ratio is not None and not 0.0 <= args.minimum_ratio <=
100.0:
- raise ValueError("minimum-ratio must be between 0 and 100")
- except ValueError as ex:
- parser.error(str(ex))
- paths = [Path(each) for each in args.paths]
- baseline_path = Path(args.scope_baseline) if args.scope_baseline else None
- scan_result = quality_rules.collect_scan_result(paths, baseline_path)
- rules_ok = not quality_rules.failed_rule_names(scan_result)
- print_rule_baseline(scan_result)
- jacoco_xml_path = Path(args.jacoco_xml_path)
- if not jacoco_xml_path.exists():
- print(f"[baseline] missingJacocoXml={jacoco_xml_path}",
file=sys.stderr)
- return 2
- try:
- coverage_ok = print_coverage_baseline(jacoco_xml_path, target_classes,
args.minimum_ratio)
- except (OSError, ET.ParseError) as ex:
- print(f"[baseline] invalidJacocoXml={jacoco_xml_path}: {ex}",
file=sys.stderr)
- return 2
- return 0 if args.minimum_ratio is None or coverage_ok and rules_ok else 1
-
-
-if __name__ == "__main__":
- sys.exit(main())
diff --git a/.codex/skills/gen-ut/tests/test_collect_quality_baseline.py
b/.codex/skills/gen-ut/tests/test_collect_quality_baseline.py
deleted file mode 100644
index 5b887d57606..00000000000
--- a/.codex/skills/gen-ut/tests/test_collect_quality_baseline.py
+++ /dev/null
@@ -1,105 +0,0 @@
-#
-# 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.
-#
-
-import sys
-import unittest
-import xml.etree.ElementTree as ET
-from pathlib import Path
-
-
-SCRIPTS_DIR = Path(__file__).resolve().parents[1] / "scripts"
-if str(SCRIPTS_DIR) not in sys.path:
- sys.path.insert(0, str(SCRIPTS_DIR))
-
-import collect_quality_baseline as baseline
-
-
-class CollectQualityBaselineTest(unittest.TestCase):
-
- def test_missing_target_class_is_not_covered(self):
- root = ET.fromstring("<report />")
- found, counters, missed_branch_lines =
baseline.summarize_target_coverage(root, "sample.MissingClass")
- self.assertFalse(found)
- self.assertEqual({"CLASS": None, "LINE": None, "BRANCH": None},
counters)
- self.assertEqual([], missed_branch_lines)
-
- def test_aggregate_target_and_inner_class_coverage(self):
- root = ET.fromstring("""
- <report>
- <package name="sample">
- <class name="sample/Target" sourcefilename="Target.java">
- <counter type="CLASS" missed="0" covered="1" />
- <counter type="LINE" missed="1" covered="4" />
- <counter type="BRANCH" missed="1" covered="3" />
- </class>
- <class name="sample/Target$Inner" sourcefilename="Target.java">
- <counter type="CLASS" missed="0" covered="1" />
- <counter type="LINE" missed="0" covered="2" />
- <counter type="BRANCH" missed="0" covered="2" />
- </class>
- <sourcefile name="Target.java">
- <line nr="12" mb="1" cb="1" />
- </sourcefile>
- </package>
- </report>
- """)
- found, counters, missed_branch_lines =
baseline.summarize_target_coverage(root, "sample.Target")
- self.assertTrue(found)
- self.assertEqual((2, 0, 100.0), counters["CLASS"])
- self.assertEqual((6, 1, 6 * 100.0 / 7), counters["LINE"])
- self.assertEqual((5, 1, 5 * 100.0 / 6), counters["BRANCH"])
- self.assertEqual([12], missed_branch_lines)
-
- def test_branchless_target_satisfies_target(self):
- root = ET.fromstring("""
- <report>
- <package name="sample">
- <class name="sample/Target" sourcefilename="Target.java">
- <counter type="CLASS" missed="0" covered="1" />
- <counter type="LINE" missed="0" covered="1" />
- </class>
- <sourcefile name="Target.java">
- <line nr="1" mb="0" cb="0" />
- </sourcefile>
- </package>
- </report>
- """)
- found, counters, missed_branch_lines =
baseline.summarize_target_coverage(root, "sample.Target")
- self.assertTrue(found)
- self.assertEqual((0, 0, 100.0), counters["BRANCH"])
- self.assertEqual([], missed_branch_lines)
- self.assertTrue(baseline.meets_target(found, counters, 100.0))
-
- def test_missing_line_counter_does_not_satisfy_target(self):
- root = ET.fromstring("""
- <report>
- <package name="sample">
- <class name="sample/Target" sourcefilename="Target.java">
- <counter type="CLASS" missed="0" covered="1" />
- </class>
- </package>
- </report>
- """)
- found, counters, _ = baseline.summarize_target_coverage(root,
"sample.Target")
- self.assertTrue(found)
- self.assertIsNone(counters["LINE"])
- self.assertEqual((0, 0, 100.0), counters["BRANCH"])
- self.assertFalse(baseline.meets_target(found, counters, 100.0))
-
-
-if __name__ == "__main__":
- unittest.main()
diff --git a/pom.xml b/pom.xml
index bb9fd2466e8..eb63611846b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1511,6 +1511,7 @@
<id>coverage-check</id>
<properties>
<jacoco.check.class.pattern>**/*</jacoco.check.class.pattern>
+ <jacoco.check.counter>INSTRUCTION</jacoco.check.counter>
<jacoco.minimum.coverage>0.95</jacoco.minimum.coverage>
</properties>
<build>
@@ -1541,6 +1542,7 @@
<goal>check</goal>
</goals>
<configuration>
+ <dataFile>${jacoco.dataFile}</dataFile>
<rules>
<rule>
<element>CLASS</element>
@@ -1549,7 +1551,7 @@
</includes>
<limits>
<limit>
-
<counter>INSTRUCTION</counter>
+
<counter>${jacoco.check.counter}</counter>
<value>COVEREDRATIO</value>
<minimum>${jacoco.minimum.coverage}</minimum>
</limit>