This is an automated email from the ASF dual-hosted git repository. papegaaij pushed a commit to branch add-code-coverage in repository https://gitbox.apache.org/repos/asf/wicket.git
commit a434345d9e47b913d7b8c34a31450ec8fe8ad256 Author: Emond Papegaaij <[email protected]> AuthorDate: Mon Aug 24 09:09:43 2026 +0200 Add JaCoCo coverage measurement and Codecov reporting The 'coverage' profile produced no data at all. maven-surefire-plugin's pluginManagement set a literal <argLine> for the --add-opens flags, which overrode the argLine property that jacoco:prepare-agent sets, so the agent never attached to the forked test JVMs. Surefire now consumes @{jacoco.argLine}, substituted at fork time. That placeholder property must stay declared even though it is empty: surefire only substitutes @{x} for properties that exist, and would otherwise hand the literal token to the JVM and break every test module whenever the profile is inactive. Per-module reports would have been misleading too, because most tests live in a module other than the code they exercise: wicket-core has no tests of its own, and wicket-core-tests has no production classes. The new wicket-coverage module aggregates the reactor with jacoco:report-aggregate instead, using dependency scope to say what belongs in the report -- compile contributes classes and sources, test contributes execution data only. The per-module 'report' execution is dropped. Coverage is measured on the JDK 21 leg of the existing build and uploaded to Codecov for every push and pull request. It is reported, never enforced: codecov.yml marks both status checks informational, so neither can fail a build or block a merge. check-coverage-report.py guards the measurement itself. Should a future <argLine> override drop the placeholder, or a dependency scope change, coverage would silently fall to zero rather than fail; the script asserts the module set and non-zero coverage for the three cross-module cases, but never a percentage. Current aggregate: 67.5% of instructions, 67.4% of lines. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> --- .github/scripts/check-coverage-report.py | 97 ++++++++++++++ .github/workflows/maven.yml | 26 +++- README.md | 23 ++++ codecov.yml | 33 +++++ pom.xml | 54 ++++++-- wicket-coverage/pom.xml | 209 +++++++++++++++++++++++++++++++ 6 files changed, 430 insertions(+), 12 deletions(-) diff --git a/.github/scripts/check-coverage-report.py b/.github/scripts/check-coverage-report.py new file mode 100755 index 0000000000..6c3e343f3c --- /dev/null +++ b/.github/scripts/check-coverage-report.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python3 +# 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. +"""Sanity-check the aggregated JaCoCo report before it is published. + +Most Wicket tests live in a module other than the code they exercise, so the +report depends on the dependency scopes declared in wicket-coverage/pom.xml: +compile contributes classes, test contributes execution data only. If someone +overrides maven-surefire-plugin's <argLine> without keeping the +@{jacoco.argLine} placeholder, or changes those scopes, coverage silently drops +to zero instead of failing the build. These assertions are that tripwire. + +This deliberately checks structure, not a coverage percentage. It is not a +quality gate: it only fails when the measurement itself is broken. +""" +import sys +import xml.etree.ElementTree as ET + +REPORT = 'wicket-coverage/target/site/jacoco-aggregate/jacoco.xml' + +# Every module listed at compile scope in wicket-coverage/pom.xml. +EXPECTED_MODULES = { + 'wicket-auth-roles', 'wicket-bean-validation', 'wicket-cdi', 'wicket-core', + 'wicket-devutils', 'wicket-extensions', 'wicket-extensions-tester', + 'wicket-guice', 'wicket-ioc', 'wicket-jmx', 'wicket-native-websocket-core', + 'wicket-native-websocket-javax', 'wicket-native-websocket-tester', + 'wicket-request', 'wicket-spring', 'wicket-tester', 'wicket-util', + 'wicket-velocity', +} + +# Modules whose tests live elsewhere. Zero here means cross-module attribution +# has broken, which is the failure this script exists to catch. +MUST_BE_COVERED = ('wicket-core', 'wicket-tester', 'wicket-cdi') + + +def instructions(group): + for counter in group.findall('counter'): + if counter.get('type') == 'INSTRUCTION': + return int(counter.get('missed')), int(counter.get('covered')) + return 0, 0 + + +def main(): + try: + root = ET.parse(REPORT).getroot() + except (OSError, ET.ParseError) as e: + sys.exit('cannot read %s: %s' % (REPORT, e)) + + groups = {g.get('name'): g for g in root.findall('group')} + failures = [] + + for name in sorted(groups): + missed, covered = instructions(groups[name]) + total = missed + covered + pct = (100.0 * covered / total) if total else 0.0 + print('%-34s %8d / %8d instructions (%5.1f%%)' % (name, covered, total, pct)) + print() + + missing = EXPECTED_MODULES - set(groups) + extra = set(groups) - EXPECTED_MODULES + if missing: + failures.append('missing from the report: %s' % ', '.join(sorted(missing))) + if extra: + failures.append('unexpectedly present: %s -- update EXPECTED_MODULES here and ' + 'the dependency list in wicket-coverage/pom.xml together' + % ', '.join(sorted(extra))) + + for name in MUST_BE_COVERED: + if name in groups and instructions(groups[name])[1] == 0: + failures.append('%s has zero coverage: its tests live in another module, so ' + 'this means the JaCoCo agent did not attach or a dependency ' + 'scope in wicket-coverage/pom.xml is wrong' % name) + + if failures: + for f in failures: + print('FAIL: %s' % f, file=sys.stderr) + return 1 + print('OK: %d modules reported, cross-module attribution intact' % len(groups)) + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index 27c889137f..015890e92e 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -53,4 +53,28 @@ jobs: - name: Build with Maven run: | java -version - ./apache-maven-${MAVEN_VERSION}/bin/mvn --show-version clean verify -Pjs-test + ./apache-maven-${MAVEN_VERSION}/bin/mvn --show-version \ + clean verify -Pjs-test \ + ${{ matrix.java == '21' && '-Pcoverage' || '' }} + + # Fails if the measurement itself has broken - for instance if a future + # <argLine> override drops the @{jacoco.argLine} placeholder, which would + # silently zero a module's coverage rather than failing the build. + - name: Check coverage report + if: matrix.java == '21' + run: python3 .github/scripts/check-coverage-report.py + + # Coverage is measured on one JDK only: a single number is all that is needed, + # and this leaves the other legs' timings untouched. The aggregated report is + # produced by the wicket-coverage module; see the 'coverage' profile in pom.xml. + - name: Upload coverage to Codecov + # v7.0.0 - external actions must be pinned to a commit SHA: + # https://infra.apache.org/github-actions-policy.html + uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f + if: matrix.java == '21' && github.repository_owner == 'apache' + with: + files: ./wicket-coverage/target/site/jacoco-aggregate/jacoco.xml + # Empty for pull requests from forks, which upload tokenlessly. Do NOT switch + # to pull_request_target to get at the secret; ASF policy forbids it. + token: ${{ secrets.CODECOV_TOKEN }} + disable_search: true diff --git a/README.md b/README.md index b6b89b1574..fb2234a047 100644 --- a/README.md +++ b/README.md @@ -200,6 +200,29 @@ When building using Maven 3, execute one of the following in the root folder: creates wicket-(subproject)-x.y.z.jar(s) in according target subdirectories and installs the jar files into your local Maven repository for use in other projects. +Code coverage +------------- + +Coverage is measured with JaCoCo and aggregated into a single report, because most +Wicket tests live in a module other than the code they exercise (the tests for +wicket-core are in wicket-core-tests, for example). To produce it: + + - mvn clean verify -Pcoverage + + writes the aggregated report to + wicket-coverage/target/site/jacoco-aggregate/index.html + +To build only the modules that feed the report, which is considerably faster: + + - mvn clean verify -Pcoverage -pl wicket-coverage -am + +Note that a build without "clean" merges the previous run's data into the new report, +because the JaCoCo agent appends by default. + +Every push and pull request also uploads this report to +https://app.codecov.io/gh/apache/wicket. Coverage is reported there, never enforced: +no coverage check can fail a build or block a merge. + Migrating from 9.x ------------------ diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 0000000000..a5b8b91d98 --- /dev/null +++ b/codecov.yml @@ -0,0 +1,33 @@ +# 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. + +# Coverage is reported, never enforced. 'informational' keeps the project and patch +# checks visible on a pull request, showing the real numbers, while making them +# incapable of failing a build or blocking a merge. +coverage: + status: + project: + default: + informational: true + patch: + default: + informational: true + +comment: + layout: "condensed_header, diff, flags, components" + # Stay quiet on pull requests that do not move coverage, which is most of them. + require_changes: true diff --git a/pom.xml b/pom.xml index 9c5e0f5e5a..d162101833 100644 --- a/pom.xml +++ b/pom.xml @@ -124,6 +124,8 @@ <module>wicket-migration</module> <module>wicket-tester</module> <module>wicket-extensions-tester</module> + <!-- must stay last: aggregates the JaCoCo data of every module above --> + <module>wicket-coverage</module> </modules> <properties> <!-- Encoding --> @@ -132,6 +134,17 @@ <project.build.outputTimestamp>2026-01-31T19:00:35Z</project.build.outputTimestamp> + <!-- + Late-bound placeholder for the JaCoCo agent's -javaagent argument, consumed + by maven-surefire-plugin's <argLine> as @{jacoco.argLine}. + + This property MUST stay declared, even though it is empty: surefire only + substitutes @{x} for properties that actually exist, otherwise it passes the + literal "@{x}" to the forked JVM and every test module fails with + "Unrecognized option". jacoco:prepare-agent overwrites the value; see + <propertyName> in the 'coverage' profile. + --> + <jacoco.argLine /> <javadoc.additionalJOption /> <javadoc.jdk.apidocs.link>https://docs.oracle.com/en/java/javase/${java.specification.version}/docs/api/</javadoc.jdk.apidocs.link> @@ -1123,7 +1136,14 @@ <includes> <include>**/*Test.java</include> </includes> - <argLine>--add-opens java.base/jdk.internal.loader=ALL-UNNAMED --add-opens=java.base/java.lang=ALL-UNNAMED + <!-- + @{jacoco.argLine} is replaced by surefire at fork time, and is empty + unless -Pcoverage is active. Any future override of <argLine> (in a + profile or a module) MUST keep this placeholder, or coverage for that + module silently drops to zero. + --> + <argLine>@{jacoco.argLine} + --add-opens java.base/jdk.internal.loader=ALL-UNNAMED --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.lang.reflect=ALL-UNNAMED --add-opens=java.base/java.io=ALL-UNNAMED --add-modules=ALL-SYSTEM</argLine> <useModulePath>false</useModulePath> @@ -1253,6 +1273,11 @@ </lifecycleMappingMetadata> </configuration> </plugin> + <plugin> + <groupId>org.jacoco</groupId> + <artifactId>jacoco-maven-plugin</artifactId> + <version>${jacoco.version}</version> + </plugin> <plugin> <groupId>org.primefaces.extensions</groupId> <artifactId>resources-optimizer-maven-plugin</artifactId> @@ -1357,30 +1382,37 @@ </profile> <profile> + <!-- + Attaches the JaCoCo agent to every surefire fork by setting the + 'jacoco.argLine' property, which surefire's <argLine> consumes as + @{jacoco.argLine}. + + The aggregated report is produced by the wicket-coverage module, which + declares a profile with this same id. Deliberately NO per-module 'report' + goal: most tests live in a module other than the code they exercise + (wicket-core has no tests of its own, wicket-core-tests has ~500), so + per-module reports would show 0% for wicket-core. + + Usage: mvn clean verify -Pcoverage + Result: wicket-coverage/target/site/jacoco-aggregate/{index.html,jacoco.xml} + --> <id>coverage</id> <build> <plugins> <plugin> <groupId>org.jacoco</groupId> <artifactId>jacoco-maven-plugin</artifactId> - <version>${jacoco.version}</version> - <executions> <execution> <id>jacoco-initialize</id> <goals> <goal>prepare-agent</goal> </goals> - </execution> - <execution> - <id>jacoco-site</id> - <phase>package</phase> - <goals> - <goal>report</goal> - </goals> + <configuration> + <propertyName>jacoco.argLine</propertyName> + </configuration> </execution> </executions> - </plugin> </plugins> </build> diff --git a/wicket-coverage/pom.xml b/wicket-coverage/pom.xml new file mode 100644 index 0000000000..bac3a56108 --- /dev/null +++ b/wicket-coverage/pom.xml @@ -0,0 +1,209 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + 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. +--> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>org.apache.wicket</groupId> + <artifactId>wicket-parent</artifactId> + <version>11.0.0-SNAPSHOT</version> + <relativePath>../pom.xml</relativePath> + </parent> + <artifactId>wicket-coverage</artifactId> + <packaging>pom</packaging> + <name>Wicket Code Coverage</name> + <description> + Build-only module that aggregates the JaCoCo execution data of all Wicket + modules into a single report. Not released, and does nothing unless the + 'coverage' profile is active. + + The dependency list below IS the configuration. jacoco:report-aggregate reads + the scope of each dependency: + compile -> the module's classes and sources appear in the report, and its + own target/jacoco.exec is read; + test -> only the module's target/jacoco.exec is read. Used for the + test-only modules that hold the tests of a *different* module. + </description> + <properties> + <japicmp.skip>true</japicmp.skip> + <maven.deploy.skip>true</maven.deploy.skip> <!-- this module is not released --> + </properties> + <dependencies> + <!-- + compile scope: classes + sources + own execution data are reported. + Scopes are stated explicitly on purpose, because the whole design hinges + on Dependency.getScope(). + --> + <dependency> + <groupId>org.apache.wicket</groupId> + <artifactId>wicket-auth-roles</artifactId> + <scope>compile</scope> + </dependency> + <dependency> + <groupId>org.apache.wicket</groupId> + <artifactId>wicket-bean-validation</artifactId> + <scope>compile</scope> + </dependency> + <dependency> + <groupId>org.apache.wicket</groupId> + <artifactId>wicket-cdi</artifactId> + <scope>compile</scope> + </dependency> + <dependency> + <groupId>org.apache.wicket</groupId> + <artifactId>wicket-core</artifactId> + <scope>compile</scope> + </dependency> + <dependency> + <groupId>org.apache.wicket</groupId> + <artifactId>wicket-devutils</artifactId> + <scope>compile</scope> + </dependency> + <dependency> + <groupId>org.apache.wicket</groupId> + <artifactId>wicket-extensions</artifactId> + <scope>compile</scope> + </dependency> + <dependency> + <groupId>org.apache.wicket</groupId> + <artifactId>wicket-extensions-tester</artifactId> + <scope>compile</scope> + </dependency> + <dependency> + <groupId>org.apache.wicket</groupId> + <artifactId>wicket-guice</artifactId> + <scope>compile</scope> + </dependency> + <dependency> + <groupId>org.apache.wicket</groupId> + <artifactId>wicket-ioc</artifactId> + <scope>compile</scope> + </dependency> + <dependency> + <groupId>org.apache.wicket</groupId> + <artifactId>wicket-jmx</artifactId> + <scope>compile</scope> + </dependency> + <dependency> + <groupId>org.apache.wicket</groupId> + <artifactId>wicket-native-websocket-core</artifactId> + <scope>compile</scope> + </dependency> + <dependency> + <groupId>org.apache.wicket</groupId> + <artifactId>wicket-native-websocket-javax</artifactId> + <scope>compile</scope> + </dependency> + <dependency> + <groupId>org.apache.wicket</groupId> + <artifactId>wicket-native-websocket-tester</artifactId> + <scope>compile</scope> + </dependency> + <dependency> + <groupId>org.apache.wicket</groupId> + <artifactId>wicket-request</artifactId> + <scope>compile</scope> + </dependency> + <dependency> + <groupId>org.apache.wicket</groupId> + <artifactId>wicket-spring</artifactId> + <scope>compile</scope> + </dependency> + <dependency> + <!-- managed to 'test' in wicket-parent; forced to compile so that its classes are reported --> + <groupId>org.apache.wicket</groupId> + <artifactId>wicket-tester</artifactId> + <scope>compile</scope> + </dependency> + <dependency> + <groupId>org.apache.wicket</groupId> + <artifactId>wicket-util</artifactId> + <scope>compile</scope> + </dependency> + <dependency> + <groupId>org.apache.wicket</groupId> + <artifactId>wicket-velocity</artifactId> + <scope>compile</scope> + </dependency> + <!-- + test scope: execution data only, no classes or sources. These modules have + no main sources at all; they hold the tests for the modules above. + wicket-core-tests -> the tests of wicket-core AND wicket-tester + wicket-cdi-tests -> the only tests wicket-cdi has + + Deliberately omitted, and why: + wicket, wicket-experimental, wicket-native-websocket + aggregator POMs, no classes + wicket-examples demo webapp, not a released library; including it + would swamp the framework signal and drag jetty, + weld and httpunit into this POM's compile graph. + To let its tests count towards wicket-core, add it + here as <scope>test</scope><type>war</type> and + re-check dependency convergence. + wicket-user-guide no Java sources (asciidoctor documentation) + archetypes/quickstart no Java sources (packaging=maven-archetype) + wicket-migration no main Java sources (OpenRewrite recipes) + wicket-metrics no tests, so it would only contribute a 0% bundle + wicket-objectsizeof-agent a -javaagent JAR, not a classpath artifact + wicket-common-tests its single test only opens jar files off the + classpath; no meaningful coverage + wicket-js-tests surefire is skipped there, so no jacoco.exec exists + --> + <dependency> + <groupId>org.apache.wicket</groupId> + <artifactId>wicket-cdi-tests</artifactId> + <version>${project.version}</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.apache.wicket</groupId> + <artifactId>wicket-core-tests</artifactId> + <scope>test</scope> + </dependency> + </dependencies> + <profiles> + <profile> + <!-- + Same id as the profile in wicket-parent that attaches the agent, so a + single -Pcoverage switches on both halves of the feature. + + Gated on the profile rather than unconditional on purpose: with no + execution data present, report-aggregate still emits a well-formed 0% + jacoco.xml, which would be a trap for anything consuming that path. + --> + <id>coverage</id> + <build> + <plugins> + <plugin> + <groupId>org.jacoco</groupId> + <artifactId>jacoco-maven-plugin</artifactId> + <executions> + <execution> + <!-- report-aggregate has no default phase; binding is mandatory --> + <id>jacoco-aggregate-report</id> + <phase>verify</phase> + <goals> + <goal>report-aggregate</goal> + </goals> + </execution> + </executions> + </plugin> + </plugins> + </build> + </profile> + </profiles> +</project>
