Skip to content

Commit 1d4b9af

Browse files
authored
Multi-module build (#6)
Multi-module build + deferred reporting with tree.
1 parent 5c6167f commit 1d4b9af

File tree

12 files changed

+1591
-272
lines changed

12 files changed

+1591
-272
lines changed

.github/workflows/build.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,12 @@ jobs:
3535
- name: Build and test
3636
run: mvn -B verify
3737

38+
- name: find exec files
39+
run: find . |& grep exec
40+
41+
- name: find jacoco files
42+
run: find . |& grep jacoco
43+
3844
- name: Upload test results
3945
if: always()
4046
uses: actions/upload-artifact@v4
@@ -59,4 +65,5 @@ jobs:
5965
if: ${{ !cancelled() }}
6066
uses: codecov/codecov-action@v5
6167
with:
68+
verbose: true
6269
token: ${{ secrets.CODECOV_TOKEN }}

README.md

Lines changed: 99 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
1-
# Jacoco Console Reporter Maven Plugin
1+
# JaCoCo Console Reporter Maven Plugin
22

3-
A custom Maven plugin that generates a textual tree-like coverage report from JaCoCo's `jacoco.exec` file, displaying coverage metrics (Class %, Method %, Branch %, Line %) for packages, source files, and the entire project.
3+
A custom Maven plugin that generates a textual tree-like coverage report from JaCoCo's execution data files, displaying coverage metrics (Class %, Method %, Branch %, Line %) for packages, source files, and the entire project.
44

55
## Features
6-
- Reads coverage data from `jacoco.exec`
6+
- Reads coverage data from `jacoco.exec` files
77
- Analyzes class files from the project's build output directory
88
- Outputs a hierarchical console-based report with coverage metrics
9-
- Tree-like package structure visualization
9+
- Tree-like package structure visualization with collapsible paths
1010
- Instant visibility of coverage metrics during build
11+
- Multi-module project support with option to defer reporting until the end
12+
- Automatic scanning for `jacoco.exec` files across modules
13+
- Support for custom JaCoCo execution file patterns
1114

1215
## Prerequisites
1316
- Maven 3.x
@@ -45,20 +48,105 @@ Ensure the JaCoCo plugin has executed beforehand to generate jacoco.exec.
4548

4649
## Configuration
4750

48-
* `jacocoExecFile`: Path to `jacoco.exec` (default: `${project.build.directory}/jacoco.exec`)
49-
* `classesDirectory`: Directory containing compiled classes (default: `${project.build.outputDirectory}`)
51+
| Parameter | Description | Default Value |
52+
|-----------------------|-----------------------------------------------------------|------------------------------------------|
53+
| `jacocoExecFile` | Path to the JaCoCo execution data file | `${project.build.directory}/jacoco.exec` |
54+
| `classesDirectory` | Directory containing compiled classes | `${project.build.outputDirectory}` |
55+
| `deferReporting` | Defer reporting until the end (for multi-module projects) | `false` |
56+
| `showFiles` | Whether to show individual source files in the report | `true` |
57+
| `scanModules` | Automatically scan for exec files in project modules | `false` |
58+
| `baseDir` | Base directory for module scanning | `${project.basedir}` |
59+
| `additionalExecFiles` | Additional exec files to include in the report | `[]` |
5060

5161
## Example Output
5262
```text
5363
[INFO] Overall Coverage Summary
5464
[INFO] Package | Class, % | Method, % | Branch, % | Line, %
5565
[INFO] --------------------------------------------------------------------------------------------------------------------------------------------------------
56-
[INFO] com.example | 50.00% (1/2) | 33.33% (2/6) | 25.00% (1/4) | 40.00% (4/10)
57-
[INFO] ├─ Calculator.java | 50.00% (1/2) | 50.00% (1/2) | 0.00% (0/0) | 50.00% (1/2)
58-
[INFO] └─ AdvancedCalculator.java | 100.00% (1/1) | 25.00% (1/4) | 25.00% (1/4) | 37.50% (3/8)
66+
[INFO] com.example | 100.00% (3/3) | 83.33% (5/6) | 50.00% (2/4) | 75.00% (15/20)
67+
[INFO] ├─model | 100.00% (1/1) | 100.00% (2/2) | 50.00% (1/2) | 87.50% (7/8)
68+
[INFO] │ └─Model.java | 100.00% (1/1) | 100.00% (2/2) | 50.00% (1/2) | 87.50% (7/8)
69+
[INFO] ├─util | 100.00% (1/1) | 100.00% (2/2) | 50.00% (1/2) | 87.50% (7/8)
70+
[INFO] │ └─Util.java | 100.00% (1/1) | 100.00% (2/2) | 50.00% (1/2) | 87.50% (7/8)
71+
[INFO] └─Example.java | 100.00% (1/1) | 33.33% (1/2) | 0.00% (0/0) | 25.00% (1/4)
5972
[INFO] --------------------------------------------------------------------------------------------------------------------------------------------------------
60-
[INFO] all classes | 75.00% (2/3) | 33.33% (2/6) | 25.00% (1/4) | 40.00% (4/10)
73+
[INFO] all classes | 100.00% (3/3) | 83.33% (5/6) | 50.00% (2/4) | 75.00% (15/20)
6174
```
6275

76+
## Advanced Usage
77+
78+
### Multi-Module Projects
79+
80+
For multi-module projects, you can configure the plugin to aggregate coverage across modules. All configuration values are the defaults so they don't have to be added, but are shown here for completeness’s sake:
81+
82+
```xml
83+
<plugin>
84+
<groupId>io.github.svaningelgem</groupId>
85+
<artifactId>jacoco-console-reporter</artifactId>
86+
<version>1.0.0</version>
87+
<configuration>
88+
<jacocoExecFile>${project.build.directory}/jacoco.exec</jacocoExecFile>
89+
<classesDirectory>${project.build.outputDirectory}</classesDirectory>
90+
<deferReporting>true</deferReporting>
91+
<showFiles>true</showFiles>
92+
<additionalExecFiles />
93+
<scanModules>false</scanModules>
94+
<baseDir>${project.basedir}</baseDir>
95+
</configuration>
96+
<executions>
97+
<execution>
98+
<goals>
99+
<goal>report</goal>
100+
</goals>
101+
<phase>verify</phase>
102+
</execution>
103+
</executions>
104+
</plugin>
105+
```
106+
107+
This configuration will:
108+
- Defer the reporting until the last module in the build
109+
- Automatically scan for JaCoCo execution files in all modules
110+
111+
### Custom JaCoCo File Locations
112+
113+
If your JaCoCo plugin uses a non-default location for the execution data file:
114+
115+
```xml
116+
<plugin>
117+
<groupId>io.github.svaningelgem</groupId>
118+
<artifactId>jacoco-console-reporter</artifactId>
119+
<version>1.0.0</version>
120+
<configuration>
121+
<jacocoExecFile>${project.build.directory}/custom-jacoco.exec</jacocoExecFile>
122+
</configuration>
123+
<!-- ... -->
124+
</plugin>
125+
```
126+
127+
## Implementation Details
128+
129+
The plugin works by:
130+
131+
1. Detecting JaCoCo execution data files (default or custom locations)
132+
2. Loading the execution data using JaCoCo's API
133+
3. Analyzing compiled classes using the execution data
134+
4. Building a hierarchical directory structure representing the package organization
135+
5. Calculating coverage metrics (class, method, branch, line) for each node
136+
6. Generating a tree-like report to the console
137+
63138
## Contributing
64-
Contributions are welcome! Feel free to submit issues or pull requests to enhance this plugin.
139+
140+
Contributions are welcome! Feel free to submit issues or pull requests to enhance this plugin.
141+
142+
### Building from Source
143+
144+
```bash
145+
mvn clean install
146+
```
147+
148+
### Running Tests
149+
150+
```bash
151+
mvn test
152+
```

jacoco-console-reporter/pom.xml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,11 @@
9191
<groupId>org.slf4j</groupId>
9292
<artifactId>slf4j-simple</artifactId>
9393
</dependency>
94+
95+
<dependency>
96+
<groupId>org.mockito</groupId>
97+
<artifactId>mockito-core</artifactId>
98+
</dependency>
9499
</dependencies>
95100

96101
<build>
@@ -125,6 +130,11 @@
125130
<artifactId>maven-javadoc-plugin</artifactId>
126131
</plugin>
127132

133+
<!-- Jacoco -->
134+
<plugin>
135+
<groupId>org.jacoco</groupId>
136+
<artifactId>jacoco-maven-plugin</artifactId>
137+
</plugin>
128138
</plugins>
129139
</build>
130140

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package io.github.svaningelgem;
2+
3+
import lombok.Data;
4+
import org.jetbrains.annotations.NotNull;
5+
6+
/**
7+
* Comprehensive collection of coverage metrics for a source file or directory.
8+
* Tracks coverage at multiple levels: classes, methods, lines, and branches.
9+
* All metrics maintain both total count and covered count for percentage calculation.
10+
*/
11+
@Data
12+
class CoverageMetrics {
13+
/**
14+
* Total number of classes in the scope
15+
*/
16+
public int totalClasses;
17+
/**
18+
* Number of classes that have any coverage
19+
*/
20+
public int coveredClasses;
21+
/**
22+
* Total number of methods across all classes
23+
*/
24+
public int totalMethods;
25+
/**
26+
* Number of methods that have been executed
27+
*/
28+
public int coveredMethods;
29+
/**
30+
* Total number of lines of code
31+
*/
32+
public int totalLines;
33+
/**
34+
* Number of lines that have been executed
35+
*/
36+
public int coveredLines;
37+
/**
38+
* Total number of branches in conditional statements
39+
*/
40+
public int totalBranches;
41+
/**
42+
* Number of branches that have been executed
43+
*/
44+
public int coveredBranches;
45+
46+
void add(@NotNull CoverageMetrics other) {
47+
totalClasses += other.totalClasses;
48+
coveredClasses += other.coveredClasses;
49+
totalMethods += other.totalMethods;
50+
coveredMethods += other.coveredMethods;
51+
totalLines += other.totalLines;
52+
coveredLines += other.coveredLines;
53+
totalBranches += other.totalBranches;
54+
coveredBranches += other.coveredBranches;
55+
}
56+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package io.github.svaningelgem;
2+
3+
import org.jetbrains.annotations.Contract;
4+
import org.jetbrains.annotations.NotNull;
5+
6+
public class Defaults {
7+
// Define column widths
8+
static final int PACKAGE_WIDTH = 50;
9+
static final int METRICS_WIDTH = 20;
10+
11+
// Define tree characters based on terminal capabilities
12+
public static final String LASTDIR_SPACE = " ";
13+
public static final String VERTICAL_LINE = "│ ";
14+
public static final String TEE = "├─";
15+
public static final String CORNER = "└─";
16+
17+
static final String DIVIDER = getDivider();
18+
static final String HEADER_FORMAT = "%-" + PACKAGE_WIDTH + "s " + VERTICAL_LINE + "%-" + METRICS_WIDTH + "s " + VERTICAL_LINE + "%-" + METRICS_WIDTH + "s " + VERTICAL_LINE + "%-" + METRICS_WIDTH + "s " + VERTICAL_LINE + "%-" + METRICS_WIDTH + "s";
19+
static final String LINE_FORMAT = "%-" + PACKAGE_WIDTH + "s " + VERTICAL_LINE + "%-" + METRICS_WIDTH + "s " + VERTICAL_LINE + "%-" + METRICS_WIDTH + "s " + VERTICAL_LINE + "%-" + METRICS_WIDTH + "s " + VERTICAL_LINE + "%-" + METRICS_WIDTH + "s";
20+
21+
/**
22+
* Truncates a string in the middle if it exceeds maxLength
23+
* Example: "com.example.very.long.package.name" -> "com.example...kage.name"
24+
*/
25+
public static @NotNull String truncateMiddle(@NotNull String input) {
26+
if (input.length() <= PACKAGE_WIDTH) {
27+
return input;
28+
}
29+
30+
int prefixLength = (PACKAGE_WIDTH - 2) / 2;
31+
int suffixLength = PACKAGE_WIDTH - 2 - prefixLength;
32+
33+
return input.substring(0, prefixLength) + ".." +
34+
input.substring(input.length() - suffixLength);
35+
}
36+
37+
/**
38+
* Formats coverage metrics as a percentage with covered/total values.
39+
*
40+
* @param covered Number of covered items
41+
* @param total Total number of items
42+
* @return Formatted string showing percentage and ratio (e.g., "75.00% (3/4)")
43+
*/
44+
@Contract(pure = true)
45+
public static @NotNull String formatCoverage(int covered, int total) {
46+
if (total == 0) return "100.00% (0/0)";
47+
double percentage = (double) covered / total * 100;
48+
return String.format("%5.2f%% (%d/%d)", percentage, covered, total);
49+
}
50+
51+
/**
52+
* Build a divider with certain widths
53+
*/
54+
private static @NotNull String getDivider() {
55+
StringBuilder divider = new StringBuilder();
56+
for (int i = 0; i < PACKAGE_WIDTH; i++) divider.append("-");
57+
divider.append("-|-");
58+
for (int i = 0; i < METRICS_WIDTH; i++) divider.append("-");
59+
divider.append("-|-");
60+
for (int i = 0; i < METRICS_WIDTH; i++) divider.append("-");
61+
divider.append("-|-");
62+
for (int i = 0; i < METRICS_WIDTH; i++) divider.append("-");
63+
divider.append("-|-");
64+
for (int i = 0; i < METRICS_WIDTH; i++) divider.append("-");
65+
return divider.toString();
66+
}
67+
68+
}

0 commit comments

Comments
 (0)