1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.eclipse.aether.transport.file;
20
21 import java.io.FileNotFoundException;
22 import java.net.URI;
23 import java.nio.charset.StandardCharsets;
24 import java.nio.file.FileSystem;
25 import java.nio.file.FileSystems;
26 import java.nio.file.Files;
27 import java.nio.file.Path;
28 import java.nio.file.Paths;
29 import java.util.HashMap;
30
31 import com.google.common.jimfs.Configuration;
32 import com.google.common.jimfs.Jimfs;
33 import org.eclipse.aether.DefaultRepositorySystemSession;
34 import org.eclipse.aether.internal.test.util.TestFileUtils;
35 import org.eclipse.aether.internal.test.util.TestUtils;
36 import org.eclipse.aether.repository.RemoteRepository;
37 import org.eclipse.aether.spi.connector.transport.GetTask;
38 import org.eclipse.aether.spi.connector.transport.PeekTask;
39 import org.eclipse.aether.spi.connector.transport.PutTask;
40 import org.eclipse.aether.spi.connector.transport.Transporter;
41 import org.eclipse.aether.spi.connector.transport.TransporterFactory;
42 import org.eclipse.aether.transfer.NoTransporterException;
43 import org.eclipse.aether.transfer.TransferCancelledException;
44 import org.junit.jupiter.api.AfterEach;
45 import org.junit.jupiter.api.Assertions;
46 import org.junit.jupiter.api.Test;
47 import org.junit.jupiter.params.ParameterizedTest;
48 import org.junit.jupiter.params.provider.EnumSource;
49
50 import static org.junit.jupiter.api.Assertions.assertEquals;
51 import static org.junit.jupiter.api.Assertions.assertFalse;
52 import static org.junit.jupiter.api.Assertions.assertThrows;
53 import static org.junit.jupiter.api.Assertions.assertTrue;
54 import static org.junit.jupiter.api.Assertions.fail;
55 import static org.junit.jupiter.api.Assumptions.assumeTrue;
56
57
58
59 public class FileTransporterTest {
60
61 private DefaultRepositorySystemSession session;
62
63 private TransporterFactory factory;
64
65 private Transporter transporter;
66
67 private Path repoDir;
68
69 private Path tempDir;
70
71 private FileSystem fileSystem;
72
73 enum FS {
74 DEFAULT(true, ""),
75 DEFAULT_SL(true, "symlink+"),
76 DEFAULT_HL(true, "hardlink+"),
77 JIMFS(true, ""),
78 JIMFS_SL(true, "symlink+"),
79 JIMFS_HL(true, "hardlink+"),
80 BUNDLE(false, "bundle:");
81
82 final boolean writable;
83 final String uriPrefix;
84
85 FS(boolean writable, String uriPrefix) {
86 this.writable = writable;
87 this.uriPrefix = uriPrefix;
88 }
89 }
90
91 private RemoteRepository newRepo(String url) {
92 return new RemoteRepository.Builder("test", "default", url).build();
93 }
94
95 private void newTransporter(String url) throws Exception {
96 if (transporter != null) {
97 transporter.close();
98 transporter = null;
99 }
100 if (factory == null) {
101 factory = new FileTransporterFactory();
102 }
103 if (session == null) {
104 session = TestUtils.newSession();
105 }
106 transporter = factory.newInstance(session, newRepo(url));
107 }
108
109 void setUp(FS fs) {
110 try {
111 fileSystem = fs.name().startsWith("JIMFS") ? Jimfs.newFileSystem() : null;
112 repoDir = fileSystem == null ? TestFileUtils.createTempDir().toPath() : fileSystem.getPath("repo");
113 Files.createDirectories(repoDir);
114 tempDir = fileSystem == null ? TestFileUtils.createTempDir().toPath() : fileSystem.getPath("tmp");
115 Files.createDirectories(tempDir);
116 Files.write(repoDir.resolve("file.txt"), "test".getBytes(StandardCharsets.UTF_8));
117 Files.write(repoDir.resolve("empty.txt"), "".getBytes(StandardCharsets.UTF_8));
118 Files.write(repoDir.resolve("some space.txt"), "space".getBytes(StandardCharsets.UTF_8));
119 if (fs == FS.BUNDLE) {
120 URI bundle = URI.create("jar:"
121 + repoDir.resolve("bundle.zip").toAbsolutePath().toUri().toASCIIString());
122
123 HashMap<String, String> env = new HashMap<>();
124 env.put("create", "true");
125 try (FileSystem zfs = FileSystems.newFileSystem(bundle, env)) {
126 Files.copy(repoDir.resolve("file.txt"), zfs.getPath("file.txt"));
127 Files.copy(repoDir.resolve("empty.txt"), zfs.getPath("empty.txt"));
128 Files.copy(repoDir.resolve("some space.txt"), zfs.getPath("some space.txt"));
129 }
130 repoDir = repoDir.resolve("bundle.zip");
131 }
132 newTransporter(fs.uriPrefix + repoDir.toUri().toASCIIString());
133 } catch (Exception e) {
134 Assertions.fail(e);
135 }
136 }
137
138 @AfterEach
139 void tearDown() throws Exception {
140 if (transporter != null) {
141 transporter.close();
142 transporter = null;
143 }
144 if (fileSystem != null) {
145 fileSystem.close();
146 }
147 factory = null;
148 session = null;
149 }
150
151 @ParameterizedTest
152 @EnumSource(FS.class)
153 void testClassify(FS fs) {
154 setUp(fs);
155 assertEquals(Transporter.ERROR_OTHER, transporter.classify(new FileNotFoundException()));
156 assertEquals(Transporter.ERROR_NOT_FOUND, transporter.classify(new ResourceNotFoundException("test")));
157 }
158
159 @ParameterizedTest
160 @EnumSource(FS.class)
161 void testPeek(FS fs) throws Exception {
162 setUp(fs);
163 transporter.peek(new PeekTask(URI.create("file.txt")));
164 }
165
166 @ParameterizedTest
167 @EnumSource(FS.class)
168 void testPeek_NotFound(FS fs) throws Exception {
169 setUp(fs);
170 try {
171 transporter.peek(new PeekTask(URI.create("missing.txt")));
172 fail("Expected error");
173 } catch (ResourceNotFoundException e) {
174 assertEquals(Transporter.ERROR_NOT_FOUND, transporter.classify(e));
175 }
176 }
177
178 @ParameterizedTest
179 @EnumSource(FS.class)
180 void testPeek_Closed(FS fs) throws Exception {
181 setUp(fs);
182 transporter.close();
183 try {
184 transporter.peek(new PeekTask(URI.create("missing.txt")));
185 fail("Expected error");
186 } catch (IllegalStateException e) {
187 assertEquals(Transporter.ERROR_OTHER, transporter.classify(e));
188 }
189 }
190
191 @ParameterizedTest
192 @EnumSource(FS.class)
193 void testGet_ToMemory(FS fs) throws Exception {
194 setUp(fs);
195 RecordingTransportListener listener = new RecordingTransportListener();
196 GetTask task = new GetTask(URI.create("file.txt")).setListener(listener);
197 transporter.get(task);
198 assertEquals("test", task.getDataString());
199 assertEquals(0L, listener.dataOffset);
200 assertEquals(4L, listener.dataLength);
201 assertEquals(1, listener.startedCount);
202 assertTrue(listener.progressedCount > 0, "Count: " + listener.progressedCount);
203 assertEquals(task.getDataString(), new String(listener.baos.toByteArray(), StandardCharsets.UTF_8));
204 }
205
206 @ParameterizedTest
207 @EnumSource(FS.class)
208 void testGet_ToFile(FS fs) throws Exception {
209 setUp(fs);
210 Path file = tempDir.resolve("testGet_ToFile");
211 Files.write(file, "whatever".getBytes(StandardCharsets.UTF_8));
212 RecordingTransportListener listener = new RecordingTransportListener();
213 GetTask task = new GetTask(URI.create("file.txt")).setDataPath(file).setListener(listener);
214 transporter.get(task);
215 assertEquals("test", new String(Files.readAllBytes(file), StandardCharsets.UTF_8));
216 assertEquals(0L, listener.dataOffset);
217 assertEquals(4L, listener.dataLength);
218 assertEquals(1, listener.startedCount);
219 assertTrue(listener.progressedCount > 0, "Count: " + listener.progressedCount);
220 assertEquals("test", new String(listener.baos.toByteArray(), StandardCharsets.UTF_8));
221 }
222
223 @ParameterizedTest
224 @EnumSource(FS.class)
225 void testGet_EmptyResource(FS fs) throws Exception {
226 setUp(fs);
227 Path file = tempDir.resolve("testGet_EmptyResource");
228 Files.write(file, "".getBytes(StandardCharsets.UTF_8));
229 RecordingTransportListener listener = new RecordingTransportListener();
230 GetTask task = new GetTask(URI.create("empty.txt")).setDataPath(file).setListener(listener);
231 transporter.get(task);
232 assertEquals("", new String(Files.readAllBytes(file), StandardCharsets.UTF_8));
233 assertEquals(0L, listener.dataOffset);
234 assertEquals(0L, listener.dataLength);
235 assertEquals(1, listener.startedCount);
236 assertEquals(0, listener.progressedCount);
237 assertEquals("", new String(listener.baos.toByteArray(), StandardCharsets.UTF_8));
238 }
239
240 @ParameterizedTest
241 @EnumSource(FS.class)
242 void testGet_EncodedResourcePath(FS fs) throws Exception {
243 setUp(fs);
244 GetTask task = new GetTask(URI.create("some%20space.txt"));
245 transporter.get(task);
246 assertEquals("space", task.getDataString());
247 }
248
249 @ParameterizedTest
250 @EnumSource(FS.class)
251 void testGet_Fragment(FS fs) throws Exception {
252 setUp(fs);
253 GetTask task = new GetTask(URI.create("file.txt#ignored"));
254 transporter.get(task);
255 assertEquals("test", task.getDataString());
256 }
257
258 @ParameterizedTest
259 @EnumSource(FS.class)
260 void testGet_Query(FS fs) throws Exception {
261 setUp(fs);
262 GetTask task = new GetTask(URI.create("file.txt?ignored"));
263 transporter.get(task);
264 assertEquals("test", task.getDataString());
265 }
266
267 @ParameterizedTest
268 @EnumSource(FS.class)
269 void testGet_FileHandleLeak(FS fs) throws Exception {
270 setUp(fs);
271 for (int i = 0; i < 100; i++) {
272 Path file = tempDir.resolve("testGet_FileHandleLeak" + i);
273 transporter.get(new GetTask(URI.create("file.txt")).setDataPath(file));
274 if (fs.uriPrefix.startsWith("symlink+")) {
275 assertTrue(Files.isSymbolicLink(file));
276 assertTrue(Files.deleteIfExists(file), i + ", " + file.toAbsolutePath());
277 } else if (fs.uriPrefix.startsWith("hardlink+")) {
278 assertTrue(Files.isRegularFile(file));
279
280
281
282
283
284 } else {
285 assertTrue(Files.isRegularFile(file));
286 assertTrue(Files.deleteIfExists(file), i + ", " + file.toAbsolutePath());
287 }
288 }
289 }
290
291 @ParameterizedTest
292 @EnumSource(FS.class)
293 void testGet_NotFound(FS fs) throws Exception {
294 setUp(fs);
295 try {
296 transporter.get(new GetTask(URI.create("missing.txt")));
297 fail("Expected error");
298 } catch (ResourceNotFoundException e) {
299 assertEquals(Transporter.ERROR_NOT_FOUND, transporter.classify(e));
300 }
301 }
302
303 @ParameterizedTest
304 @EnumSource(FS.class)
305 void testGet_Closed(FS fs) throws Exception {
306 setUp(fs);
307 transporter.close();
308 try {
309 transporter.get(new GetTask(URI.create("file.txt")));
310 fail("Expected error");
311 } catch (IllegalStateException e) {
312 assertEquals(Transporter.ERROR_OTHER, transporter.classify(e));
313 }
314 }
315
316 @ParameterizedTest
317 @EnumSource(FS.class)
318 void testGet_StartCancelled(FS fs) throws Exception {
319 setUp(fs);
320 RecordingTransportListener listener = new RecordingTransportListener();
321 listener.cancelStart = true;
322 GetTask task = new GetTask(URI.create("file.txt")).setListener(listener);
323 try {
324 transporter.get(task);
325 fail("Expected error");
326 } catch (TransferCancelledException e) {
327 assertEquals(Transporter.ERROR_OTHER, transporter.classify(e));
328 }
329 assertEquals(0L, listener.dataOffset);
330 assertEquals(4L, listener.dataLength);
331 assertEquals(1, listener.startedCount);
332 assertEquals(0, listener.progressedCount);
333 }
334
335 @ParameterizedTest
336 @EnumSource(FS.class)
337 void testGet_ProgressCancelled(FS fs) throws Exception {
338 setUp(fs);
339 RecordingTransportListener listener = new RecordingTransportListener();
340 listener.cancelProgress = true;
341 GetTask task = new GetTask(URI.create("file.txt")).setListener(listener);
342 try {
343 transporter.get(task);
344 fail("Expected error");
345 } catch (TransferCancelledException e) {
346 assertEquals(Transporter.ERROR_OTHER, transporter.classify(e));
347 }
348 assertEquals(0L, listener.dataOffset);
349 assertEquals(4L, listener.dataLength);
350 assertEquals(1, listener.startedCount);
351 assertEquals(1, listener.progressedCount);
352 }
353
354 @ParameterizedTest
355 @EnumSource(FS.class)
356 void testPut_FromMemory(FS fs) throws Exception {
357 assumeTrue(fs.writable);
358 setUp(fs);
359 RecordingTransportListener listener = new RecordingTransportListener();
360 PutTask task = new PutTask(URI.create("file.txt")).setListener(listener).setDataString("upload");
361 transporter.put(task);
362 assertEquals(0L, listener.dataOffset);
363 assertEquals(6L, listener.dataLength);
364 assertEquals(1, listener.startedCount);
365 assertTrue(listener.progressedCount > 0, "Count: " + listener.progressedCount);
366 assertEquals("upload", new String(Files.readAllBytes(repoDir.resolve("file.txt")), StandardCharsets.UTF_8));
367 }
368
369 @ParameterizedTest
370 @EnumSource(FS.class)
371 void testPut_FromFile(FS fs) throws Exception {
372 assumeTrue(fs.writable);
373 setUp(fs);
374 Path file = tempDir.resolve("upload");
375 Files.write(file, "upload".getBytes(StandardCharsets.UTF_8));
376 RecordingTransportListener listener = new RecordingTransportListener();
377 PutTask task = new PutTask(URI.create("file.txt")).setListener(listener).setDataPath(file);
378 transporter.put(task);
379 assertEquals(0L, listener.dataOffset);
380 assertEquals(6L, listener.dataLength);
381 assertEquals(1, listener.startedCount);
382 assertTrue(listener.progressedCount > 0, "Count: " + listener.progressedCount);
383 assertEquals("upload", new String(Files.readAllBytes(repoDir.resolve("file.txt")), StandardCharsets.UTF_8));
384 }
385
386 @ParameterizedTest
387 @EnumSource(FS.class)
388 void testPut_EmptyResource(FS fs) throws Exception {
389 assumeTrue(fs.writable);
390 setUp(fs);
391 RecordingTransportListener listener = new RecordingTransportListener();
392 PutTask task = new PutTask(URI.create("file.txt")).setListener(listener);
393 transporter.put(task);
394 assertEquals(0L, listener.dataOffset);
395 assertEquals(0L, listener.dataLength);
396 assertEquals(1, listener.startedCount);
397 assertEquals(0, listener.progressedCount);
398 assertEquals("", new String(Files.readAllBytes(repoDir.resolve("file.txt")), StandardCharsets.UTF_8));
399 }
400
401 @ParameterizedTest
402 @EnumSource(FS.class)
403 void testPut_NonExistentParentDir(FS fs) throws Exception {
404 assumeTrue(fs.writable);
405 setUp(fs);
406 RecordingTransportListener listener = new RecordingTransportListener();
407 PutTask task = new PutTask(URI.create("dir/sub/dir/file.txt"))
408 .setListener(listener)
409 .setDataString("upload");
410 transporter.put(task);
411 assertEquals(0L, listener.dataOffset);
412 assertEquals(6L, listener.dataLength);
413 assertEquals(1, listener.startedCount);
414 assertTrue(listener.progressedCount > 0, "Count: " + listener.progressedCount);
415 assertEquals(
416 "upload",
417 new String(Files.readAllBytes(repoDir.resolve("dir/sub/dir/file.txt")), StandardCharsets.UTF_8));
418 }
419
420 @ParameterizedTest
421 @EnumSource(FS.class)
422 void testPut_EncodedResourcePath(FS fs) throws Exception {
423 assumeTrue(fs.writable);
424 setUp(fs);
425 RecordingTransportListener listener = new RecordingTransportListener();
426 PutTask task = new PutTask(URI.create("some%20space.txt"))
427 .setListener(listener)
428 .setDataString("OK");
429 transporter.put(task);
430 assertEquals(0L, listener.dataOffset);
431 assertEquals(2L, listener.dataLength);
432 assertEquals(1, listener.startedCount);
433 assertTrue(listener.progressedCount > 0, "Count: " + listener.progressedCount);
434 assertEquals("OK", new String(Files.readAllBytes(repoDir.resolve("some space.txt")), StandardCharsets.UTF_8));
435 }
436
437 @ParameterizedTest
438 @EnumSource(FS.class)
439 void testPut_FileHandleLeak(FS fs) throws Exception {
440 assumeTrue(fs.writable);
441 setUp(fs);
442 for (int i = 0; i < 100; i++) {
443 Path src = tempDir.resolve("upload");
444 Files.write(src, "upload".getBytes(StandardCharsets.UTF_8));
445 Path dst = repoDir.resolve("file.txt");
446 transporter.put(new PutTask(URI.create("file.txt")).setDataPath(src));
447 assertTrue(Files.deleteIfExists(src), i + ", " + src.toAbsolutePath());
448 assertTrue(Files.deleteIfExists(dst), i + ", " + dst.toAbsolutePath());
449 }
450 }
451
452 @ParameterizedTest
453 @EnumSource(FS.class)
454 void testPut_Closed(FS fs) throws Exception {
455 assumeTrue(fs.writable);
456 setUp(fs);
457 transporter.close();
458 try {
459 transporter.put(new PutTask(URI.create("missing.txt")));
460 fail("Expected error");
461 } catch (IllegalStateException e) {
462 assertEquals(Transporter.ERROR_OTHER, transporter.classify(e));
463 }
464 }
465
466 @ParameterizedTest
467 @EnumSource(FS.class)
468 void testPut_StartCancelled(FS fs) throws Exception {
469 assumeTrue(fs.writable);
470 setUp(fs);
471 RecordingTransportListener listener = new RecordingTransportListener();
472 listener.cancelStart = true;
473 PutTask task = new PutTask(URI.create("file.txt")).setListener(listener).setDataString("upload");
474 try {
475 transporter.put(task);
476 fail("Expected error");
477 } catch (TransferCancelledException e) {
478 assertEquals(Transporter.ERROR_OTHER, transporter.classify(e));
479 }
480 assertEquals(0L, listener.dataOffset);
481 assertEquals(6L, listener.dataLength);
482 assertEquals(1, listener.startedCount);
483 assertEquals(0, listener.progressedCount);
484 assertFalse(Files.exists(repoDir.resolve("file.txt")));
485 }
486
487 @ParameterizedTest
488 @EnumSource(FS.class)
489 void testPut_ProgressCancelled(FS fs) throws Exception {
490 assumeTrue(fs.writable);
491 setUp(fs);
492 RecordingTransportListener listener = new RecordingTransportListener();
493 listener.cancelProgress = true;
494 PutTask task = new PutTask(URI.create("file.txt")).setListener(listener).setDataString("upload");
495 try {
496 transporter.put(task);
497 fail("Expected error");
498 } catch (TransferCancelledException e) {
499 assertEquals(Transporter.ERROR_OTHER, transporter.classify(e));
500 }
501 assertEquals(0L, listener.dataOffset);
502 assertEquals(6L, listener.dataLength);
503 assertEquals(1, listener.startedCount);
504 assertEquals(1, listener.progressedCount);
505 assertFalse(Files.exists(repoDir.resolve("file.txt")));
506 }
507
508 @Test
509 void testInit_BadProtocol() {
510 assertThrows(NoTransporterException.class, () -> newTransporter("bad:/void"));
511 }
512
513 @Test
514 void testInit_CaseInsensitiveProtocol() throws Exception {
515 newTransporter("file:/void");
516 newTransporter("FILE:/void");
517 newTransporter("File:/void");
518 }
519
520 @Test
521 void testInit_OpaqueUrl() throws Exception {
522 testInit("file:repository", "repository");
523 }
524
525 @Test
526 void testInit_OpaqueUrlTrailingSlash() throws Exception {
527 testInit("file:repository/", "repository");
528 }
529
530 @Test
531 void testInit_OpaqueUrlSpaces() throws Exception {
532 testInit("file:repo%20space", "repo space");
533 }
534
535 @Test
536 void testInit_OpaqueUrlSpacesDecoded() throws Exception {
537 testInit("file:repo space", "repo space");
538 }
539
540 @Test
541 void testInit_HierarchicalUrl() throws Exception {
542 testInit("file:/repository", "/repository");
543 }
544
545 @Test
546 void testInit_HierarchicalUrlTrailingSlash() throws Exception {
547 testInit("file:/repository/", "/repository");
548 }
549
550 @Test
551 void testInit_HierarchicalUrlSpaces() throws Exception {
552 testInit("file:/repo%20space", "/repo space");
553 }
554
555 @Test
556 void testInit_HierarchicalUrlSpacesDecoded() throws Exception {
557 testInit("file:/repo space", "/repo space");
558 }
559
560 @Test
561 void testInit_HierarchicalUrlRoot() throws Exception {
562 testInit("file:/", "/");
563 }
564
565 @Test
566 void testInit_HierarchicalUrlHostNoPath() throws Exception {
567 testInit("file://host/", "/");
568 }
569
570 @Test
571 void testInit_HierarchicalUrlHostPath() throws Exception {
572 testInit("file://host/dir", "/dir");
573 }
574
575 @Test
576 void testInit_NonDefaultFileSystemRelative() throws Exception {
577 try (FileSystem fs = Jimfs.newFileSystem(Configuration.unix())) {
578 Path path = fs.getPath("dir");
579 testInit(path.toUri().toASCIIString(), "/work/dir");
580 }
581 }
582
583 @Test
584 void testInit_NonDefaultFileSystemAbsolute() throws Exception {
585 try (FileSystem fs = Jimfs.newFileSystem(Configuration.unix())) {
586 Path path = fs.getPath("/dir");
587 testInit(path.toUri().toASCIIString(), "/dir");
588 }
589 }
590
591 private void testInit(String base, String expected) throws Exception {
592 newTransporter(base);
593 String exp = expected;
594 if (base.startsWith("file:")) {
595
596
597
598 exp = Paths.get(expected).toAbsolutePath().toString();
599 }
600
601
602 assertEquals(exp, ((FileTransporter) transporter).getBasePath().toString());
603 }
604 }