1 /*
2  * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License").
5  * You may not use this file except in compliance with the License.
6  * A copy of the License is located at
7  *
8  *  http://aws.amazon.com/apache2.0
9  *
10  * or in the "license" file accompanying this file. This file is distributed
11  * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12  * express or implied. See the License for the specific language governing
13  * permissions and limitations under the License.
14  */
15 
16 package software.amazon.awssdk.transfer.s3.internal;
17 
18 import static org.assertj.core.api.Assertions.assertThat;
19 import static org.assertj.core.api.Assertions.assertThatThrownBy;
20 import static org.mockito.ArgumentMatchers.any;
21 import static org.mockito.Mockito.mock;
22 import static org.mockito.Mockito.when;
23 
24 import com.google.common.jimfs.Configuration;
25 import com.google.common.jimfs.Jimfs;
26 import java.io.IOException;
27 import java.io.UncheckedIOException;
28 import java.nio.charset.StandardCharsets;
29 import java.nio.file.FileSystem;
30 import java.nio.file.Files;
31 import java.nio.file.Path;
32 import java.nio.file.Paths;
33 import java.util.Arrays;
34 import java.util.Collection;
35 import java.util.List;
36 import java.util.concurrent.CancellationException;
37 import java.util.concurrent.CompletableFuture;
38 import java.util.concurrent.TimeUnit;
39 import java.util.function.Consumer;
40 import java.util.function.Function;
41 import java.util.stream.Collectors;
42 import org.junit.jupiter.api.AfterAll;
43 import org.junit.jupiter.api.AfterEach;
44 import org.junit.jupiter.api.BeforeAll;
45 import org.junit.jupiter.api.BeforeEach;
46 import org.junit.jupiter.api.Test;
47 import org.junit.jupiter.params.ParameterizedTest;
48 import org.junit.jupiter.params.provider.MethodSource;
49 import org.mockito.ArgumentCaptor;
50 import software.amazon.awssdk.core.exception.SdkClientException;
51 import software.amazon.awssdk.services.s3.internal.crt.S3MetaRequestPauseObservable;
52 import software.amazon.awssdk.services.s3.model.PutObjectRequest;
53 import software.amazon.awssdk.services.s3.model.PutObjectResponse;
54 import software.amazon.awssdk.testutils.FileUtils;
55 import software.amazon.awssdk.transfer.s3.config.TransferRequestOverrideConfiguration;
56 import software.amazon.awssdk.transfer.s3.internal.model.DefaultFileUpload;
57 import software.amazon.awssdk.transfer.s3.internal.progress.DefaultTransferProgress;
58 import software.amazon.awssdk.transfer.s3.internal.progress.DefaultTransferProgressSnapshot;
59 import software.amazon.awssdk.transfer.s3.model.CompletedDirectoryUpload;
60 import software.amazon.awssdk.transfer.s3.model.CompletedFileUpload;
61 import software.amazon.awssdk.transfer.s3.model.DirectoryUpload;
62 import software.amazon.awssdk.transfer.s3.model.FileUpload;
63 import software.amazon.awssdk.transfer.s3.model.UploadDirectoryRequest;
64 import software.amazon.awssdk.transfer.s3.model.UploadFileRequest;
65 import software.amazon.awssdk.transfer.s3.progress.LoggingTransferListener;
66 import software.amazon.awssdk.transfer.s3.progress.TransferListener;
67 
68 public class UploadDirectoryHelperTest {
69     private FileSystem jimfs;
70     private Path directory;
71 
72     /**
73      * Local directory is needed to test symlinks because jimfs doesn't work well with symlinks
74      */
75     private static Path localDirectory;
76     private Function<UploadFileRequest, FileUpload> singleUploadFunction;
77     private UploadDirectoryHelper uploadDirectoryHelper;
78 
fileSystems()79     public static Collection<FileSystem> fileSystems() {
80         return Arrays.asList(Jimfs.newFileSystem(Configuration.unix()),
81                              Jimfs.newFileSystem(Configuration.osX()),
82                              Jimfs.newFileSystem(Configuration.windows()));
83     }
84 
85     @BeforeAll
setUp()86     public static void setUp() throws IOException {
87         localDirectory = createLocalTestDirectory();
88     }
89 
90     @AfterAll
tearDown()91     public static void tearDown() throws IOException {
92         FileUtils.cleanUpTestDirectory(localDirectory);
93     }
94 
95     @BeforeEach
methodSetup()96     public void methodSetup() throws IOException {
97         jimfs = Jimfs.newFileSystem();
98         directory = jimfs.getPath("test");
99         Files.createDirectory(directory);
100         Files.createFile(jimfs.getPath("test/1"));
101         Files.createFile(jimfs.getPath("test/2"));
102 
103         singleUploadFunction = mock(Function.class);
104 
105         uploadDirectoryHelper = new UploadDirectoryHelper(TransferManagerConfiguration.builder().build(), singleUploadFunction);
106     }
107 
108     @AfterEach
methodCleanup()109     public void methodCleanup() throws IOException {
110         jimfs.close();
111     }
112 
113     @Test
uploadDirectory_cancel_shouldCancelAllFutures()114     void uploadDirectory_cancel_shouldCancelAllFutures() {
115         CompletableFuture<CompletedFileUpload> future = new CompletableFuture<>();
116         FileUpload fileUpload = newUpload(future);
117 
118         CompletableFuture<CompletedFileUpload> future2 = new CompletableFuture<>();
119         FileUpload fileUpload2 = newUpload(future2);
120 
121         when(singleUploadFunction.apply(any(UploadFileRequest.class))).thenReturn(fileUpload, fileUpload2);
122 
123         DirectoryUpload uploadDirectory =
124             uploadDirectoryHelper.uploadDirectory(UploadDirectoryRequest.builder()
125                                                                         .source(directory)
126                                                                         .bucket("bucket")
127                                                                         .build());
128 
129         uploadDirectory.completionFuture().cancel(true);
130 
131         assertThatThrownBy(() -> future.get(1, TimeUnit.SECONDS))
132             .isInstanceOf(CancellationException.class);
133 
134         assertThatThrownBy(() -> future2.get(1, TimeUnit.SECONDS))
135             .isInstanceOf(CancellationException.class);
136     }
137 
138     @Test
uploadDirectory_allUploadsSucceed_failedUploadsShouldBeEmpty()139     void uploadDirectory_allUploadsSucceed_failedUploadsShouldBeEmpty() throws Exception {
140         PutObjectResponse putObjectResponse = PutObjectResponse.builder().eTag("1234").build();
141         CompletedFileUpload completedFileUpload = CompletedFileUpload.builder().response(putObjectResponse).build();
142         CompletableFuture<CompletedFileUpload> successfulFuture = new CompletableFuture<>();
143 
144         FileUpload fileUpload = newUpload(successfulFuture);
145         successfulFuture.complete(completedFileUpload);
146 
147         PutObjectResponse putObjectResponse2 = PutObjectResponse.builder().eTag("5678").build();
148         CompletedFileUpload completedFileUpload2 = CompletedFileUpload.builder().response(putObjectResponse2).build();
149         CompletableFuture<CompletedFileUpload> successfulFuture2 = new CompletableFuture<>();
150         FileUpload fileUpload2 = newUpload(successfulFuture2);
151         successfulFuture2.complete(completedFileUpload2);
152 
153         when(singleUploadFunction.apply(any(UploadFileRequest.class))).thenReturn(fileUpload, fileUpload2);
154 
155         DirectoryUpload uploadDirectory =
156             uploadDirectoryHelper.uploadDirectory(UploadDirectoryRequest.builder()
157                                                                         .source(directory)
158                                                                         .bucket("bucket")
159                                                                         .build());
160 
161         CompletedDirectoryUpload completedDirectoryUpload = uploadDirectory.completionFuture().get(5, TimeUnit.SECONDS);
162 
163         assertThat(completedDirectoryUpload.failedTransfers()).isEmpty();
164     }
165 
166     @Test
uploadDirectory_partialSuccess_shouldProvideFailedUploads()167     void uploadDirectory_partialSuccess_shouldProvideFailedUploads() throws Exception {
168         PutObjectResponse putObjectResponse = PutObjectResponse.builder().eTag("1234").build();
169         CompletedFileUpload completedFileUpload = CompletedFileUpload.builder().response(putObjectResponse).build();
170         CompletableFuture<CompletedFileUpload> successfulFuture = new CompletableFuture<>();
171         FileUpload fileUpload = newUpload(successfulFuture);
172         successfulFuture.complete(completedFileUpload);
173 
174         SdkClientException exception = SdkClientException.create("failed");
175         CompletableFuture<CompletedFileUpload> failedFuture = new CompletableFuture<>();
176         FileUpload fileUpload2 = newUpload(failedFuture);
177         failedFuture.completeExceptionally(exception);
178 
179         when(singleUploadFunction.apply(any(UploadFileRequest.class))).thenReturn(fileUpload, fileUpload2);
180 
181         DirectoryUpload uploadDirectory =
182             uploadDirectoryHelper.uploadDirectory(UploadDirectoryRequest.builder()
183                                                                         .source(directory)
184                                                                         .bucket("bucket")
185                                                                         .build());
186 
187         CompletedDirectoryUpload completedDirectoryUpload = uploadDirectory.completionFuture().get(5, TimeUnit.SECONDS);
188 
189         assertThat(completedDirectoryUpload.failedTransfers()).hasSize(1);
190         assertThat(completedDirectoryUpload.failedTransfers().iterator().next().exception()).isEqualTo(exception);
191         assertThat(completedDirectoryUpload.failedTransfers().iterator().next().request().source().toString())
192             .isEqualTo("test" + directory.getFileSystem().getSeparator() + "2");
193     }
194 
195     @Test
uploadDirectory_withRequestTransformer_usesRequestTransformer()196     void uploadDirectory_withRequestTransformer_usesRequestTransformer() throws Exception {
197         PutObjectResponse putObjectResponse = PutObjectResponse.builder().eTag("1234").build();
198         CompletedFileUpload completedFileUpload = CompletedFileUpload.builder().response(putObjectResponse).build();
199         CompletableFuture<CompletedFileUpload> successfulFuture = new CompletableFuture<>();
200 
201         FileUpload upload = newUpload(successfulFuture);
202         successfulFuture.complete(completedFileUpload);
203 
204         PutObjectResponse putObjectResponse2 = PutObjectResponse.builder().eTag("5678").build();
205         CompletedFileUpload completedFileUpload2 = CompletedFileUpload.builder().response(putObjectResponse2).build();
206         CompletableFuture<CompletedFileUpload> successfulFuture2 = new CompletableFuture<>();
207         FileUpload upload2 = newUpload(successfulFuture2);
208         successfulFuture2.complete(completedFileUpload2);
209 
210         ArgumentCaptor<UploadFileRequest> uploadRequestCaptor = ArgumentCaptor.forClass(UploadFileRequest.class);
211 
212         when(singleUploadFunction.apply(uploadRequestCaptor.capture())).thenReturn(upload, upload2);
213 
214         Path newSource = Paths.get("/new/path");
215         PutObjectRequest newPutObjectRequest = PutObjectRequest.builder().build();
216         TransferRequestOverrideConfiguration newOverrideConfig = TransferRequestOverrideConfiguration.builder()
217                                                                                                      .build();
218         List<TransferListener> listeners = Arrays.asList(LoggingTransferListener.create());
219 
220         Consumer<UploadFileRequest.Builder> uploadFileRequestTransformer = r -> r.source(newSource)
221                                                                                  .putObjectRequest(newPutObjectRequest)
222                                                                                  .transferListeners(listeners);
223 
224         uploadDirectoryHelper.uploadDirectory(UploadDirectoryRequest.builder()
225                                                                     .source(directory)
226                                                                     .bucket("bucket")
227                                                                     .uploadFileRequestTransformer(uploadFileRequestTransformer)
228                                                                     .build())
229                              .completionFuture()
230                              .get(5, TimeUnit.SECONDS);
231 
232         List<UploadFileRequest> uploadRequests = uploadRequestCaptor.getAllValues();
233         assertThat(uploadRequests).hasSize(2);
234         assertThat(uploadRequests).element(0).satisfies(r -> {
235             assertThat(r.source()).isEqualTo(newSource);
236             assertThat(r.putObjectRequest()).isEqualTo(newPutObjectRequest);
237             assertThat(r.transferListeners()).isEqualTo(listeners);
238         });
239         assertThat(uploadRequests).element(1).satisfies(r -> {
240             assertThat(r.source()).isEqualTo(newSource);
241             assertThat(r.putObjectRequest()).isEqualTo(newPutObjectRequest);
242             assertThat(r.transferListeners()).isEqualTo(listeners);
243         });
244     }
245 
246     @ParameterizedTest
247     @MethodSource("fileSystems")
uploadDirectory_defaultSetting_shouldRecursivelyUpload(FileSystem fileSystem)248     void uploadDirectory_defaultSetting_shouldRecursivelyUpload(FileSystem fileSystem) {
249         directory = createJimFsTestDirectory(fileSystem);
250         ArgumentCaptor<UploadFileRequest> requestArgumentCaptor = ArgumentCaptor.forClass(UploadFileRequest.class);
251 
252         when(singleUploadFunction.apply(requestArgumentCaptor.capture()))
253             .thenReturn(completedUpload());
254         DirectoryUpload uploadDirectory =
255             uploadDirectoryHelper.uploadDirectory(UploadDirectoryRequest.builder()
256                                                                         .source(directory)
257                                                                         .bucket("bucket")
258                                                                         .followSymbolicLinks(false)
259                                                                         .build());
260         uploadDirectory.completionFuture().join();
261 
262         List<UploadFileRequest> actualRequests = requestArgumentCaptor.getAllValues();
263         actualRequests.forEach(r -> assertThat(r.putObjectRequest().bucket()).isEqualTo("bucket"));
264 
265         assertThat(actualRequests.size()).isEqualTo(3);
266 
267         List<String> keys =
268             actualRequests.stream().map(u -> u.putObjectRequest().key())
269                           .collect(Collectors.toList());
270 
271         assertThat(keys).containsOnly("bar.txt", "foo/1.txt", "foo/2.txt");
272     }
273 
274     @Test
uploadDirectory_depth1FollowSymlinkTrue_shouldOnlyUploadTopLevel()275     void uploadDirectory_depth1FollowSymlinkTrue_shouldOnlyUploadTopLevel() {
276         ArgumentCaptor<UploadFileRequest> requestArgumentCaptor = ArgumentCaptor.forClass(UploadFileRequest.class);
277 
278         when(singleUploadFunction.apply(requestArgumentCaptor.capture())).thenReturn(completedUpload());
279         DirectoryUpload uploadDirectory =
280             uploadDirectoryHelper.uploadDirectory(UploadDirectoryRequest.builder()
281                                                                         .source(localDirectory)
282                                                                         .bucket("bucket")
283                                                                         .maxDepth(1)
284                                                                         .followSymbolicLinks(true)
285                                                                         .build());
286         uploadDirectory.completionFuture().join();
287 
288         List<UploadFileRequest> actualRequests = requestArgumentCaptor.getAllValues();
289         List<String> keys =
290             actualRequests.stream().map(u -> u.putObjectRequest().key())
291                           .collect(Collectors.toList());
292 
293         assertThat(keys.size()).isEqualTo(2);
294         assertThat(keys).containsOnly("bar.txt", "symlink2");
295     }
296 
297     @Test
uploadDirectory_FollowSymlinkTrue_shouldIncludeLinkedFiles()298     void uploadDirectory_FollowSymlinkTrue_shouldIncludeLinkedFiles() {
299         ArgumentCaptor<UploadFileRequest> requestArgumentCaptor = ArgumentCaptor.forClass(UploadFileRequest.class);
300 
301         when(singleUploadFunction.apply(requestArgumentCaptor.capture())).thenReturn(completedUpload());
302         DirectoryUpload uploadDirectory =
303             uploadDirectoryHelper.uploadDirectory(UploadDirectoryRequest.builder()
304                                                                         .source(localDirectory)
305                                                                         .bucket("bucket")
306                                                                         .followSymbolicLinks(true)
307                                                                         .build());
308         uploadDirectory.completionFuture().join();
309 
310         List<UploadFileRequest> actualRequests = requestArgumentCaptor.getAllValues();
311         actualRequests.forEach(r -> assertThat(r.putObjectRequest().bucket()).isEqualTo("bucket"));
312 
313         List<String> keys =
314             actualRequests.stream().map(u -> u.putObjectRequest().key())
315                           .collect(Collectors.toList());
316 
317         assertThat(keys.size()).isEqualTo(5);
318         assertThat(keys).containsOnly("bar.txt", "foo/1.txt", "foo/2.txt", "symlink/2.txt", "symlink2");
319     }
320 
321     @ParameterizedTest
322     @MethodSource("fileSystems")
uploadDirectory_withPrefix_keysShouldHavePrefix(FileSystem fileSystem)323     void uploadDirectory_withPrefix_keysShouldHavePrefix(FileSystem fileSystem) {
324         directory = createJimFsTestDirectory(fileSystem);
325         ArgumentCaptor<UploadFileRequest> requestArgumentCaptor = ArgumentCaptor.forClass(UploadFileRequest.class);
326 
327         when(singleUploadFunction.apply(requestArgumentCaptor.capture())).thenReturn(completedUpload());
328         DirectoryUpload uploadDirectory =
329             uploadDirectoryHelper.uploadDirectory(UploadDirectoryRequest.builder()
330                                                                         .source(directory)
331                                                                         .bucket("bucket")
332                                                                         .s3Prefix("yolo")
333                                                                         .build());
334         uploadDirectory.completionFuture().join();
335 
336         List<String> keys =
337             requestArgumentCaptor.getAllValues().stream().map(u -> u.putObjectRequest().key())
338                                  .collect(Collectors.toList());
339 
340         assertThat(keys.size()).isEqualTo(3);
341         keys.forEach(r -> assertThat(r).startsWith("yolo/"));
342     }
343 
344     @ParameterizedTest
345     @MethodSource("fileSystems")
uploadDirectory_withDelimiter_shouldHonor(FileSystem fileSystem)346     void uploadDirectory_withDelimiter_shouldHonor(FileSystem fileSystem) {
347         directory = createJimFsTestDirectory(fileSystem);
348         ArgumentCaptor<UploadFileRequest> requestArgumentCaptor = ArgumentCaptor.forClass(UploadFileRequest.class);
349 
350         when(singleUploadFunction.apply(requestArgumentCaptor.capture())).thenReturn(completedUpload());
351         DirectoryUpload uploadDirectory =
352             uploadDirectoryHelper.uploadDirectory(UploadDirectoryRequest.builder()
353                                                                         .source(directory)
354                                                                         .bucket("bucket")
355                                                                         .s3Delimiter(",")
356                                                                         .s3Prefix("yolo")
357                                                                         .build());
358         uploadDirectory.completionFuture().join();
359 
360         List<String> keys =
361             requestArgumentCaptor.getAllValues().stream().map(u -> u.putObjectRequest().key())
362                                  .collect(Collectors.toList());
363 
364         assertThat(keys.size()).isEqualTo(3);
365         assertThat(keys).containsOnly("yolo,foo,2.txt", "yolo,foo,1.txt", "yolo,bar.txt");
366     }
367 
368     @ParameterizedTest
369     @MethodSource("fileSystems")
uploadDirectory_maxLengthOne_shouldOnlyUploadTopLevel(FileSystem fileSystem)370     void uploadDirectory_maxLengthOne_shouldOnlyUploadTopLevel(FileSystem fileSystem) {
371         directory = createJimFsTestDirectory(fileSystem);
372         ArgumentCaptor<UploadFileRequest> requestArgumentCaptor = ArgumentCaptor.forClass(UploadFileRequest.class);
373 
374         when(singleUploadFunction.apply(requestArgumentCaptor.capture()))
375             .thenReturn(completedUpload());
376         DirectoryUpload uploadDirectory =
377             uploadDirectoryHelper.uploadDirectory(UploadDirectoryRequest.builder()
378                                                                         .source(directory)
379                                                                         .bucket("bucket")
380                                                                         .maxDepth(1)
381                                                                         .build());
382         uploadDirectory.completionFuture().join();
383 
384         List<UploadFileRequest> actualRequests = requestArgumentCaptor.getAllValues();
385         actualRequests.forEach(r -> assertThat(r.putObjectRequest().bucket()).isEqualTo("bucket"));
386 
387         assertThat(actualRequests.size()).isEqualTo(1);
388 
389         List<String> keys =
390             actualRequests.stream().map(u -> u.putObjectRequest().key())
391                           .collect(Collectors.toList());
392 
393         assertThat(keys).containsOnly("bar.txt");
394     }
395 
396 
397     @ParameterizedTest
398     @MethodSource("fileSystems")
uploadDirectory_directoryNotExist_shouldCompleteFutureExceptionally(FileSystem fileSystem)399     void uploadDirectory_directoryNotExist_shouldCompleteFutureExceptionally(FileSystem fileSystem) {
400         directory = createJimFsTestDirectory(fileSystem);
401         assertThatThrownBy(() -> uploadDirectoryHelper.uploadDirectory(UploadDirectoryRequest.builder().source(Paths.get(
402                                                                                                  "randomstringneverexistas234ersaf1231"))
403                                                                                              .bucket("bucketName").build()).completionFuture().join())
404             .hasMessageContaining("does not exist").hasCauseInstanceOf(IllegalArgumentException.class);
405     }
406 
407     @Test
uploadDirectory_notDirectory_shouldCompleteFutureExceptionally()408     void uploadDirectory_notDirectory_shouldCompleteFutureExceptionally() {
409         assertThatThrownBy(() -> uploadDirectoryHelper.uploadDirectory(UploadDirectoryRequest.builder()
410                                                                                              .source(Paths.get(localDirectory.toString(), "symlink"))
411                                                                                              .bucket("bucketName").build()).completionFuture().join())
412             .hasMessageContaining("is not a directory").hasCauseInstanceOf(IllegalArgumentException.class);
413     }
414 
415     @Test
uploadDirectory_notDirectoryFollowSymlinkTrue_shouldCompleteSuccessfully()416     void uploadDirectory_notDirectoryFollowSymlinkTrue_shouldCompleteSuccessfully() {
417         ArgumentCaptor<UploadFileRequest> requestArgumentCaptor = ArgumentCaptor.forClass(UploadFileRequest.class);
418 
419         when(singleUploadFunction.apply(requestArgumentCaptor.capture())).thenReturn(completedUpload());
420         DirectoryUpload uploadDirectory = uploadDirectoryHelper.uploadDirectory(UploadDirectoryRequest.builder()
421                                                                                                       .followSymbolicLinks(true)
422                                                                                                       .source(Paths.get(localDirectory.toString(), "symlink"))
423                                                                                                       .bucket("bucket").build());
424 
425         uploadDirectory.completionFuture().join();
426 
427         List<UploadFileRequest> actualRequests = requestArgumentCaptor.getAllValues();
428         actualRequests.forEach(r -> assertThat(r.putObjectRequest().bucket()).isEqualTo("bucket"));
429 
430         assertThat(actualRequests.size()).isEqualTo(1);
431 
432         List<String> keys =
433             actualRequests.stream().map(u -> u.putObjectRequest().key())
434                           .collect(Collectors.toList());
435 
436         assertThat(keys).containsOnly("2.txt");
437     }
438 
completedUpload()439     private DefaultFileUpload completedUpload() {
440         return new DefaultFileUpload(CompletableFuture.completedFuture(CompletedFileUpload.builder()
441                                                                                           .response(PutObjectResponse.builder().build())
442                                                                                           .build()),
443                                      new DefaultTransferProgress(DefaultTransferProgressSnapshot.builder()
444                                                                                                 .transferredBytes(0L)
445                                                                                                 .build()),
446                                      UploadFileRequest.builder()
447                                                       .source(Paths.get(".")).putObjectRequest(b -> b.bucket("bucket").key("key"))
448                                                       .build());
449     }
450 
newUpload(CompletableFuture<CompletedFileUpload> future)451     private FileUpload newUpload(CompletableFuture<CompletedFileUpload> future) {
452         return new DefaultFileUpload(future,
453                                      new DefaultTransferProgress(DefaultTransferProgressSnapshot.builder()
454                                                                                                 .transferredBytes(0L)
455                                                                                                 .build()),
456                                      UploadFileRequest.builder()
457                                                       .putObjectRequest(p -> p.key("key").bucket("bucket")).source(Paths.get(
458                                                           "test.txt"))
459                                                       .build());
460     }
461 
createJimFsTestDirectory(FileSystem fileSystem)462     private Path createJimFsTestDirectory(FileSystem fileSystem) {
463 
464         try {
465             return createJmfsTestDirectory(fileSystem);
466         } catch (IOException exception) {
467             throw new UncheckedIOException(exception);
468         }
469 
470     }
471 
createLocalTestDirectory()472     private static Path createLocalTestDirectory() {
473 
474         try {
475             return createLocalTestDirectoryWithSymLink();
476         } catch (IOException exception) {
477             throw new UncheckedIOException(exception);
478         }
479     }
480 
481     /**
482      * Create a test directory with the following structure - test1 - foo - 1.txt - 2.txt - bar.txt - symlink -> test2 - symlink2
483      * -> test3/4.txt - test2 - 2.txt - test3 - 4.txt
484      */
createLocalTestDirectoryWithSymLink()485     private static Path createLocalTestDirectoryWithSymLink() throws IOException {
486         Path directory = Files.createTempDirectory("test1");
487         Path anotherDirectory = Files.createTempDirectory("test2");
488         Path thirdDirectory = Files.createTempDirectory("test3");
489 
490         String directoryName = directory.toString();
491         String anotherDirectoryName = anotherDirectory.toString();
492 
493         Files.createDirectory(Paths.get(directory + "/foo"));
494 
495         Files.write(Paths.get(directoryName, "bar.txt"), "bar".getBytes(StandardCharsets.UTF_8));
496         Files.write(Paths.get(directoryName, "foo/1.txt"), "1".getBytes(StandardCharsets.UTF_8));
497         Files.write(Paths.get(directoryName, "foo/2.txt"), "2".getBytes(StandardCharsets.UTF_8));
498 
499         Files.write(Paths.get(anotherDirectoryName, "2.txt"), "2".getBytes(StandardCharsets.UTF_8));
500         Files.write(Paths.get(thirdDirectory.toString(), "3.txt"), "3".getBytes(StandardCharsets.UTF_8));
501 
502         Files.createSymbolicLink(Paths.get(directoryName, "symlink"), anotherDirectory);
503         Files.createSymbolicLink(Paths.get(directoryName, "symlink2"), Paths.get(thirdDirectory.toString(), "3.txt"));
504         return directory;
505     }
506 
507     /**
508      * Create a test directory with the following structure - test1 - foo - 1.txt - 2.txt - bar.txt
509      */
createJmfsTestDirectory(FileSystem jimfs)510     private Path createJmfsTestDirectory(FileSystem jimfs) throws IOException {
511         String directoryName = "test";
512         Path directory = jimfs.getPath(directoryName);
513 
514         Files.createDirectory(directory);
515 
516         Files.createDirectory(jimfs.getPath(directoryName + "/foo"));
517 
518         Files.write(jimfs.getPath(directoryName, "bar.txt"), "bar".getBytes(StandardCharsets.UTF_8));
519         Files.write(jimfs.getPath(directoryName, "foo", "1.txt"), "1".getBytes(StandardCharsets.UTF_8));
520         Files.write(jimfs.getPath(directoryName, "foo", "2.txt"), "2".getBytes(StandardCharsets.UTF_8));
521         return directory;
522     }
523 }
524