1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package com.hazendaz.maven.makeself;
19
20 import java.lang.reflect.Field;
21 import java.nio.file.Files;
22 import java.nio.file.Path;
23 import java.util.Arrays;
24 import java.util.List;
25
26 import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
27 import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
28 import org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream;
29 import org.apache.maven.plugin.MojoFailureException;
30 import org.apache.maven.plugin.logging.Log;
31 import org.eclipse.aether.RepositorySystem;
32 import org.eclipse.aether.RepositorySystemSession;
33 import org.eclipse.aether.artifact.Artifact;
34 import org.eclipse.aether.repository.LocalRepository;
35 import org.eclipse.aether.resolution.ArtifactResolutionException;
36 import org.eclipse.aether.resolution.ArtifactResult;
37 import org.junit.jupiter.api.Assertions;
38 import org.junit.jupiter.api.Assumptions;
39 import org.junit.jupiter.api.Test;
40 import org.junit.jupiter.api.extension.ExtendWith;
41 import org.junit.jupiter.api.io.TempDir;
42 import org.mockito.Mock;
43 import org.mockito.Mockito;
44 import org.mockito.junit.jupiter.MockitoExtension;
45
46
47
48
49 @ExtendWith(MockitoExtension.class)
50 class AbstractGitMojoTest {
51
52
53 @TempDir
54 Path tempDir;
55
56
57 @Mock
58 private Log log;
59
60
61 @Mock
62 private RepositorySystemSession repoSession;
63
64
65 @Mock
66 private RepositorySystem repositorySystem;
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81 private static void setField(final Object obj, final String fieldName, final Object value) throws Exception {
82 final Field field = findField(obj.getClass(), fieldName);
83 field.setAccessible(true);
84 field.set(obj, value);
85 }
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100 @SuppressWarnings("unchecked")
101 private static <T> T getField(final Object obj, final String fieldName) throws Exception {
102 final Field field = findField(obj.getClass(), fieldName);
103 field.setAccessible(true);
104 return (T) field.get(obj);
105 }
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120 private static Field findField(final Class<?> clazz, final String name) throws NoSuchFieldException {
121 try {
122 return clazz.getDeclaredField(name);
123 } catch (final NoSuchFieldException e) {
124 if (clazz.getSuperclass() != null) {
125 return findField(clazz.getSuperclass(), name);
126 }
127 throw e;
128 }
129 }
130
131
132
133
134
135
136
137 @Test
138 void testRunInstallerSuccess() throws Exception {
139 final GitMojo mojo = new GitMojo();
140 mojo.setLog(log);
141
142 final List<String> command = AbstractGitMojo.WINDOWS ? Arrays.asList("cmd", "/c", "echo", "hello")
143 : Arrays.asList("echo", "hello");
144
145 mojo.runInstaller(command);
146
147
148 Mockito.verify(log).info("");
149 }
150
151
152
153
154
155
156
157 @Test
158 void testRunInstallerFailedStatus() throws Exception {
159 Assumptions.assumeFalse(AbstractGitMojo.WINDOWS, "Non-zero exit test only applicable on non-Windows");
160
161 final GitMojo mojo = new GitMojo();
162 mojo.setLog(log);
163
164 mojo.runInstaller(Arrays.asList("bash", "-c", "exit 1"));
165
166 Mockito.verify(log).error(Mockito.contains("Process failed with error status:"));
167 }
168
169
170
171
172
173
174
175
176 @Test
177 void testExtractPortableGitExistingLocation() throws Exception {
178 final GitMojo mojo = new GitMojo();
179 mojo.setLog(log);
180
181 final PortableGit portableGit = new PortableGit(log);
182 setField(mojo, "portableGit", portableGit);
183 setField(mojo, "remoteRepositories", List.of());
184
185
186 final Path existingLocation = tempDir.resolve(portableGit.getName()).resolve(portableGit.getVersion());
187 Files.createDirectories(existingLocation);
188
189 final LocalRepository localRepository = new LocalRepository(tempDir.toFile());
190 Mockito.when(repoSession.getLocalRepository()).thenReturn(localRepository);
191 setField(mojo, "repoSession", repoSession);
192
193 mojo.extractPortableGit();
194
195
196 final String gitPath = getField(mojo, "gitPath");
197 Assertions.assertTrue(gitPath.contains(portableGit.getName()), "gitPath should reference the PortableGit name");
198 }
199
200
201
202
203
204
205
206
207 @Test
208 void testExtractPortableGitResolutionException() throws Exception {
209 final GitMojo mojo = new GitMojo();
210 mojo.setLog(log);
211
212 final PortableGit portableGit = new PortableGit(log);
213 setField(mojo, "portableGit", portableGit);
214 setField(mojo, "remoteRepositories", List.of());
215
216
217 final LocalRepository localRepository = new LocalRepository(tempDir.toFile());
218 Mockito.when(repoSession.getLocalRepository()).thenReturn(localRepository);
219 setField(mojo, "repoSession", repoSession);
220
221 Mockito.when(repositorySystem.resolveArtifact(Mockito.any(), Mockito.any()))
222 .thenThrow(new ArtifactResolutionException(List.of(), "resolution failed in test"));
223 setField(mojo, "repositorySystem", repositorySystem);
224
225 Assertions.assertThrows(MojoFailureException.class, mojo::extractPortableGit);
226 }
227
228
229
230
231
232
233
234
235 @Test
236 void testExtractPortableGitNotResolved() throws Exception {
237 final GitMojo mojo = new GitMojo();
238 mojo.setLog(log);
239
240 final PortableGit portableGit = new PortableGit(log);
241 setField(mojo, "portableGit", portableGit);
242 setField(mojo, "remoteRepositories", List.of());
243
244
245 final LocalRepository localRepository = new LocalRepository(tempDir.toFile());
246 Mockito.when(repoSession.getLocalRepository()).thenReturn(localRepository);
247 setField(mojo, "repoSession", repoSession);
248
249 final ArtifactResult notResolved = Mockito.mock(ArtifactResult.class);
250 Mockito.when(notResolved.isResolved()).thenReturn(false);
251 Mockito.when(repositorySystem.resolveArtifact(Mockito.any(), Mockito.any())).thenReturn(notResolved);
252 setField(mojo, "repositorySystem", repositorySystem);
253
254 Assertions.assertThrows(MojoFailureException.class, mojo::extractPortableGit);
255 }
256
257
258
259
260
261
262
263
264 @Test
265 void testCheckGitSetupWithExistingLocation() throws Exception {
266 final GitMojo mojo = new GitMojo();
267 mojo.setLog(log);
268 setField(mojo, "remoteRepositories", List.of());
269
270
271 final PortableGit portableGit = new PortableGit(log);
272 final Path existingLocation = tempDir.resolve(portableGit.getName()).resolve(portableGit.getVersion());
273 Files.createDirectories(existingLocation);
274
275 final LocalRepository localRepository = new LocalRepository(tempDir.toFile());
276 Mockito.when(repoSession.getLocalRepository()).thenReturn(localRepository);
277 setField(mojo, "repoSession", repoSession);
278
279 mojo.checkGitSetup();
280
281 final String gitPath = getField(mojo, "gitPath");
282 Assertions.assertNotNull(gitPath, "gitPath should be set after checkGitSetup");
283 Assertions.assertFalse(gitPath.isEmpty(), "gitPath should not be empty after checkGitSetup");
284 }
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299 private static void createTarGz(final Path tarGzPath, final String entryName, final byte[] entryContent)
300 throws Exception {
301 try (java.io.OutputStream fos = Files.newOutputStream(tarGzPath);
302 GzipCompressorOutputStream gzos = new GzipCompressorOutputStream(fos);
303 TarArchiveOutputStream taos = new TarArchiveOutputStream(gzos)) {
304 taos.setLongFileMode(TarArchiveOutputStream.LONGFILE_GNU);
305
306
307 final String dirName = entryName.contains("/") ? entryName.substring(0, entryName.lastIndexOf('/') + 1)
308 : "";
309 if (!dirName.isEmpty()) {
310 final TarArchiveEntry dirEntry = new TarArchiveEntry(dirName);
311 taos.putArchiveEntry(dirEntry);
312 taos.closeArchiveEntry();
313 }
314
315
316 final TarArchiveEntry fileEntry = new TarArchiveEntry(entryName);
317 fileEntry.setSize(entryContent.length);
318 taos.putArchiveEntry(fileEntry);
319 taos.write(entryContent);
320 taos.closeArchiveEntry();
321 }
322 }
323
324
325
326
327
328
329
330
331
332 @Test
333 void testInstallGitExtractsFiles() throws Exception {
334 final GitMojo mojo = new GitMojo();
335 mojo.setLog(log);
336
337 final PortableGit portableGit = new PortableGit(log);
338 setField(mojo, "portableGit", portableGit);
339
340
341 final LocalRepository localRepository = new LocalRepository(tempDir.toFile());
342 Mockito.when(repoSession.getLocalRepository()).thenReturn(localRepository);
343 setField(mojo, "repoSession", repoSession);
344
345
346 final Path tarGzPath = tempDir.resolve("portable-git.tar.gz");
347 final byte[] scriptContent = "#!/bin/sh\nexit 0\n".getBytes(java.nio.charset.StandardCharsets.UTF_8);
348 createTarGz(tarGzPath, portableGit.getName() + "/install.sh", scriptContent);
349
350
351 final Artifact artifact = Mockito.mock(Artifact.class);
352 Mockito.when(artifact.getFile()).thenReturn(tarGzPath.toFile());
353
354 final String location = tempDir.toFile().getAbsolutePath() + java.io.File.separator + portableGit.getName()
355 + java.io.File.separator + portableGit.getVersion();
356
357
358 mojo.installGit(artifact, location);
359
360
361 final Path extractedDir = tempDir.resolve(portableGit.getName());
362 Assertions.assertTrue(Files.exists(extractedDir), "PortableGit extraction directory should have been created");
363 }
364
365
366
367
368
369
370
371 @Test
372 void testInstallGitHandlesBadTarGz() throws Exception {
373 final GitMojo mojo = new GitMojo();
374 mojo.setLog(log);
375
376 final PortableGit portableGit = new PortableGit(log);
377 setField(mojo, "portableGit", portableGit);
378 setField(mojo, "repoSession", repoSession);
379
380
381 final Path emptyFile = Files.createTempFile(tempDir, "empty", ".tar.gz");
382
383 final Artifact artifact = Mockito.mock(Artifact.class);
384 Mockito.when(artifact.getFile()).thenReturn(emptyFile.toFile());
385
386 final String location = tempDir.toFile().getAbsolutePath() + java.io.File.separator + portableGit.getName()
387 + java.io.File.separator + portableGit.getVersion();
388
389
390 Assertions.assertDoesNotThrow(() -> mojo.installGit(artifact, location));
391 Mockito.verify(log).error(Mockito.eq(""), Mockito.any(Exception.class));
392 }
393
394
395
396
397
398
399
400
401 @Test
402 void testInstallGitDirectoryTraversal() throws Exception {
403 final GitMojo mojo = new GitMojo();
404 mojo.setLog(log);
405
406 final PortableGit portableGit = new PortableGit(log);
407 setField(mojo, "portableGit", portableGit);
408
409 final LocalRepository localRepository = new LocalRepository(tempDir.toFile());
410 Mockito.when(repoSession.getLocalRepository()).thenReturn(localRepository);
411 setField(mojo, "repoSession", repoSession);
412
413
414 final Path tarGzPath = tempDir.resolve("traversal.tar.gz");
415 final byte[] content = "evil content".getBytes(java.nio.charset.StandardCharsets.UTF_8);
416 try (java.io.OutputStream fos = Files.newOutputStream(tarGzPath);
417 GzipCompressorOutputStream gzos = new GzipCompressorOutputStream(fos);
418 TarArchiveOutputStream taos = new TarArchiveOutputStream(gzos)) {
419 final TarArchiveEntry fileEntry = new TarArchiveEntry("../../evil.sh");
420 fileEntry.setSize(content.length);
421 taos.putArchiveEntry(fileEntry);
422 taos.write(content);
423 taos.closeArchiveEntry();
424 }
425
426 final Artifact artifact = Mockito.mock(Artifact.class);
427 Mockito.when(artifact.getFile()).thenReturn(tarGzPath.toFile());
428
429 final String location = tempDir.toFile().getAbsolutePath() + java.io.File.separator + portableGit.getName()
430 + java.io.File.separator + portableGit.getVersion();
431
432
433 Assertions.assertDoesNotThrow(() -> mojo.installGit(artifact, location));
434 Mockito.verify(log, Mockito.atLeastOnce()).error(Mockito.eq(""), Mockito.any(Exception.class));
435 }
436
437
438
439
440
441
442
443
444 @Test
445 void testInstallGitRunsInstaller() throws Exception {
446 final GitMojo mojo = new GitMojo() {
447 @Override
448 protected void runInstaller(final java.util.List<String> command)
449 throws java.io.IOException, InterruptedException {
450
451 }
452 };
453 mojo.setLog(log);
454
455 final PortableGit portableGit = new PortableGit(log);
456 setField(mojo, "portableGit", portableGit);
457
458 final LocalRepository localRepository = new LocalRepository(tempDir.toFile());
459 Mockito.when(repoSession.getLocalRepository()).thenReturn(localRepository);
460 setField(mojo, "repoSession", repoSession);
461
462
463 final Path tarGzPath = tempDir.resolve("portable-git-installer.tar.gz");
464 final byte[] scriptContent = "#!/bin/sh\nexit 0\n".getBytes(java.nio.charset.StandardCharsets.UTF_8);
465 createTarGz(tarGzPath, portableGit.getName() + "/install.sh", scriptContent);
466
467 final Artifact artifact = Mockito.mock(Artifact.class);
468 Mockito.when(artifact.getFile()).thenReturn(tarGzPath.toFile());
469
470 final String location = tempDir.toFile().getAbsolutePath() + java.io.File.separator + portableGit.getName()
471 + java.io.File.separator + portableGit.getVersion();
472
473 mojo.installGit(artifact, location);
474
475
476 final String gitPath = getField(mojo, "gitPath");
477 Assertions.assertTrue(gitPath.contains(portableGit.getName()), "gitPath should reference the PortableGit name");
478 }
479
480
481
482
483
484
485
486
487 @Test
488 void testExtractPortableGitResolvedAndInstalled() throws Exception {
489 final GitMojo mojo = new GitMojo();
490 mojo.setLog(log);
491
492 final PortableGit portableGit = new PortableGit(log);
493 setField(mojo, "portableGit", portableGit);
494 setField(mojo, "remoteRepositories", List.of());
495
496
497 final LocalRepository localRepository = new LocalRepository(tempDir.toFile());
498 Mockito.when(repoSession.getLocalRepository()).thenReturn(localRepository);
499 setField(mojo, "repoSession", repoSession);
500
501
502 final Path tarGzPath = tempDir.resolve("resolved-portable-git.tar.gz");
503 final byte[] scriptContent = "#!/bin/sh\nexit 0\n".getBytes(java.nio.charset.StandardCharsets.UTF_8);
504 createTarGz(tarGzPath, portableGit.getName() + "/install.sh", scriptContent);
505
506
507 final Artifact resolvedArtifact = Mockito.mock(Artifact.class);
508 Mockito.when(resolvedArtifact.getFile()).thenReturn(tarGzPath.toFile());
509
510 final ArtifactResult resolvedResult = Mockito.mock(ArtifactResult.class);
511 Mockito.when(resolvedResult.isResolved()).thenReturn(true);
512 Mockito.when(resolvedResult.getArtifact()).thenReturn(resolvedArtifact);
513
514 Mockito.when(repositorySystem.resolveArtifact(Mockito.any(), Mockito.any())).thenReturn(resolvedResult);
515 setField(mojo, "repositorySystem", repositorySystem);
516
517
518 Assertions.assertDoesNotThrow(mojo::extractPortableGit);
519 }
520
521 }