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