View Javadoc
1   /*
2    * The MIT License (MIT)
3    *
4    * Copyright (c) 2013-2026 The Coveralls Maven Plugin Project Contributors:
5    *     https://github.com/hazendaz/coveralls-maven-plugin/graphs/contributors
6    *
7    * Permission is hereby granted, free of charge, to any person obtaining a copy
8    * of this software and associated documentation files (the "Software"), to deal
9    * in the Software without restriction, including without limitation the rights
10   * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11   * copies of the Software, and to permit persons to whom the Software is
12   * furnished to do so, subject to the following conditions:
13   *
14   * The above copyright notice and this permission notice shall be included in
15   * all copies or substantial portions of the Software.
16   *
17   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18   * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20   * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22   * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23   * THE SOFTWARE.
24   */
25  package org.eluder.coveralls.maven.plugin.util;
26  
27  import java.io.File;
28  import java.io.IOException;
29  import java.nio.file.Files;
30  import java.nio.file.Path;
31  import java.util.Arrays;
32  import java.util.List;
33  
34  import org.apache.maven.model.Build;
35  import org.apache.maven.model.Model;
36  import org.apache.maven.model.Reporting;
37  import org.apache.maven.project.MavenProject;
38  import org.eluder.coveralls.maven.plugin.CoverageParser;
39  import org.eluder.coveralls.maven.plugin.parser.CloverParser;
40  import org.eluder.coveralls.maven.plugin.parser.CoberturaParser;
41  import org.eluder.coveralls.maven.plugin.parser.JaCoCoParser;
42  import org.eluder.coveralls.maven.plugin.parser.SagaParser;
43  import org.eluder.coveralls.maven.plugin.source.SourceLoader;
44  import org.junit.jupiter.api.Assertions;
45  import org.junit.jupiter.api.BeforeEach;
46  import org.junit.jupiter.api.Test;
47  import org.junit.jupiter.api.extension.ExtendWith;
48  import org.junit.jupiter.api.io.CleanupMode;
49  import org.junit.jupiter.api.io.TempDir;
50  import org.mockito.Mock;
51  import org.mockito.Mockito;
52  import org.mockito.junit.jupiter.MockitoExtension;
53  
54  /**
55   * The Class CoverageParsersFactoryTest.
56   */
57  @ExtendWith(MockitoExtension.class)
58  class CoverageParsersFactoryTest {
59  
60      /** The folder. */
61      @TempDir(cleanup = CleanupMode.ON_SUCCESS)
62      public Path folder;
63  
64      /** The project mock. */
65      @Mock
66      private MavenProject projectMock;
67  
68      /** The source loader mock. */
69      @Mock
70      private SourceLoader sourceLoaderMock;
71  
72      /** The model mock. */
73      @Mock
74      private Model modelMock;
75  
76      /** The reporting mock. */
77      @Mock
78      private Reporting reportingMock;
79  
80      /** The build mock. */
81      @Mock
82      private Build buildMock;
83  
84      /** The reporting dir. */
85      private Path reportingDir;
86  
87      /** The target dir. */
88      private Path targetDir;
89  
90      /**
91       * Inits the covreage parsers factory test.
92       *
93       * @throws IOException
94       *             Signals that an I/O exception has occurred.
95       */
96      @BeforeEach
97      void init() throws IOException {
98          this.reportingDir = Files.createDirectory(this.folder.resolve("reportingDir"));
99          this.targetDir = Files.createDirectory(this.folder.resolve("targetDir"));
100         Mockito.lenient().when(this.projectMock.getCollectedProjects()).thenReturn(List.of());
101         Mockito.lenient().when(this.projectMock.getModel()).thenReturn(this.modelMock);
102         Mockito.lenient().when(this.projectMock.getBuild()).thenReturn(this.buildMock);
103         Mockito.lenient().when(this.modelMock.getReporting()).thenReturn(this.reportingMock);
104         Mockito.lenient().when(this.reportingMock.getOutputDirectory())
105                 .thenReturn(this.reportingDir.toFile().getAbsolutePath());
106         Mockito.lenient().when(this.buildMock.getDirectory()).thenReturn(this.targetDir.toFile().getAbsolutePath());
107     }
108 
109     /**
110      * Creates the empty parsers.
111      */
112     @Test
113     void createEmptyParsers() {
114         Assertions.assertThrows(IOException.class, () -> this.createCoverageParsersFactory().createParsers());
115     }
116 
117     /**
118      * In this test, only the unit test JaCoCo report exists, so it should be added to parsers.
119      *
120      * @throws IOException
121      *             Signals that an I/O exception has occurred.
122      */
123     @Test
124     void createJaCoCoParserForUnitTestReport() throws IOException {
125         final var jacocoDir = Files
126                 .createDirectory(this.reportingDir.resolve(CoverageParsersFactory.DEFAULT_JACOCO_DIRECTORY));
127         Files.createFile(jacocoDir.resolve("jacoco.xml"));
128         final var parsers = this.createCoverageParsersFactory().createParsers();
129         Assertions.assertEquals(1, parsers.size());
130         Assertions.assertEquals(JaCoCoParser.class, parsers.get(0).getClass());
131     }
132 
133     /**
134      * In this test, only the integration test JaCoCo report exists, so it should be added to parsers.
135      *
136      * @throws IOException
137      *             Signals that an I/O exception has occurred.
138      */
139     @Test
140     void createJaCoCoParserForIntegrationTestReport() throws IOException {
141         final var jacocoItDir = Files
142                 .createDirectory(this.reportingDir.resolve(CoverageParsersFactory.DEFAULT_JACOCO_IT_DIRECTORY));
143         Files.createFile(jacocoItDir.resolve("jacoco.xml"));
144         final var parsers = this.createCoverageParsersFactory().createParsers();
145         Assertions.assertEquals(1, parsers.size());
146         Assertions.assertEquals(JaCoCoParser.class, parsers.get(0).getClass());
147     }
148 
149     /**
150      * In this test, both the unit test and integration test JaCoCo reports exist, so both should be added to parsers.
151      *
152      * @throws IOException
153      *             Signals that an I/O exception has occurred.
154      */
155     @Test
156     void createJaCoCoParserForUnitTestAndIntegrationTestReports() throws IOException {
157         final var jacocoDir = Files
158                 .createDirectory(this.reportingDir.resolve(CoverageParsersFactory.DEFAULT_JACOCO_DIRECTORY));
159         Files.createFile(jacocoDir.resolve("jacoco.xml"));
160 
161         final var jacocoItDir = Files
162                 .createDirectory(this.reportingDir.resolve(CoverageParsersFactory.DEFAULT_JACOCO_IT_DIRECTORY));
163         Files.createFile(jacocoItDir.resolve("jacoco.xml"));
164 
165         final var parsers = this.createCoverageParsersFactory().createParsers();
166         Assertions.assertEquals(2, parsers.size());
167         Assertions.assertEquals(JaCoCoParser.class, parsers.get(0).getClass());
168         Assertions.assertEquals(JaCoCoParser.class, parsers.get(1).getClass());
169     }
170 
171     /**
172      * In this test, even though all JaCoCo reports exist, only the merged report should be added to parsers.
173      *
174      * @throws IOException
175      *             Signals that an I/O exception has occurred.
176      */
177     @Test
178     void createJaCoCoParserForMergedReport() throws IOException {
179         final var jacocoDir = Files
180                 .createDirectory(this.reportingDir.resolve(CoverageParsersFactory.DEFAULT_JACOCO_DIRECTORY));
181         Files.createFile(jacocoDir.resolve("jacoco.xml"));
182 
183         final var jacocoItDir = Files
184                 .createDirectory(this.reportingDir.resolve(CoverageParsersFactory.DEFAULT_JACOCO_IT_DIRECTORY));
185         Files.createFile(jacocoItDir.resolve("jacoco.xml"));
186 
187         final var jacocoMergedDir = Files
188                 .createDirectory(this.reportingDir.resolve(CoverageParsersFactory.DEFAULT_JACOCO_MERGED_DIRECTORY));
189         Files.createFile(jacocoMergedDir.resolve("jacoco.xml"));
190 
191         final var parsers = this.createCoverageParsersFactory().createParsers();
192         Assertions.assertEquals(1, parsers.size());
193         Assertions.assertEquals(JaCoCoParser.class, parsers.get(0).getClass());
194     }
195 
196     /**
197      * Creates the cobertura parser.
198      *
199      * @throws IOException
200      *             Signals that an I/O exception has occurred.
201      */
202     @Test
203     void createCoberturaParser() throws IOException {
204         final var coberturaDir = Files.createDirectory(this.reportingDir.resolve("cobertura"));
205         Files.createFile(coberturaDir.resolve("coverage.xml"));
206         final var parsers = this.createCoverageParsersFactory().createParsers();
207         Assertions.assertEquals(1, parsers.size());
208         Assertions.assertEquals(CoberturaParser.class, parsers.get(0).getClass());
209     }
210 
211     /**
212      * Creates the saga parser.
213      *
214      * @throws IOException
215      *             Signals that an I/O exception has occurred.
216      */
217     @Test
218     void createSagaParser() throws IOException {
219         final var sagaDir = Files.createDirectory(this.targetDir.resolve("saga-coverage"));
220         Files.createFile(sagaDir.resolve("total-coverage.xml"));
221         final var parsers = this.createCoverageParsersFactory().createParsers();
222         Assertions.assertEquals(1, parsers.size());
223         Assertions.assertEquals(SagaParser.class, parsers.get(0).getClass());
224     }
225 
226     /**
227      * Creates the clover parser.
228      *
229      * @throws IOException
230      *             Signals that an I/O exception has occurred.
231      */
232     @Test
233     void createCloverParser() throws IOException {
234         final var cloverDir = Files.createDirectory(this.targetDir.resolve("clover"));
235         Files.createFile(cloverDir.resolve("clover.xml"));
236         final var parsers = this.createCoverageParsersFactory().createParsers();
237         Assertions.assertEquals(1, parsers.size());
238         Assertions.assertEquals(CloverParser.class, parsers.get(0).getClass());
239     }
240 
241     /**
242      * Simulate the "jacocoAggregateReport" property being set on the mojo.
243      *
244      * @throws IOException
245      *             Signals that an I/O exception has occurred.
246      */
247     @Test
248     void withJacocoAggregateReportParam() throws IOException {
249         final var jacocoDir = Files
250                 .createDirectory(this.reportingDir.resolve(CoverageParsersFactory.DEFAULT_JACOCO_DIRECTORY));
251         Files.createFile(jacocoDir.resolve("jacoco.xml"));
252 
253         final var jacocoItDir = Files
254                 .createDirectory(this.reportingDir.resolve(CoverageParsersFactory.DEFAULT_JACOCO_IT_DIRECTORY));
255         Files.createFile(jacocoItDir.resolve("jacoco.xml"));
256 
257         final var jacocoAggregateDir = Files.createDirectory(this.reportingDir.resolve("my-aggregate-dir"));
258         final var jacocoAggregateReport = Files.createFile(jacocoAggregateDir.resolve("jacoco.xml"));
259 
260         final var factory = this.createCoverageParsersFactory()
261                 .withJacocoAggregateReport(jacocoAggregateReport.toFile());
262 
263         final var parsers = factory.createParsers();
264         Assertions.assertEquals(1, parsers.size());
265         Assertions.assertEquals(JaCoCoParser.class, parsers.get(0).getClass());
266         final var path = parsers.get(0).getCoverageFile().getPath().replace('\\', '/');
267         Assertions.assertTrue(path.contains("my-aggregate-dir/jacoco.xml"));
268     }
269 
270     /**
271      * Simulate the "jacocoReports" property being set on the mojo. This field adds reports to other that get detected,
272      * so by having the default "jacoco.xml" in place, there should be two parsers.
273      *
274      * @throws IOException
275      *             Signals that an I/O exception has occurred.
276      */
277     @Test
278     void withJacocoReportsParam() throws IOException {
279         final var jacocoDir = Files
280                 .createDirectory(this.reportingDir.resolve(CoverageParsersFactory.DEFAULT_JACOCO_DIRECTORY));
281         Files.createFile(jacocoDir.resolve("jacoco.xml"));
282 
283         final var customJacocoFile = Files.createFile(this.reportingDir.resolve("custom-jacoco-report.xml")).toFile();
284 
285         final var factory = this.createCoverageParsersFactory().withJaCoCoReports(Arrays.asList(customJacocoFile));
286 
287         final var parsers = factory.createParsers();
288         Assertions.assertEquals(2, parsers.size());
289         Assertions.assertTrue(parsers.stream().allMatch(p -> p.getClass() == JaCoCoParser.class));
290         Assertions.assertTrue(parsers.stream().map(CoverageParser::getCoverageFile).map(File::getPath)
291                 .anyMatch(p -> p.contains("jacoco.xml")));
292         Assertions.assertTrue(parsers.stream().map(CoverageParser::getCoverageFile).map(File::getPath)
293                 .anyMatch(p -> p.contains("custom-jacoco-report.xml")));
294     }
295 
296     /**
297      * With cobertura report.
298      *
299      * @throws IOException
300      *             Signals that an I/O exception has occurred.
301      */
302     @Test
303     void withCoberturaReport() throws IOException {
304         final var coberturaFile = Files.createFile(this.reportingDir.resolve("cobertura-report.xml")).toFile();
305         final var factory = this.createCoverageParsersFactory().withCoberturaReports(Arrays.asList(coberturaFile));
306         final var parsers = factory.createParsers();
307         Assertions.assertEquals(1, parsers.size());
308         Assertions.assertEquals(CoberturaParser.class, parsers.get(0).getClass());
309     }
310 
311     /**
312      * With saga report.
313      *
314      * @throws IOException
315      *             Signals that an I/O exception has occurred.
316      */
317     @Test
318     void withSagaReport() throws IOException {
319         final var sagaFile = Files.createFile(this.reportingDir.resolve("saga-report.xml")).toFile();
320         final var factory = this.createCoverageParsersFactory().withSagaReports(Arrays.asList(sagaFile));
321         final var parsers = factory.createParsers();
322         Assertions.assertEquals(1, parsers.size());
323         Assertions.assertEquals(SagaParser.class, parsers.get(0).getClass());
324     }
325 
326     /**
327      * With clover report.
328      *
329      * @throws IOException
330      *             Signals that an I/O exception has occurred.
331      */
332     @Test
333     void withCloverReport() throws IOException {
334         final var cloverFile = Files.createFile(this.reportingDir.resolve("clover-report.xml")).toFile();
335         final var factory = this.createCoverageParsersFactory().withCloverReports(Arrays.asList(cloverFile));
336         final var parsers = factory.createParsers();
337         Assertions.assertEquals(1, parsers.size());
338         Assertions.assertEquals(CloverParser.class, parsers.get(0).getClass());
339     }
340 
341     /**
342      * With relative report directory.
343      *
344      * @throws IOException
345      *             Signals that an I/O exception has occurred.
346      */
347     @Test
348     void withRelativeReportDirectory() throws IOException {
349         final var coberturaDir = Files.createDirectory(this.reportingDir.resolve("customdir"));
350         Files.createFile(coberturaDir.resolve("coverage.xml"));
351         final var factory = this.createCoverageParsersFactory().withRelativeReportDirs(Arrays.asList("customdir"));
352         final var parsers = factory.createParsers();
353         Assertions.assertEquals(1, parsers.size());
354         Assertions.assertEquals(CoberturaParser.class, parsers.get(0).getClass());
355     }
356 
357     /**
358      * With root relative report directory.
359      *
360      * @throws IOException
361      *             Signals that an I/O exception has occurred.
362      */
363     @Test
364     void withRootRelativeReportDirectory() throws IOException {
365         Files.createFile(this.reportingDir.resolve("coverage.xml")).toFile();
366         final var factory = this.createCoverageParsersFactory().withRelativeReportDirs(Arrays.asList(File.separator));
367         final var parsers = factory.createParsers();
368         Assertions.assertEquals(1, parsers.size());
369         Assertions.assertEquals(CoberturaParser.class, parsers.get(0).getClass());
370     }
371 
372     /**
373      * Creates the coverage parsers factory.
374      *
375      * @return the coverage parsers factory
376      */
377     private CoverageParsersFactory createCoverageParsersFactory() {
378         return new CoverageParsersFactory(this.projectMock, this.sourceLoaderMock);
379     }
380 
381 }