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