View Javadoc
1   /*
2    * SPDX-FileCopyrightText: Copyright (c) 2011-2026 Yegor Bugayenko
3    * SPDX-License-Identifier: MIT
4    */
5   package com.qulice.maven;
6   
7   import com.google.common.collect.ImmutableList;
8   import java.io.File;
9   import java.nio.charset.StandardCharsets;
10  import java.nio.file.Files;
11  import java.nio.file.Path;
12  import java.util.Collections;
13  import org.apache.maven.model.Build;
14  import org.apache.maven.model.Resource;
15  import org.apache.maven.plugin.testing.stubs.MavenProjectStub;
16  import org.hamcrest.MatcherAssert;
17  import org.hamcrest.Matchers;
18  import org.junit.jupiter.api.Test;
19  import org.junit.jupiter.api.io.TempDir;
20  
21  /**
22   * Test case for {@link DefaultMavenEnvironment} class.
23   *
24   * @since 0.8
25   */
26  final class DefaultMavenEnvironmentTest {
27  
28      /**
29       * DefaultMavenEnvironment can produce list of excludes.
30       */
31      @Test
32      void excludeAllFiles() {
33          final DefaultMavenEnvironment env = new DefaultMavenEnvironment();
34          env.setExcludes(Collections.singletonList("codenarc:**/*.groovy"));
35          MatcherAssert.assertThat(
36              "Excludes should be returned",
37              env.excludes("codenarc"),
38              Matchers.contains("**/*.groovy")
39          );
40      }
41  
42      /**
43       * DefaultMavenEnvironment can produce list of excludes from empty source.
44       */
45      @Test
46      void emptyExclude() {
47          final DefaultMavenEnvironment env = new DefaultMavenEnvironment();
48          env.setExcludes(Collections.<String>emptyList());
49          MatcherAssert.assertThat(
50              "Empty list should be returned",
51              env.excludes("codenarc").iterator().hasNext(),
52              Matchers.is(false)
53          );
54      }
55  
56      /**
57       * DefaultMavenEnvironment can produce list of excludes without excludes.
58       */
59      @Test
60      void noExclude() {
61          MatcherAssert.assertThat(
62              "Excludes should be empty list by default",
63              new DefaultMavenEnvironment().excludes("codenarc")
64                  .iterator().hasNext(),
65              Matchers.is(false)
66          );
67      }
68  
69      /**
70       * DefaultMavenEnvironment can produce list of excludes.
71       */
72      @Test
73      void excludeSomeFiles() {
74          final DefaultMavenEnvironment env = new DefaultMavenEnvironment();
75          env.setExcludes(
76              ImmutableList.<String>builder()
77                  .add("codenarc:**/src/ex1/Main.groovy")
78                  .add("codenarc:**/src/ex2/Main2.groovy")
79                  .build()
80          );
81          MatcherAssert.assertThat(
82              "Excludes should be returned as list",
83              env.excludes("codenarc"),
84              Matchers.containsInAnyOrder(
85                  "**/src/ex1/Main.groovy",
86                  "**/src/ex2/Main2.groovy"
87              )
88          );
89      }
90  
91      /**
92       * DefaultMavenEnvironment can work with whitespaces in classpath.
93       *
94       * @throws Exception If something wrong happens inside
95       */
96      @Test
97      void passPathsWithWhitespaces() throws Exception {
98          final DefaultMavenEnvironment env = new DefaultMavenEnvironment();
99          final MavenProjectStub project = new MavenProjectStub();
100         project.setTestClasspathElements(
101             Collections.singletonList("/Users/Carlos Miranda/git")
102         );
103         env.setProject(project);
104         MatcherAssert.assertThat(
105             "ClassPath should be returned",
106             env.classloader(),
107             Matchers.notNullValue()
108         );
109     }
110 
111     /**
112      * DefaultMavenEnvironment.classpath() should return the fully-resolved
113      * test classpath (compile + runtime + test scope, transitives included)
114      * so that ErrorProne, which compiles test sources too, can access
115      * transitive test-scope dependencies such as {@code opentest4j}
116      * (see <a href="https://github.com/yegor256/qulice/issues/1691">
117      * issue #1691</a>).
118      *
119      * @throws Exception If something wrong happens inside
120      */
121     @Test
122     void classpathIncludesTransitiveTestDependencies() throws Exception {
123         final DefaultMavenEnvironment env = new DefaultMavenEnvironment();
124         final MavenProjectStub project = new MavenProjectStub();
125         project.setTestClasspathElements(
126             ImmutableList.of(
127                 "/repo/junit-jupiter-api.jar",
128                 "/repo/opentest4j.jar",
129                 "/repo/apiguardian-api.jar"
130             )
131         );
132         env.setProject(project);
133         MatcherAssert.assertThat(
134             "Transitive test dependencies must be on the classpath",
135             env.classpath(),
136             Matchers.containsInAnyOrder(
137                 "/repo/junit-jupiter-api.jar",
138                 "/repo/opentest4j.jar",
139                 "/repo/apiguardian-api.jar"
140             )
141         );
142     }
143 
144     /**
145      * DefaultMavenEnvironment can produce empty collection when no matches
146      * with checker.
147      */
148     @Test
149     void producesEmptyExcludesWhenNoMatches() {
150         final DefaultMavenEnvironment env = new DefaultMavenEnvironment();
151         env.setExcludes(
152             ImmutableList.of(
153                 "checkstyle:**/src/ex1/Main.groovy",
154                 "pmd:**/src/ex2/Main2.groovy"
155             )
156         );
157         MatcherAssert.assertThat(
158             "Exclude dependencies should be empty",
159             env.excludes("dependencies"),
160             Matchers.empty()
161         );
162     }
163 
164     /**
165      * DefaultMavenEnvironment.files() should silently drop binary files so
166      * that validators never try to read them as text
167      * (see <a href="https://github.com/yegor256/qulice/issues/1264">
168      * issue #1264</a>).
169      *
170      * @param basedir Temporary base directory
171      * @throws Exception If something wrong happens inside
172      */
173     @Test
174     void skipsBinaryFilesWhenListing(@TempDir final Path basedir)
175         throws Exception {
176         final Path src = basedir.resolve("src/main/java");
177         Files.createDirectories(src);
178         final Path source = src.resolve("Foo.java");
179         Files.writeString(
180             source,
181             "class Foo {}".concat(String.valueOf('\n')),
182             StandardCharsets.UTF_8
183         );
184         final Path image = basedir.resolve("src/main/resources/pixel.png");
185         Files.createDirectories(image.getParent());
186         Files.write(
187             image,
188             new byte[] {
189                 (byte) 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A,
190                 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52,
191             }
192         );
193         final DefaultMavenEnvironment env = new DefaultMavenEnvironment();
194         env.setProject(
195             new MavenProjectStub() {
196                 @Override
197                 public File getBasedir() {
198                     return basedir.toFile();
199                 }
200             }
201         );
202         MatcherAssert.assertThat(
203             "Binary files cannot leak into the list of files to validate",
204             env.files("*.*"),
205             Matchers.allOf(
206                 Matchers.hasItem(source.toFile()),
207                 Matchers.not(Matchers.hasItem(image.toFile()))
208             )
209         );
210     }
211 
212     /**
213      * DefaultMavenEnvironment.files() should honor a non-default
214      * {@code <sourceDirectory>} declared in the POM
215      * (see <a href="https://github.com/yegor256/qulice/issues/382">
216      * issue #382</a>).
217      *
218      * @param basedir Temporary base directory
219      * @throws Exception If something wrong happens inside
220      */
221     @Test
222     void respectsCustomSourceDirectory(@TempDir final Path basedir)
223         throws Exception {
224         final Path sources = basedir.resolve("sources");
225         Files.createDirectories(sources);
226         final Path source = sources.resolve("Foo.java");
227         Files.writeString(
228             source,
229             "class Foo {}".concat(String.valueOf('\n')),
230             StandardCharsets.UTF_8
231         );
232         final DefaultMavenEnvironment env = new DefaultMavenEnvironment();
233         final MavenProjectStub project = new MavenProjectStub() {
234             @Override
235             public File getBasedir() {
236                 return basedir.toFile();
237             }
238         };
239         project.addCompileSourceRoot(sources.toAbsolutePath().toString());
240         env.setProject(project);
241         MatcherAssert.assertThat(
242             "Files under custom sourceDirectory should be found",
243             env.files("*.*"),
244             Matchers.hasItem(source.toFile())
245         );
246     }
247 
248     /**
249      * DefaultMavenEnvironment.files() should honor a non-default
250      * {@code <testSourceDirectory>} declared in the POM
251      * (see <a href="https://github.com/yegor256/qulice/issues/382">
252      * issue #382</a>).
253      *
254      * @param basedir Temporary base directory
255      * @throws Exception If something wrong happens inside
256      */
257     @Test
258     void respectsCustomTestSourceDirectory(@TempDir final Path basedir)
259         throws Exception {
260         final Path tests = basedir.resolve("tests");
261         Files.createDirectories(tests);
262         final Path test = tests.resolve("FooTest.java");
263         Files.writeString(
264             test,
265             "class FooTest {}".concat(String.valueOf('\n')),
266             StandardCharsets.UTF_8
267         );
268         final DefaultMavenEnvironment env = new DefaultMavenEnvironment();
269         final MavenProjectStub project = new MavenProjectStub() {
270             @Override
271             public File getBasedir() {
272                 return basedir.toFile();
273             }
274         };
275         project.addTestCompileSourceRoot(tests.toAbsolutePath().toString());
276         env.setProject(project);
277         MatcherAssert.assertThat(
278             "Files under custom testSourceDirectory should be found",
279             env.files("*.*"),
280             Matchers.hasItem(test.toFile())
281         );
282     }
283 
284     /**
285      * DefaultMavenEnvironment.files() should honor directories declared in
286      * {@code <resources>} blocks of the POM
287      * (see <a href="https://github.com/yegor256/qulice/issues/382">
288      * issue #382</a>).
289      *
290      * @param basedir Temporary base directory
291      * @throws Exception If something wrong happens inside
292      */
293     @Test
294     void respectsCustomResourcesDirectory(@TempDir final Path basedir)
295         throws Exception {
296         final Path assets = basedir.resolve("assets");
297         Files.createDirectories(assets);
298         final Path asset = assets.resolve("config.xml");
299         Files.writeString(
300             asset,
301             "<config/>".concat(String.valueOf('\n')),
302             StandardCharsets.UTF_8
303         );
304         final Build build = new Build();
305         final Resource resource = new Resource();
306         resource.setDirectory(assets.toAbsolutePath().toString());
307         build.addResource(resource);
308         final DefaultMavenEnvironment env = new DefaultMavenEnvironment();
309         env.setProject(
310             new MavenProjectStub() {
311                 @Override
312                 public File getBasedir() {
313                     return basedir.toFile();
314                 }
315 
316                 @Override
317                 public Build getBuild() {
318                     return build;
319                 }
320             }
321         );
322         MatcherAssert.assertThat(
323             "Files under declared resources directory should be found",
324             env.files("*.*"),
325             Matchers.hasItem(asset.toFile())
326         );
327     }
328 
329     /**
330      * DefaultMavenEnvironment.testdirs() should report every test source
331      * root the project declares, not just {@code src/test/java}, since
332      * {@code build-helper-maven-plugin:add-test-source} may add more of
333      * them, e.g. {@code src/mock/java}
334      * (see <a href="https://github.com/yegor256/qulice/issues/1742">
335      * issue #1742</a>).
336      *
337      * @param basedir Temporary base directory
338      */
339     @Test
340     void reportsEveryTestSourceRoot(@TempDir final Path basedir) {
341         final Path tests = basedir.resolve("src/test/java");
342         final Path mocks = basedir.resolve("src/mock/java");
343         final DefaultMavenEnvironment env = new DefaultMavenEnvironment();
344         final MavenProjectStub project = new MavenProjectStub() {
345             @Override
346             public File getBasedir() {
347                 return basedir.toFile();
348             }
349         };
350         project.addTestCompileSourceRoot(
351             tests.toAbsolutePath().toString()
352         );
353         project.addTestCompileSourceRoot(
354             mocks.toAbsolutePath().toString()
355         );
356         env.setProject(project);
357         MatcherAssert.assertThat(
358             "Every declared test source root must be reported",
359             env.testdirs(),
360             Matchers.hasItems(tests.toFile(), mocks.toFile())
361         );
362     }
363 
364     /**
365      * Default source files encoding should be UFT-8.
366      */
367     @Test
368     void defaultEncodingIsUtf() {
369         MatcherAssert.assertThat(
370             "Default encoding should be UTF-8",
371             new DefaultMavenEnvironment().encoding(),
372             Matchers.is(StandardCharsets.UTF_8)
373         );
374     }
375 
376     /**
377      * DefaultMavenEnvironment.files() should ignore source roots that live
378      * under the project's build directory, since those are generated build
379      * outputs (e.g. target/generated-sources/...) and not user-authored code
380      * (see <a href="https://github.com/yegor256/qulice/issues/1560">
381      * issue #1560</a>).
382      *
383      * @param basedir Temporary base directory
384      * @throws Exception If something wrong happens inside
385      */
386     @Test
387     void skipsSourcesUnderBuildDirectory(@TempDir final Path basedir)
388         throws Exception {
389         final Path src = basedir.resolve("src/main/java");
390         Files.createDirectories(src);
391         final Path source = src.resolve("Foo.java");
392         Files.writeString(
393             source,
394             "class Foo {}".concat(String.valueOf('\n')),
395             StandardCharsets.UTF_8
396         );
397         final Path generated = basedir.resolve(
398             "target/generated-sources/plugin/com/example"
399         );
400         Files.createDirectories(generated);
401         final Path help = generated.resolve("HelpMojo.java");
402         Files.writeString(
403             help,
404             "class HelpMojo {}".concat(String.valueOf('\n')),
405             StandardCharsets.UTF_8
406         );
407         final Build build = new Build();
408         build.setDirectory(
409             basedir.resolve("target").toAbsolutePath().toString()
410         );
411         final DefaultMavenEnvironment env = new DefaultMavenEnvironment();
412         final MavenProjectStub project = new MavenProjectStub() {
413             @Override
414             public File getBasedir() {
415                 return basedir.toFile();
416             }
417 
418             @Override
419             public Build getBuild() {
420                 return build;
421             }
422         };
423         project.addCompileSourceRoot(src.toAbsolutePath().toString());
424         project.addCompileSourceRoot(
425             generated.toAbsolutePath().toString()
426         );
427         env.setProject(project);
428         MatcherAssert.assertThat(
429             "Generated sources under target/ must not be returned",
430             env.files("*.java"),
431             Matchers.allOf(
432                 Matchers.hasItem(source.toFile()),
433                 Matchers.not(Matchers.hasItem(help.toFile()))
434             )
435         );
436     }
437 
438     /**
439      * DefaultMavenEnvironment.tempdir() must not point at the directory
440      * with compiled classes, otherwise validators drop their scratch files
441      * into {@code target/classes} and they end up in the JAR and confuse
442      * {@code jacoco:report}
443      * (see <a href="https://github.com/yegor256/qulice/issues/1741">
444      * issue #1741</a>).
445      *
446      * @param basedir Temporary base directory
447      */
448     @Test
449     void keepsTempdirOutsideOutdir(@TempDir final Path basedir) {
450         final Path target = basedir.resolve("target");
451         final Build build = new Build();
452         build.setDirectory(target.toAbsolutePath().toString());
453         build.setOutputDirectory(
454             target.resolve("classes").toAbsolutePath().toString()
455         );
456         final DefaultMavenEnvironment env = new DefaultMavenEnvironment();
457         env.setProject(
458             new MavenProjectStub() {
459                 @Override
460                 public File getBasedir() {
461                     return basedir.toFile();
462                 }
463 
464                 @Override
465                 public Build getBuild() {
466                     return build;
467                 }
468             }
469         );
470         final Path temp = env.tempdir().toPath().toAbsolutePath();
471         MatcherAssert.assertThat(
472             "Scratch dir must be usable, under target/ and away from classes",
473             env.tempdir().isDirectory()
474                 && temp.startsWith(target.toAbsolutePath())
475                 && !temp.startsWith(env.outdir().toPath().toAbsolutePath()),
476             Matchers.is(true)
477         );
478     }
479 }