View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
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     /**
110      * Builds a task location URI carrying the absolute path of the given resource within the repository base
111      * directory, in a platform and file system neutral way (e.g. {@code /C:/repo/file.txt} on Windows).
112      */
113     private URI absoluteLocation(String resource) throws Exception {
114         Path absolute =
115                 ((FileTransporter) transporter).getBasePath().resolve(resource).toAbsolutePath();
116         String rawPath = absolute.toString().replace('\\', '/');
117         if (!rawPath.startsWith("/")) {
118             rawPath = "/" + rawPath;
119         }
120         return new URI(null, null, rawPath, null);
121     }
122 
123     void setUp(FS fs) {
124         try {
125             fileSystem = fs.name().startsWith("JIMFS") ? Jimfs.newFileSystem() : null;
126             repoDir = fileSystem == null ? TestFileUtils.createTempDir().toPath() : fileSystem.getPath("repo");
127             Files.createDirectories(repoDir);
128             tempDir = fileSystem == null ? TestFileUtils.createTempDir().toPath() : fileSystem.getPath("tmp");
129             Files.createDirectories(tempDir);
130             Files.write(repoDir.resolve("file.txt"), "test".getBytes(StandardCharsets.UTF_8));
131             Files.write(repoDir.resolve("empty.txt"), "".getBytes(StandardCharsets.UTF_8));
132             Files.write(repoDir.resolve("some space.txt"), "space".getBytes(StandardCharsets.UTF_8));
133             if (fs == FS.BUNDLE) {
134                 URI bundle = URI.create("jar:"
135                         + repoDir.resolve("bundle.zip").toAbsolutePath().toUri().toASCIIString());
136                 // zip it up and change uri to zip file path
137                 HashMap<String, String> env = new HashMap<>();
138                 env.put("create", "true");
139                 try (FileSystem zfs = FileSystems.newFileSystem(bundle, env)) {
140                     Files.copy(repoDir.resolve("file.txt"), zfs.getPath("file.txt"));
141                     Files.copy(repoDir.resolve("empty.txt"), zfs.getPath("empty.txt"));
142                     Files.copy(repoDir.resolve("some space.txt"), zfs.getPath("some space.txt"));
143                 }
144                 repoDir = repoDir.resolve("bundle.zip");
145             }
146             newTransporter(fs.uriPrefix + repoDir.toUri().toASCIIString());
147         } catch (Exception e) {
148             Assertions.fail(e);
149         }
150     }
151 
152     @AfterEach
153     void tearDown() throws Exception {
154         if (transporter != null) {
155             transporter.close();
156             transporter = null;
157         }
158         if (fileSystem != null) {
159             fileSystem.close();
160         }
161         factory = null;
162         session = null;
163     }
164 
165     @ParameterizedTest
166     @EnumSource(FS.class)
167     void testClassify(FS fs) {
168         setUp(fs);
169         assertEquals(Transporter.ERROR_OTHER, transporter.classify(new FileNotFoundException()));
170         assertEquals(Transporter.ERROR_NOT_FOUND, transporter.classify(new ResourceNotFoundException("test")));
171     }
172 
173     @ParameterizedTest
174     @EnumSource(FS.class)
175     void testPeek(FS fs) throws Exception {
176         setUp(fs);
177         transporter.peek(new PeekTask(URI.create("file.txt")));
178     }
179 
180     @ParameterizedTest
181     @EnumSource(FS.class)
182     void testPeek_NotFound(FS fs) throws Exception {
183         setUp(fs);
184         try {
185             transporter.peek(new PeekTask(URI.create("missing.txt")));
186             fail("Expected error");
187         } catch (ResourceNotFoundException e) {
188             assertEquals(Transporter.ERROR_NOT_FOUND, transporter.classify(e));
189         }
190     }
191 
192     @ParameterizedTest
193     @EnumSource(FS.class)
194     void testPeek_Closed(FS fs) throws Exception {
195         setUp(fs);
196         transporter.close();
197         try {
198             transporter.peek(new PeekTask(URI.create("missing.txt")));
199             fail("Expected error");
200         } catch (IllegalStateException e) {
201             assertEquals(Transporter.ERROR_OTHER, transporter.classify(e));
202         }
203     }
204 
205     @ParameterizedTest
206     @EnumSource(FS.class)
207     void testGet_ToMemory(FS fs) throws Exception {
208         setUp(fs);
209         RecordingTransportListener listener = new RecordingTransportListener();
210         GetTask task = new GetTask(URI.create("file.txt")).setListener(listener);
211         transporter.get(task);
212         assertEquals("test", task.getDataString());
213         assertEquals(0L, listener.dataOffset);
214         assertEquals(4L, listener.dataLength);
215         assertEquals(1, listener.startedCount);
216         assertTrue(listener.progressedCount > 0, "Count: " + listener.progressedCount);
217         assertEquals(task.getDataString(), new String(listener.baos.toByteArray(), StandardCharsets.UTF_8));
218     }
219 
220     @ParameterizedTest
221     @EnumSource(FS.class)
222     void testGet_ToFile(FS fs) throws Exception {
223         setUp(fs);
224         Path file = tempDir.resolve("testGet_ToFile");
225         Files.write(file, "whatever".getBytes(StandardCharsets.UTF_8));
226         RecordingTransportListener listener = new RecordingTransportListener();
227         GetTask task = new GetTask(URI.create("file.txt")).setDataPath(file).setListener(listener);
228         transporter.get(task);
229         assertEquals("test", new String(Files.readAllBytes(file), StandardCharsets.UTF_8));
230         assertEquals(0L, listener.dataOffset);
231         assertEquals(4L, listener.dataLength);
232         assertEquals(1, listener.startedCount);
233         assertTrue(listener.progressedCount > 0, "Count: " + listener.progressedCount);
234         assertEquals("test", new String(listener.baos.toByteArray(), StandardCharsets.UTF_8));
235     }
236 
237     @ParameterizedTest
238     @EnumSource(FS.class)
239     void testGet_EmptyResource(FS fs) throws Exception {
240         setUp(fs);
241         Path file = tempDir.resolve("testGet_EmptyResource");
242         Files.write(file, "".getBytes(StandardCharsets.UTF_8));
243         RecordingTransportListener listener = new RecordingTransportListener();
244         GetTask task = new GetTask(URI.create("empty.txt")).setDataPath(file).setListener(listener);
245         transporter.get(task);
246         assertEquals("", new String(Files.readAllBytes(file), StandardCharsets.UTF_8));
247         assertEquals(0L, listener.dataOffset);
248         assertEquals(0L, listener.dataLength);
249         assertEquals(1, listener.startedCount);
250         assertEquals(0, listener.progressedCount);
251         assertEquals("", new String(listener.baos.toByteArray(), StandardCharsets.UTF_8));
252     }
253 
254     @ParameterizedTest
255     @EnumSource(FS.class)
256     void testGet_EncodedResourcePath(FS fs) throws Exception {
257         setUp(fs);
258         GetTask task = new GetTask(URI.create("some%20space.txt"));
259         transporter.get(task);
260         assertEquals("space", task.getDataString());
261     }
262 
263     @ParameterizedTest
264     @EnumSource(FS.class)
265     void testGet_Fragment(FS fs) throws Exception {
266         setUp(fs);
267         GetTask task = new GetTask(URI.create("file.txt#ignored"));
268         transporter.get(task);
269         assertEquals("test", task.getDataString());
270     }
271 
272     @ParameterizedTest
273     @EnumSource(FS.class)
274     void testGet_Query(FS fs) throws Exception {
275         setUp(fs);
276         GetTask task = new GetTask(URI.create("file.txt?ignored"));
277         transporter.get(task);
278         assertEquals("test", task.getDataString());
279     }
280 
281     @ParameterizedTest
282     @EnumSource(FS.class)
283     void testGet_FileHandleLeak(FS fs) throws Exception {
284         setUp(fs);
285         for (int i = 0; i < 100; i++) {
286             Path file = tempDir.resolve("testGet_FileHandleLeak" + i);
287             transporter.get(new GetTask(URI.create("file.txt")).setDataPath(file));
288             if (fs.uriPrefix.startsWith("symlink+")) {
289                 assertTrue(Files.isSymbolicLink(file));
290                 assertTrue(Files.deleteIfExists(file), i + ", " + file.toAbsolutePath());
291             } else if (fs.uriPrefix.startsWith("hardlink+")) {
292                 assertTrue(Files.isRegularFile(file));
293                 // Doing this on windows FS is not possible (immediately create then delete link) due windows lock
294                 // semantics. While other OS do perform this test OK, it fails on Windows with AccessDeniedEx.
295                 // The file becomes deletable on Windows after some arbitrary time, but let's not fiddle with that in
296                 // this UT.
297                 // assertTrue(Files.deleteIfExists(file), i + ", " + file.toAbsolutePath());
298             } else {
299                 assertTrue(Files.isRegularFile(file));
300                 assertTrue(Files.deleteIfExists(file), i + ", " + file.toAbsolutePath());
301             }
302         }
303     }
304 
305     @ParameterizedTest
306     @EnumSource(FS.class)
307     void testGet_NotFound(FS fs) throws Exception {
308         setUp(fs);
309         try {
310             transporter.get(new GetTask(URI.create("missing.txt")));
311             fail("Expected error");
312         } catch (ResourceNotFoundException e) {
313             assertEquals(Transporter.ERROR_NOT_FOUND, transporter.classify(e));
314         }
315     }
316 
317     @ParameterizedTest
318     @EnumSource(FS.class)
319     void testGet_AbsoluteResourcePathRejected(FS fs) throws Exception {
320         setUp(fs);
321         GetTask task = new GetTask(absoluteLocation("file.txt"));
322         assertThrows(IllegalArgumentException.class, () -> transporter.get(task));
323     }
324 
325     @ParameterizedTest
326     @EnumSource(FS.class)
327     void testGet_ParentReferenceRejected(FS fs) {
328         setUp(fs);
329         assertThrows(IllegalArgumentException.class, () -> transporter.get(new GetTask(URI.create("../file.txt"))));
330     }
331 
332     @ParameterizedTest
333     @EnumSource(FS.class)
334     void testGet_BareParentDirRejected(FS fs) {
335         setUp(fs);
336         assertThrows(IllegalArgumentException.class, () -> transporter.get(new GetTask(URI.create(".."))));
337     }
338 
339     @ParameterizedTest
340     @EnumSource(FS.class)
341     void testGet_NestedParentReferenceRejected(FS fs) {
342         setUp(fs);
343         assertThrows(
344                 IllegalArgumentException.class, () -> transporter.get(new GetTask(URI.create("a/../../file.txt"))));
345     }
346 
347     @ParameterizedTest
348     @EnumSource(FS.class)
349     void testPeek_ParentReferenceRejected(FS fs) {
350         setUp(fs);
351         assertThrows(IllegalArgumentException.class, () -> transporter.peek(new PeekTask(URI.create("../file.txt"))));
352     }
353 
354     @ParameterizedTest
355     @EnumSource(FS.class)
356     void testGet_Closed(FS fs) throws Exception {
357         setUp(fs);
358         transporter.close();
359         try {
360             transporter.get(new GetTask(URI.create("file.txt")));
361             fail("Expected error");
362         } catch (IllegalStateException e) {
363             assertEquals(Transporter.ERROR_OTHER, transporter.classify(e));
364         }
365     }
366 
367     @ParameterizedTest
368     @EnumSource(FS.class)
369     void testGet_StartCancelled(FS fs) throws Exception {
370         setUp(fs);
371         RecordingTransportListener listener = new RecordingTransportListener();
372         listener.cancelStart = true;
373         GetTask task = new GetTask(URI.create("file.txt")).setListener(listener);
374         try {
375             transporter.get(task);
376             fail("Expected error");
377         } catch (TransferCancelledException e) {
378             assertEquals(Transporter.ERROR_OTHER, transporter.classify(e));
379         }
380         assertEquals(0L, listener.dataOffset);
381         assertEquals(4L, listener.dataLength);
382         assertEquals(1, listener.startedCount);
383         assertEquals(0, listener.progressedCount);
384     }
385 
386     @ParameterizedTest
387     @EnumSource(FS.class)
388     void testGet_ProgressCancelled(FS fs) throws Exception {
389         setUp(fs);
390         RecordingTransportListener listener = new RecordingTransportListener();
391         listener.cancelProgress = true;
392         GetTask task = new GetTask(URI.create("file.txt")).setListener(listener);
393         try {
394             transporter.get(task);
395             fail("Expected error");
396         } catch (TransferCancelledException e) {
397             assertEquals(Transporter.ERROR_OTHER, transporter.classify(e));
398         }
399         assertEquals(0L, listener.dataOffset);
400         assertEquals(4L, listener.dataLength);
401         assertEquals(1, listener.startedCount);
402         assertEquals(1, listener.progressedCount);
403     }
404 
405     @ParameterizedTest
406     @EnumSource(FS.class)
407     void testPut_FromMemory(FS fs) throws Exception {
408         assumeTrue(fs.writable);
409         setUp(fs);
410         RecordingTransportListener listener = new RecordingTransportListener();
411         PutTask task = new PutTask(URI.create("file.txt")).setListener(listener).setDataString("upload");
412         transporter.put(task);
413         assertEquals(0L, listener.dataOffset);
414         assertEquals(6L, listener.dataLength);
415         assertEquals(1, listener.startedCount);
416         assertTrue(listener.progressedCount > 0, "Count: " + listener.progressedCount);
417         assertEquals("upload", new String(Files.readAllBytes(repoDir.resolve("file.txt")), StandardCharsets.UTF_8));
418     }
419 
420     @ParameterizedTest
421     @EnumSource(FS.class)
422     void testPut_FromFile(FS fs) throws Exception {
423         assumeTrue(fs.writable);
424         setUp(fs);
425         Path file = tempDir.resolve("upload");
426         Files.write(file, "upload".getBytes(StandardCharsets.UTF_8));
427         RecordingTransportListener listener = new RecordingTransportListener();
428         PutTask task = new PutTask(URI.create("file.txt")).setListener(listener).setDataPath(file);
429         transporter.put(task);
430         assertEquals(0L, listener.dataOffset);
431         assertEquals(6L, listener.dataLength);
432         assertEquals(1, listener.startedCount);
433         assertTrue(listener.progressedCount > 0, "Count: " + listener.progressedCount);
434         assertEquals("upload", new String(Files.readAllBytes(repoDir.resolve("file.txt")), StandardCharsets.UTF_8));
435     }
436 
437     @ParameterizedTest
438     @EnumSource(FS.class)
439     void testPut_EmptyResource(FS fs) throws Exception {
440         assumeTrue(fs.writable);
441         setUp(fs);
442         RecordingTransportListener listener = new RecordingTransportListener();
443         PutTask task = new PutTask(URI.create("file.txt")).setListener(listener);
444         transporter.put(task);
445         assertEquals(0L, listener.dataOffset);
446         assertEquals(0L, listener.dataLength);
447         assertEquals(1, listener.startedCount);
448         assertEquals(0, listener.progressedCount);
449         assertEquals("", new String(Files.readAllBytes(repoDir.resolve("file.txt")), StandardCharsets.UTF_8));
450     }
451 
452     @ParameterizedTest
453     @EnumSource(FS.class)
454     void testPut_NonExistentParentDir(FS fs) throws Exception {
455         assumeTrue(fs.writable);
456         setUp(fs);
457         RecordingTransportListener listener = new RecordingTransportListener();
458         PutTask task = new PutTask(URI.create("dir/sub/dir/file.txt"))
459                 .setListener(listener)
460                 .setDataString("upload");
461         transporter.put(task);
462         assertEquals(0L, listener.dataOffset);
463         assertEquals(6L, listener.dataLength);
464         assertEquals(1, listener.startedCount);
465         assertTrue(listener.progressedCount > 0, "Count: " + listener.progressedCount);
466         assertEquals(
467                 "upload",
468                 new String(Files.readAllBytes(repoDir.resolve("dir/sub/dir/file.txt")), StandardCharsets.UTF_8));
469     }
470 
471     @ParameterizedTest
472     @EnumSource(FS.class)
473     void testPut_EncodedResourcePath(FS fs) throws Exception {
474         assumeTrue(fs.writable);
475         setUp(fs);
476         RecordingTransportListener listener = new RecordingTransportListener();
477         PutTask task = new PutTask(URI.create("some%20space.txt"))
478                 .setListener(listener)
479                 .setDataString("OK");
480         transporter.put(task);
481         assertEquals(0L, listener.dataOffset);
482         assertEquals(2L, listener.dataLength);
483         assertEquals(1, listener.startedCount);
484         assertTrue(listener.progressedCount > 0, "Count: " + listener.progressedCount);
485         assertEquals("OK", new String(Files.readAllBytes(repoDir.resolve("some space.txt")), StandardCharsets.UTF_8));
486     }
487 
488     @ParameterizedTest
489     @EnumSource(FS.class)
490     void testPut_FileHandleLeak(FS fs) throws Exception {
491         assumeTrue(fs.writable);
492         setUp(fs);
493         for (int i = 0; i < 100; i++) {
494             Path src = tempDir.resolve("upload");
495             Files.write(src, "upload".getBytes(StandardCharsets.UTF_8));
496             Path dst = repoDir.resolve("file.txt");
497             transporter.put(new PutTask(URI.create("file.txt")).setDataPath(src));
498             assertTrue(Files.deleteIfExists(src), i + ", " + src.toAbsolutePath());
499             assertTrue(Files.deleteIfExists(dst), i + ", " + dst.toAbsolutePath());
500         }
501     }
502 
503     @ParameterizedTest
504     @EnumSource(FS.class)
505     void testPut_AbsoluteResourcePathRejected(FS fs) throws Exception {
506         assumeTrue(fs.writable);
507         setUp(fs);
508         PutTask task = new PutTask(absoluteLocation("absolute-upload.txt")).setDataString("upload");
509         assertThrows(IllegalArgumentException.class, () -> transporter.put(task));
510         assertFalse(Files.exists(repoDir.resolve("absolute-upload.txt")));
511     }
512 
513     @ParameterizedTest
514     @EnumSource(FS.class)
515     void testPut_ParentReferenceRejected(FS fs) throws Exception {
516         assumeTrue(fs.writable);
517         setUp(fs);
518         PutTask task = new PutTask(URI.create("../sibling.txt")).setDataString("upload");
519         assertThrows(IllegalArgumentException.class, () -> transporter.put(task));
520         Path base = ((FileTransporter) transporter).getBasePath();
521         assertFalse(Files.exists(base.getParent().resolve("sibling.txt")));
522     }
523 
524     @ParameterizedTest
525     @EnumSource(FS.class)
526     void testPut_NestedParentReferenceRejected(FS fs) throws Exception {
527         assumeTrue(fs.writable);
528         setUp(fs);
529         PutTask task = new PutTask(URI.create("a/../../sibling.txt")).setDataString("upload");
530         assertThrows(IllegalArgumentException.class, () -> transporter.put(task));
531         Path base = ((FileTransporter) transporter).getBasePath();
532         assertFalse(Files.exists(base.getParent().resolve("sibling.txt")));
533     }
534 
535     @ParameterizedTest
536     @EnumSource(FS.class)
537     void testPut_Closed(FS fs) throws Exception {
538         assumeTrue(fs.writable);
539         setUp(fs);
540         transporter.close();
541         try {
542             transporter.put(new PutTask(URI.create("missing.txt")));
543             fail("Expected error");
544         } catch (IllegalStateException e) {
545             assertEquals(Transporter.ERROR_OTHER, transporter.classify(e));
546         }
547     }
548 
549     @ParameterizedTest
550     @EnumSource(FS.class)
551     void testPut_StartCancelled(FS fs) throws Exception {
552         assumeTrue(fs.writable);
553         setUp(fs);
554         RecordingTransportListener listener = new RecordingTransportListener();
555         listener.cancelStart = true;
556         PutTask task = new PutTask(URI.create("file.txt")).setListener(listener).setDataString("upload");
557         try {
558             transporter.put(task);
559             fail("Expected error");
560         } catch (TransferCancelledException e) {
561             assertEquals(Transporter.ERROR_OTHER, transporter.classify(e));
562         }
563         assertEquals(0L, listener.dataOffset);
564         assertEquals(6L, listener.dataLength);
565         assertEquals(1, listener.startedCount);
566         assertEquals(0, listener.progressedCount);
567         assertFalse(Files.exists(repoDir.resolve("file.txt")));
568     }
569 
570     @ParameterizedTest
571     @EnumSource(FS.class)
572     void testPut_ProgressCancelled(FS fs) throws Exception {
573         assumeTrue(fs.writable);
574         setUp(fs);
575         RecordingTransportListener listener = new RecordingTransportListener();
576         listener.cancelProgress = true;
577         PutTask task = new PutTask(URI.create("file.txt")).setListener(listener).setDataString("upload");
578         try {
579             transporter.put(task);
580             fail("Expected error");
581         } catch (TransferCancelledException e) {
582             assertEquals(Transporter.ERROR_OTHER, transporter.classify(e));
583         }
584         assertEquals(0L, listener.dataOffset);
585         assertEquals(6L, listener.dataLength);
586         assertEquals(1, listener.startedCount);
587         assertEquals(1, listener.progressedCount);
588         assertFalse(Files.exists(repoDir.resolve("file.txt")));
589     }
590 
591     @Test
592     void testInit_BadProtocol() {
593         assertThrows(NoTransporterException.class, () -> newTransporter("bad:/void"));
594     }
595 
596     @Test
597     void testInit_CaseInsensitiveProtocol() throws Exception {
598         newTransporter("file:/void");
599         newTransporter("FILE:/void");
600         newTransporter("File:/void");
601     }
602 
603     @Test
604     void testInit_OpaqueUrl() throws Exception {
605         testInit("file:repository", "repository");
606     }
607 
608     @Test
609     void testInit_OpaqueUrlTrailingSlash() throws Exception {
610         testInit("file:repository/", "repository");
611     }
612 
613     @Test
614     void testInit_OpaqueUrlSpaces() throws Exception {
615         testInit("file:repo%20space", "repo space");
616     }
617 
618     @Test
619     void testInit_OpaqueUrlSpacesDecoded() throws Exception {
620         testInit("file:repo space", "repo space");
621     }
622 
623     @Test
624     void testInit_HierarchicalUrl() throws Exception {
625         testInit("file:/repository", "/repository");
626     }
627 
628     @Test
629     void testInit_HierarchicalUrlTrailingSlash() throws Exception {
630         testInit("file:/repository/", "/repository");
631     }
632 
633     @Test
634     void testInit_HierarchicalUrlSpaces() throws Exception {
635         testInit("file:/repo%20space", "/repo space");
636     }
637 
638     @Test
639     void testInit_HierarchicalUrlSpacesDecoded() throws Exception {
640         testInit("file:/repo space", "/repo space");
641     }
642 
643     @Test
644     void testInit_HierarchicalUrlRoot() throws Exception {
645         testInit("file:/", "/");
646     }
647 
648     @Test
649     void testInit_HierarchicalUrlHostNoPath() throws Exception {
650         testInit("file://host/", "/");
651     }
652 
653     @Test
654     void testInit_HierarchicalUrlHostPath() throws Exception {
655         testInit("file://host/dir", "/dir");
656     }
657 
658     @Test
659     void testInit_NonDefaultFileSystemRelative() throws Exception {
660         try (FileSystem fs = Jimfs.newFileSystem(Configuration.unix())) {
661             Path path = fs.getPath("dir");
662             testInit(path.toUri().toASCIIString(), "/work/dir");
663         }
664     }
665 
666     @Test
667     void testInit_NonDefaultFileSystemAbsolute() throws Exception {
668         try (FileSystem fs = Jimfs.newFileSystem(Configuration.unix())) {
669             Path path = fs.getPath("/dir");
670             testInit(path.toUri().toASCIIString(), "/dir");
671         }
672     }
673 
674     private void testInit(String base, String expected) throws Exception {
675         newTransporter(base);
676         String exp = expected;
677         if (base.startsWith("file:")) {
678             // on def FileSystem we do extra dance that we do NOT do in case of non-default File Systems:
679             // like accepting weird URLs/URIs and resolving/abs against CWD, that may not be defined in case
680             // of non-default FileSystems (OTOH, they MAY do it, like JIMFS has $cwd="/work")
681             exp = Paths.get(expected).toAbsolutePath().toString();
682         }
683         // compare path string representation only, as otherwise (Object equality) it would fail
684         // if we end up with non default FS for example
685         assertEquals(exp, ((FileTransporter) transporter).getBasePath().toString());
686     }
687 }