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     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                 // zip it up and change uri to zip file path
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                 // Doing this on windows FS is not possible (immediately create then delete link) due windows lock
280                 // semantics. While other OS do perform this test OK, it fails on Windows with AccessDeniedEx.
281                 // The file becomes deletable on Windows after some arbitrary time, but let's not fiddle with that in
282                 // this UT.
283                 // assertTrue(Files.deleteIfExists(file), i + ", " + file.toAbsolutePath());
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             // on def FileSystem we do extra dance that we do NOT do in case of non-default File Systems:
596             // like accepting weird URLs/URIs and resolving/abs against CWD, that may not be defined in case
597             // of non-default FileSystems (OTOH, they MAY do it, like JIMFS has $cwd="/work")
598             exp = Paths.get(expected).toAbsolutePath().toString();
599         }
600         // compare path string representation only, as otherwise (Object equality) it would fail
601         // if we end up with non default FS for example
602         assertEquals(exp, ((FileTransporter) transporter).getBasePath().toString());
603     }
604 }