1
2
3
4
5 package com.qulice.maven;
6
7 import com.google.common.base.Joiner;
8 import com.qulice.spi.Environment;
9 import com.qulice.spi.ValidationException;
10 import java.io.File;
11 import java.nio.charset.StandardCharsets;
12 import java.nio.file.Files;
13 import java.nio.file.Path;
14 import java.util.Collections;
15 import java.util.HashSet;
16 import java.util.Set;
17 import java.util.jar.JarEntry;
18 import java.util.jar.JarOutputStream;
19 import org.apache.maven.artifact.Artifact;
20 import org.apache.maven.plugin.testing.stubs.ArtifactStub;
21 import org.apache.maven.shared.dependency.analyzer.ProjectDependencyAnalysis;
22 import org.apache.maven.shared.dependency.analyzer.ProjectDependencyAnalyzer;
23 import org.junit.jupiter.api.Assertions;
24 import org.junit.jupiter.api.Test;
25 import org.junit.jupiter.api.io.TempDir;
26
27
28
29
30
31 @SuppressWarnings("PMD.TooManyMethods")
32 final class DependenciesValidatorTest {
33
34
35
36
37 private static final String ROLE =
38 ProjectDependencyAnalyzer.class.getName();
39
40
41
42
43 private static final String HINT = "default";
44
45
46
47
48 private static final String SCOPE = "compile";
49
50
51
52
53 private static final String TYPE = "jar";
54
55
56
57
58
59 @Test
60 void passesIfNoDependencyProblemsFound() throws Exception {
61 Assertions.assertDoesNotThrow(
62 () -> new DependenciesValidator().validate(
63 DependenciesValidatorTest.envWith(new ProjectDependencyAnalysis())
64 )
65 );
66 }
67
68
69
70
71
72 @Test
73 void catchesDependencyProblemsAndThrowsException() throws Exception {
74 final ArtifactStub artifact = new ArtifactStub();
75 artifact.setGroupId("group");
76 artifact.setArtifactId("artifact");
77 artifact.setScope(DependenciesValidatorTest.SCOPE);
78 artifact.setVersion("2.3.4");
79 artifact.setType(DependenciesValidatorTest.TYPE);
80 final Set<Artifact> unused = new HashSet<>();
81 unused.add(artifact);
82 Assertions.assertThrows(
83 ValidationException.class,
84 () -> new DependenciesValidator().validate(
85 DependenciesValidatorTest.envWith(
86 new ProjectDependencyAnalysis(
87 Collections.emptySet(), unused, Collections.emptySet()
88 )
89 )
90 )
91 );
92 }
93
94
95
96
97
98 @Test
99 void ignoresRuntimeScope() throws Exception {
100 final ArtifactStub artifact = new ArtifactStub();
101 artifact.setGroupId("group");
102 artifact.setArtifactId("artifact");
103 artifact.setScope("runtime");
104 artifact.setVersion("2.3.4");
105 artifact.setType(DependenciesValidatorTest.TYPE);
106 final Set<Artifact> unused = new HashSet<>();
107 unused.add(artifact);
108 Assertions.assertDoesNotThrow(
109 () -> new DependenciesValidator().validate(
110 DependenciesValidatorTest.envWith(
111 new ProjectDependencyAnalysis(
112 Collections.emptySet(), Collections.emptySet(), unused
113 )
114 )
115 )
116 );
117 }
118
119
120
121
122
123 @Test
124 void excludesUsedUndeclaredDependencies() throws Exception {
125 final Set<Artifact> used = new HashSet<>();
126 final ArtifactStub artifact = new ArtifactStub();
127 artifact.setGroupId("group");
128 artifact.setArtifactId("artifact");
129 artifact.setScope(DependenciesValidatorTest.SCOPE);
130 artifact.setVersion("2.3.4");
131 artifact.setType(DependenciesValidatorTest.TYPE);
132 used.add(artifact);
133 Assertions.assertDoesNotThrow(
134 () -> new DependenciesValidator().validate(
135 new MavenEnvironment.Wrap(
136 new Environment.Mock().withExcludes(
137 Joiner.on(':').join(
138 artifact.getGroupId(), artifact.getArtifactId()
139 )
140 ),
141 DependenciesValidatorTest.envWith(
142 new ProjectDependencyAnalysis(
143 Collections.emptySet(), used, Collections.emptySet()
144 )
145 )
146 )
147 )
148 );
149 }
150
151
152
153
154
155 @Test
156 void excludesUnusedDeclaredDependencies() throws Exception {
157 final Set<Artifact> unused = new HashSet<>();
158 final ArtifactStub artifact = new ArtifactStub();
159 artifact.setGroupId("othergroup");
160 artifact.setArtifactId("otherartifact");
161 artifact.setScope(DependenciesValidatorTest.SCOPE);
162 artifact.setVersion("1.2.3");
163 artifact.setType(DependenciesValidatorTest.TYPE);
164 unused.add(artifact);
165 Assertions.assertDoesNotThrow(
166 () -> new DependenciesValidator().validate(
167 new MavenEnvironment.Wrap(
168 new Environment.Mock().withExcludes(
169 Joiner.on(':').join(
170 artifact.getGroupId(), artifact.getArtifactId()
171 )
172 ),
173 DependenciesValidatorTest.envWith(
174 new ProjectDependencyAnalysis(
175 Collections.emptySet(), Collections.emptySet(), unused
176 )
177 )
178 )
179 )
180 );
181 }
182
183
184
185
186
187
188
189
190
191 @Test
192 void treatsImportedDependencyAsUsed(@TempDir final Path dir)
193 throws Exception {
194 final Path src = DependenciesValidatorTest.sourceRoot(dir);
195 DependenciesValidatorTest.writeJava(
196 src, "com/example/Subject.java",
197 String.join(
198 String.valueOf('\n'),
199 "package com.example;",
200 "import com.fake.Marker;",
201 "@Marker",
202 "public class Subject {}",
203 ""
204 )
205 );
206 Assertions.assertDoesNotThrow(
207 () -> new DependenciesValidator().validate(
208 DependenciesValidatorTest.envWithUnused(
209 src,
210 DependenciesValidatorTest.jar(
211 dir, "fake.jar", "com/fake/Marker.class"
212 ),
213 "com.fake:fake"
214 )
215 )
216 );
217 }
218
219
220
221
222
223
224
225 @Test
226 void treatsStaticImportAsUsage(@TempDir final Path dir) throws Exception {
227 final Path src = DependenciesValidatorTest.sourceRoot(dir);
228 DependenciesValidatorTest.writeJava(
229 src, "com/example/UsesConst.java",
230 String.join(
231 String.valueOf('\n'),
232 "package com.example;",
233 "import static com.consts.Constants.VALUE;",
234 "public class UsesConst { int x = VALUE; }",
235 ""
236 )
237 );
238 Assertions.assertDoesNotThrow(
239 () -> new DependenciesValidator().validate(
240 DependenciesValidatorTest.envWithUnused(
241 src,
242 DependenciesValidatorTest.jar(
243 dir, "consts.jar", "com/consts/Constants.class"
244 ),
245 "com.consts:consts"
246 )
247 )
248 );
249 }
250
251
252
253
254
255
256 @Test
257 void treatsWildcardImportAsUsage(@TempDir final Path dir) throws Exception {
258 final Path src = DependenciesValidatorTest.sourceRoot(dir);
259 DependenciesValidatorTest.writeJava(
260 src, "com/example/UsesWild.java",
261 String.join(
262 String.valueOf('\n'),
263 "package com.example;",
264 "import com.wild.*;",
265 "public class UsesWild {}",
266 ""
267 )
268 );
269 Assertions.assertDoesNotThrow(
270 () -> new DependenciesValidator().validate(
271 DependenciesValidatorTest.envWithUnused(
272 src,
273 DependenciesValidatorTest.jar(
274 dir, "wild.jar", "com/wild/Thing.class"
275 ),
276 "com.wild:wild"
277 )
278 )
279 );
280 }
281
282
283
284
285
286
287
288 @Test
289 void stillFailsWithoutMatchingImport(@TempDir final Path dir)
290 throws Exception {
291 final Path src = DependenciesValidatorTest.sourceRoot(dir);
292 DependenciesValidatorTest.writeJava(
293 src, "com/example/Other.java",
294 String.join(
295 String.valueOf('\n'),
296 "package com.example;",
297 "import java.util.List;",
298 "public class Other {}",
299 ""
300 )
301 );
302 Assertions.assertThrows(
303 ValidationException.class,
304 () -> new DependenciesValidator().validate(
305 DependenciesValidatorTest.envWithUnused(
306 src,
307 DependenciesValidatorTest.jar(
308 dir, "alone.jar", "com/alone/Class.class"
309 ),
310 "com.alone:alone"
311 )
312 ),
313 "a declared dependency that is neither referenced in bytecode nor imported in source must not pass validation"
314 );
315 }
316
317
318
319
320
321
322
323 private static MavenEnvironment envWith(
324 final ProjectDependencyAnalysis analysis
325 ) throws Exception {
326 return new MavenEnvironmentMocker().inPlexus(
327 DependenciesValidatorTest.ROLE,
328 DependenciesValidatorTest.HINT,
329 new FakeProjectDependencyAnalyzer(analysis)
330 ).mock();
331 }
332
333
334
335
336
337
338
339
340
341
342 private static MavenEnvironment envWithUnused(final Path src,
343 final File jar, final String coord) throws Exception {
344 final String[] parts = coord.split(":");
345 final ArtifactStub artifact = new ArtifactStub();
346 artifact.setGroupId(parts[0]);
347 artifact.setArtifactId(parts[1]);
348 artifact.setScope(DependenciesValidatorTest.SCOPE);
349 artifact.setVersion("1.0.0");
350 artifact.setType(DependenciesValidatorTest.TYPE);
351 artifact.setFile(jar);
352 final Set<Artifact> unused = new HashSet<>();
353 unused.add(artifact);
354 final MavenEnvironment env = DependenciesValidatorTest.envWith(
355 new ProjectDependencyAnalysis(
356 Collections.emptySet(), Collections.emptySet(), unused
357 )
358 );
359 env.project().addCompileSourceRoot(src.toString());
360 return env;
361 }
362
363
364
365
366
367
368
369
370
371 private static File jar(final Path dir, final String name,
372 final String... entries) throws Exception {
373 final File jar = dir.resolve(name).toFile();
374 try (
375 JarOutputStream out = new JarOutputStream(
376 Files.newOutputStream(jar.toPath())
377 )
378 ) {
379 for (final String entry : entries) {
380 out.putNextEntry(new JarEntry(entry));
381 out.write(new byte[]{0});
382 out.closeEntry();
383 }
384 }
385 return jar;
386 }
387
388
389
390
391
392
393
394 private static Path sourceRoot(final Path dir) throws Exception {
395 final Path src = dir.resolve("src").resolve("main").resolve("java");
396 Files.createDirectories(src);
397 return src;
398 }
399
400
401
402
403
404
405
406
407 private static void writeJava(final Path src, final String path,
408 final String content) throws Exception {
409 final Path target = src.resolve(path);
410 Files.createDirectories(target.getParent());
411 Files.writeString(target, content, StandardCharsets.UTF_8);
412 }
413 }