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;
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.ArrayList;
32  import java.util.Arrays;
33  import java.util.List;
34  
35  import org.apache.maven.model.Build;
36  import org.apache.maven.model.Model;
37  import org.apache.maven.model.Reporting;
38  import org.apache.maven.plugin.MojoExecutionException;
39  import org.apache.maven.plugin.MojoFailureException;
40  import org.apache.maven.plugin.logging.Log;
41  import org.apache.maven.project.MavenProject;
42  import org.apache.maven.settings.Settings;
43  import org.eluder.coveralls.maven.plugin.domain.CoverallsResponse;
44  import org.eluder.coveralls.maven.plugin.domain.Git;
45  import org.eluder.coveralls.maven.plugin.domain.Job;
46  import org.eluder.coveralls.maven.plugin.domain.Source;
47  import org.eluder.coveralls.maven.plugin.httpclient.CoverallsClient;
48  import org.eluder.coveralls.maven.plugin.json.JsonWriter;
49  import org.eluder.coveralls.maven.plugin.parser.CoberturaParser;
50  import org.eluder.coveralls.maven.plugin.source.SourceLoader;
51  import org.eluder.coveralls.maven.plugin.util.TestIoUtil;
52  import org.eluder.coveralls.maven.plugin.validation.ValidationErrors;
53  import org.junit.jupiter.api.Assertions;
54  import org.junit.jupiter.api.BeforeEach;
55  import org.junit.jupiter.api.Test;
56  import org.junit.jupiter.api.extension.ExtendWith;
57  import org.junit.jupiter.api.io.CleanupMode;
58  import org.junit.jupiter.api.io.TempDir;
59  import org.mockito.ArgumentMatchers;
60  import org.mockito.Mock;
61  import org.mockito.Mockito;
62  import org.mockito.junit.jupiter.MockitoExtension;
63  
64  /**
65   * The Class CoverallsReportMojoTest.
66   */
67  @ExtendWith(MockitoExtension.class)
68  class CoverallsReportMojoTest {
69  
70      /** The folder. */
71      @TempDir(cleanup = CleanupMode.ON_SUCCESS)
72      Path folder;
73  
74      /** The coveralls file. */
75      File coverallsFile;
76  
77      /** The mojo. */
78      CoverallsReportMojo mojo;
79  
80      /** The coveralls client mock. */
81      @Mock
82      CoverallsClient coverallsClientMock;
83  
84      /** The source loader mock. */
85      @Mock
86      SourceLoader sourceLoaderMock;
87  
88      /** The job mock. */
89      @Mock
90      Job jobMock;
91  
92      /** The log mock. */
93      @Mock
94      Log logMock;
95  
96      /** The project mock. */
97      @Mock
98      MavenProject projectMock;
99  
100     /** The collected project mock. */
101     @Mock
102     MavenProject collectedProjectMock;
103 
104     /** The model mock. */
105     @Mock
106     Model modelMock;
107 
108     /** The reporting mock. */
109     @Mock
110     Reporting reportingMock;
111 
112     /** The build mock. */
113     @Mock
114     Build buildMock;
115 
116     /** The settings mock. */
117     @Mock
118     Settings settingsMock;
119 
120     /**
121      * Inits the Coverage Report Mojo.
122      *
123      * @throws IOException
124      *             Signals that an I/O exception has occurred.
125      */
126     @BeforeEach
127     void init() throws IOException {
128         this.coverallsFile = Files.createFile(this.folder.resolve("coverallsFile.json")).toFile();
129 
130         Mockito.lenient().when(this.sourceLoaderMock.load(ArgumentMatchers.anyString())).then(invocation -> {
131             final var sourceFile = invocation.getArguments()[0].toString();
132             final var content = this.readFileContent(sourceFile);
133             return new Source(sourceFile, content, TestIoUtil.getSha512DigestHex(content));
134         });
135         Mockito.lenient().when(this.logMock.isInfoEnabled()).thenReturn(true);
136         Mockito.lenient().when(this.jobMock.validate()).thenReturn(new ValidationErrors());
137 
138         this.mojo = new CoverallsReportMojo() {
139             @Override
140             protected SourceLoader createSourceLoader(final Job job) {
141                 return CoverallsReportMojoTest.this.sourceLoaderMock;
142             }
143 
144             @Override
145             protected List<CoverageParser> createCoverageParsers(SourceLoader sourceLoader) {
146                 final List<CoverageParser> parsers = new ArrayList<>();
147                 parsers.add(new CoberturaParser(TestIoUtil.getFile("cobertura.xml"), sourceLoader));
148                 return parsers;
149             }
150 
151             @Override
152             protected Environment createEnvironment() {
153                 return new Environment(this, List.of());
154             }
155 
156             @Override
157             protected Job createJob() {
158                 return CoverallsReportMojoTest.this.jobMock;
159             }
160 
161             @Override
162             protected JsonWriter createJsonWriter(final Job job) throws IOException {
163                 return new JsonWriter(CoverallsReportMojoTest.this.jobMock, CoverallsReportMojoTest.this.coverallsFile);
164             }
165 
166             @Override
167             protected CoverallsClient createCoverallsClient() {
168                 return CoverallsReportMojoTest.this.coverallsClientMock;
169             }
170 
171             @Override
172             public Log getLog() {
173                 return CoverallsReportMojoTest.this.logMock;
174             }
175         };
176         this.mojo.settings = this.settingsMock;
177         this.mojo.project = this.projectMock;
178         this.mojo.sourceEncoding = "UTF-8";
179         this.mojo.failOnServiceError = true;
180 
181         Mockito.lenient().when(this.modelMock.getReporting()).thenReturn(this.reportingMock);
182         Mockito.lenient().when(this.reportingMock.getOutputDirectory())
183                 .thenReturn(this.folder.toFile().getAbsolutePath());
184         Mockito.lenient().when(this.buildMock.getDirectory()).thenReturn(this.folder.toFile().getAbsolutePath());
185 
186         final List<MavenProject> projects = new ArrayList<>();
187         projects.add(this.collectedProjectMock);
188         Mockito.lenient().when(this.projectMock.getCollectedProjects()).thenReturn(projects);
189         Mockito.lenient().when(this.projectMock.getBuild()).thenReturn(this.buildMock);
190         Mockito.lenient().when(this.projectMock.getModel()).thenReturn(this.modelMock);
191         final List<String> sourceRoots = new ArrayList<>();
192         sourceRoots.add(this.folder.toFile().getAbsolutePath());
193         Mockito.lenient().when(this.collectedProjectMock.getCompileSourceRoots()).thenReturn(sourceRoots);
194         Mockito.lenient().when(this.collectedProjectMock.getBuild()).thenReturn(this.buildMock);
195         Mockito.lenient().when(this.collectedProjectMock.getModel()).thenReturn(this.modelMock);
196     }
197 
198     /**
199      * Creates the coverage parsers without coverage reports.
200      */
201     @Test
202     void createCoverageParsersWithoutCoverageReports() {
203         this.mojo = new CoverallsReportMojo();
204         this.mojo.settings = this.settingsMock;
205         this.mojo.project = this.projectMock;
206         Assertions.assertThrows(IOException.class, () -> this.mojo.createCoverageParsers(this.sourceLoaderMock));
207     }
208 
209     /**
210      * Test create source loader.
211      *
212      * @throws IOException
213      *             Signals that an I/O exception has occurred.
214      */
215     @Test
216     void createSourceLoader() throws IOException {
217         final var gitMock = Mockito.mock(Git.class);
218         final var git = Files.createDirectory(this.folder.resolve("git"));
219         Mockito.when(gitMock.getBaseDir()).thenReturn(git.toFile());
220         Mockito.when(this.jobMock.getGit()).thenReturn(gitMock);
221         TestIoUtil.writeFileContent("public interface Test { }", Files.createFile(git.resolve("source.java")).toFile());
222         this.mojo = new CoverallsReportMojo();
223         this.mojo.settings = this.settingsMock;
224         this.mojo.project = this.projectMock;
225         this.mojo.sourceEncoding = "UTF-8";
226         final var sourceLoader = this.mojo.createSourceLoader(this.jobMock);
227         final var source = sourceLoader.load("git/source.java");
228         Assertions.assertNotNull(source);
229     }
230 
231     /**
232      * Default behavior.
233      *
234      * @throws IOException
235      *             Signals that an I/O exception has occurred.
236      * @throws MojoExecutionException
237      *             the mojo execution exception
238      * @throws MojoFailureException
239      *             the mojo failure exception
240      */
241     @Test
242     void defaultBehavior() throws IOException, MojoExecutionException, MojoFailureException {
243         this.mojo = new CoverallsReportMojo() {
244             @Override
245             protected SourceLoader createSourceLoader(final Job job) {
246                 return CoverallsReportMojoTest.this.sourceLoaderMock;
247             }
248 
249             @Override
250             protected List<CoverageParser> createCoverageParsers(SourceLoader sourceLoader) throws IOException {
251                 return List.of();
252             }
253         };
254         this.mojo.sourceDirectories = Arrays.asList(TestIoUtil.getFile("/"));
255         this.mojo.sourceEncoding = "UTF-8";
256         this.mojo.settings = this.settingsMock;
257         this.mojo.project = this.projectMock;
258         this.mojo.repoToken = "asdfg";
259         this.mojo.coverallsFile = Files.createFile(this.folder.resolve("mojoCoverallsFile")).toFile();
260         this.mojo.dryRun = true;
261         this.mojo.skip = false;
262         this.mojo.basedir = TestIoUtil.getFile("/");
263 
264         Assertions.assertDoesNotThrow(() -> this.mojo.execute());
265     }
266 
267     /**
268      * Successful submission.
269      *
270      * @throws ProcessingException
271      *             the processing exception
272      * @throws IOException
273      *             Signals that an I/O exception has occurred.
274      * @throws MojoExecutionException
275      *             the mojo execution exception
276      * @throws MojoFailureException
277      *             the mojo failure exception
278      * @throws InterruptedException
279      *             the interrupted exception
280      */
281     @Test
282     void successfulSubmission() throws ProcessingException, IOException, MojoExecutionException, MojoFailureException,
283             InterruptedException {
284         Mockito.when(this.coverallsClientMock.submit(ArgumentMatchers.any(File.class)))
285                 .thenReturn(new CoverallsResponse("success", false, null));
286         this.mojo.execute();
287         final var json = TestIoUtil.readFileContent(this.coverallsFile);
288         Assertions.assertNotNull(json);
289 
290         final var fixture = CoverageFixture.JAVA_FILES;
291         for (final List<String> coverageFile : fixture) {
292             Assertions.assertTrue(json.contains(coverageFile.get(0)));
293         }
294 
295         CoverallsReportMojoTest.verifySuccessfulSubmit(this.logMock, fixture);
296     }
297 
298     /**
299      * Fail with processing exception.
300      *
301      * @throws ProcessingException
302      *             the processing exception
303      * @throws IOException
304      *             Signals that an I/O exception has occurred.
305      * @throws MojoExecutionException
306      *             the mojo execution exception
307      * @throws InterruptedException
308      *             the interrupted exception
309      */
310     @Test
311     void failWithProcessingException()
312             throws ProcessingException, IOException, MojoExecutionException, InterruptedException {
313         Mockito.when(this.coverallsClientMock.submit(ArgumentMatchers.any(File.class)))
314                 .thenThrow(new ProcessingException());
315         try {
316             this.mojo.execute();
317             Assertions.fail("Should have failed with MojoFailureException");
318         } catch (final MojoFailureException e) {
319             Assertions.assertEquals(ProcessingException.class, e.getCause().getClass());
320         }
321     }
322 
323     /**
324      * Processing exception with allowed service failure.
325      *
326      * @throws ProcessingException
327      *             the processing exception
328      * @throws IOException
329      *             Signals that an I/O exception has occurred.
330      * @throws MojoExecutionException
331      *             the mojo execution exception
332      * @throws InterruptedException
333      *             the interrupted exception
334      */
335     @Test
336     void processingExceptionWithAllowedServiceFailure()
337             throws ProcessingException, IOException, MojoExecutionException, InterruptedException {
338         this.mojo.failOnServiceError = false;
339         Mockito.when(this.coverallsClientMock.submit(ArgumentMatchers.any(File.class)))
340                 .thenThrow(new ProcessingException());
341         try {
342             this.mojo.execute();
343             Assertions.fail("Should have failed with MojoFailureException");
344         } catch (final MojoFailureException e) {
345             Assertions.assertEquals(ProcessingException.class, e.getCause().getClass());
346         }
347     }
348 
349     /**
350      * Fail with IO exception.
351      *
352      * @throws ProcessingException
353      *             the processing exception
354      * @throws IOException
355      *             Signals that an I/O exception has occurred.
356      * @throws MojoExecutionException
357      *             the mojo execution exception
358      * @throws InterruptedException
359      *             the interrupted exception
360      */
361     @Test
362     void failWithIOException() throws ProcessingException, IOException, MojoExecutionException, InterruptedException {
363         Mockito.when(this.coverallsClientMock.submit(ArgumentMatchers.any(File.class))).thenThrow(new IOException());
364         try {
365             this.mojo.execute();
366             Assertions.fail("Should have failed with MojoFailureException");
367         } catch (final MojoFailureException ex) {
368             Assertions.assertEquals(IOException.class, ex.getCause().getClass());
369         }
370     }
371 
372     /**
373      * I O exception with allowed service failure.
374      *
375      * @throws ProcessingException
376      *             the processing exception
377      * @throws IOException
378      *             Signals that an I/O exception has occurred.
379      * @throws MojoExecutionException
380      *             the mojo execution exception
381      * @throws MojoFailureException
382      *             the mojo failure exception
383      * @throws InterruptedException
384      *             the interrupted exception
385      */
386     @Test
387     void ioExceptionWithAllowedServiceFailure() throws ProcessingException, IOException, MojoExecutionException,
388             MojoFailureException, InterruptedException {
389         this.mojo.failOnServiceError = false;
390         Mockito.when(this.coverallsClientMock.submit(ArgumentMatchers.any(File.class))).thenThrow(new IOException());
391         this.mojo.execute();
392         Mockito.verify(this.logMock).warn(ArgumentMatchers.anyString());
393     }
394 
395     /**
396      * Fail with null pointer exception.
397      *
398      * @throws ProcessingException
399      *             the processing exception
400      * @throws IOException
401      *             Signals that an I/O exception has occurred.
402      * @throws MojoFailureException
403      *             the mojo failure exception
404      * @throws InterruptedException
405      *             the interrupted exception
406      */
407     @Test
408     void failWithNullPointerException()
409             throws ProcessingException, IOException, MojoFailureException, InterruptedException {
410         Mockito.when(this.coverallsClientMock.submit(ArgumentMatchers.any(File.class)))
411                 .thenThrow(new NullPointerException());
412         try {
413             this.mojo.execute();
414             Assertions.fail("Should have failed with MojoFailureException");
415         } catch (final MojoExecutionException e) {
416             Assertions.assertEquals(NullPointerException.class, e.getCause().getClass());
417         }
418     }
419 
420     /**
421      * Skip execution.
422      *
423      * @throws MojoExecutionException
424      *             the mojo execution exception
425      * @throws MojoFailureException
426      *             the mojo failure exception
427      */
428     @Test
429     void skipExecution() throws MojoExecutionException, MojoFailureException {
430         this.mojo.skip = true;
431         this.mojo.execute();
432 
433         Mockito.verifyNoInteractions(this.jobMock);
434     }
435 
436     /**
437      * Verify successful submit.
438      *
439      * @param logMock
440      *            the log mock
441      * @param fixture
442      *            the fixture
443      */
444     static void verifySuccessfulSubmit(final Log logMock, final List<List<String>> fixture) {
445         Mockito.verify(logMock, Mockito.times(1))
446                 .info("Gathered code coverage metrics for " + CoverageFixture.getTotalFiles(fixture)
447                         + " source files with " + CoverageFixture.getTotalLines(fixture) + " lines of code:");
448         Mockito.verify(logMock, Mockito.times(1))
449                 .info("*** Coverage results are usually available immediately on Coveralls.");
450     }
451 
452     /**
453      * Read file content.
454      *
455      * @param sourceFile
456      *            the source file
457      *
458      * @return the string
459      *
460      * @throws IOException
461      *             Signals that an I/O exception has occurred.
462      */
463     String readFileContent(final String sourceFile) throws IOException {
464         return TestIoUtil.readFileContent(TestIoUtil.getFile(sourceFile));
465     }
466 
467 }