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.apache.rat.document;
20  
21  import java.io.BufferedReader;
22  import java.io.File;
23  import java.io.FileFilter;
24  import java.io.FileReader;
25  import java.io.FileWriter;
26  import java.io.FilenameFilter;
27  import java.io.IOException;
28  import java.nio.charset.StandardCharsets;
29  import java.nio.file.Path;
30  import java.util.ArrayList;
31  import java.util.Arrays;
32  import java.util.List;
33  import java.util.stream.Stream;
34  
35  import org.apache.rat.config.exclusion.ExclusionUtils;
36  import org.apache.rat.document.DocumentName.FSInfo;
37  
38  import org.junit.jupiter.api.Test;
39  import org.junit.jupiter.params.ParameterizedTest;
40  import org.junit.jupiter.params.provider.Arguments;
41  import org.junit.jupiter.params.provider.FieldSource;
42  import org.junit.jupiter.params.provider.MethodSource;
43  import org.junit.jupiter.params.provider.ValueSource;
44  
45  import static org.assertj.core.api.Assertions.assertThat;
46  import static org.assertj.core.api.Assertions.assertThatThrownBy;
47  import static org.mockito.Mockito.mock;
48  import static org.mockito.Mockito.when;
49  import static org.mockito.ArgumentMatchers.any;
50  
51  public class DocumentNameTest {
52      private static final FSInfo[] TEST_SUITE = FSInfoTest.TEST_SUITE;
53  
54      @ParameterizedTest
55      @ValueSource(booleans = {true, false})
56      void splitRootRespectsCaseSensitivity(boolean caseSensitive) {
57          FSInfo info = new FSInfo("test", "\\", caseSensitive, List.of("F:\\"));
58          String path = "f:\\workspace\\target\\rat.txt";
59          var result = DocumentName.builder(info).splitRoot(path);
60  
61          assertThat(result.getLeft()).isEqualTo(caseSensitive ? "" : "F:\\");
62          assertThat(result.getRight()).isEqualTo(caseSensitive ? path : "workspace\\target\\rat.txt");
63      }
64  
65      /**
66       * Create a list of mocked files from the specified directory.
67       * @param directory the native directory to read.
68       * @param fsInfo the file system to mock the files in.
69       * @return an array of mocked files in the file system.
70       */
71      private static File[] listFiles(File directory, FSInfo fsInfo) {
72          File[] fileList = directory.listFiles();
73          return fileList == null ? null : Arrays.stream(fileList).map(f -> mkFile(f, fsInfo)).toArray(File[]::new);
74      }
75  
76      /**
77       * Create an array of mocked files from the specified directory then apply applying a file filter.
78       * @param directory the native directory to read files from.
79       * @param fsInfo the file system to create the mocked files in.
80       * @param filter the filter to apply to the mocked files.
81       * @return the array of mocked files that pass the filter.
82       */
83      private static File[] listFiles(File directory, FSInfo fsInfo, FileFilter filter) {
84          File[] fileList = directory.listFiles();
85          return fileList == null ? null : Arrays.stream(fileList).map(f -> mkFile(f, fsInfo)).filter(filter::accept).toArray(File[]::new);
86      }
87  
88      /**
89       * Create an array of mocked files from the specified directory then apply applying a file filter.
90       * @param directory the native directory to read files from.
91       * @param fsInfo the file system to create the mocked files in.
92       * @param filter the filter to apply to the mocked files.
93       * @return the array of mocked files that pass the filter.
94       */
95      private static File[] listFiles(File directory, FSInfo fsInfo, FilenameFilter filter) {
96          File[] fileList = directory.listFiles();
97          return fileList == null ? null : Arrays.stream(fileList).map(f -> mkFile(f, fsInfo)).filter(x -> filter.accept(x, x.getName())).toArray(File[]::new);
98      }
99  
100     /**
101      * Creates a mocked file on the specified file system with the
102      * @param file the name of the native file.
103      * @param fsInfo the file system to mock the file in.
104      * @return the mocked file in the specified file system
105      */
106     public static File mkFile(final File file, final FSInfo fsInfo) {
107         File mockedFile = mock(File.class);
108         when(mockedFile.listFiles()).thenAnswer( env -> listFiles(file, fsInfo));
109         when(mockedFile.listFiles(any(FilenameFilter.class))).thenAnswer( env -> listFiles(file, fsInfo, env.getArgument(0, FilenameFilter.class)));
110         when(mockedFile.listFiles(any(FileFilter.class))).thenAnswer(env -> listFiles(file, fsInfo, env.getArgument(0, FileFilter.class)));
111         when(mockedFile.getName()).thenReturn(ExclusionUtils.convertSeparator(file.getName(), FSInfoTest.DEFAULT.dirSeparator(), fsInfo.dirSeparator()));
112         when(mockedFile.getAbsolutePath()).thenReturn(ExclusionUtils.convertSeparator(file.getAbsolutePath(), FSInfoTest.DEFAULT.dirSeparator(), fsInfo.dirSeparator()));
113         when(mockedFile.isFile()).thenAnswer(env -> file.isFile());
114         when(mockedFile.exists()).thenAnswer(emv -> file.exists());
115         when(mockedFile.isDirectory()).thenAnswer(env -> file.isDirectory());
116 
117         return mockedFile;
118     }
119 
120     /**
121      * Verifies that {@code resolve()} works correctly.
122      * @param fsInfo the file system under test.
123      * @param base the DocumentName to resolve from.
124      * @param toResolve the string to resolve.
125      * @param expected the expected DocumentName after resolution.
126      */
127     @ParameterizedTest(name = "{index} {0} {1} {3}")
128     @MethodSource("resolveTestData")
129     void resolveTest(DocumentName.FSInfo fsInfo, String root, DocumentName base, String toResolve, DocumentName expected) {
130        DocumentName actual = base.resolve(toResolve);
131        assertThat(actual.getName()).isEqualTo(expected.getName());
132     }
133 
134     private static Stream<Arguments> resolveTestData() {
135         List<Arguments> lst = new ArrayList<>();
136         DocumentName base;
137         DocumentName expected;
138         for (DocumentName.FSInfo fsInfo : TEST_SUITE) {
139             String root = fsInfo.roots()[0];
140             for (String baseName : List.of(root, root + fsInfo.mkPath("from", "base"))) {
141                 for (String testRoot : fsInfo.roots()) {
142                     String name = testRoot + fsInfo.mkPath("", "dir", fsInfo.toString());
143                     base = DocumentName.builder(fsInfo).setName(name).setBaseName(baseName).build();
144                     lst.add(Arguments.of(fsInfo, base.getName(),  base, null, base));
145 
146                     lst.add(Arguments.of(fsInfo, base.getName(), base, "", base));
147 
148                     lst.add(Arguments.of(fsInfo, base.getName(), base, "  ", base));
149 
150                     expected = DocumentName.builder(fsInfo).setName(fsInfo.mkPath(name, "relative")).setBaseName(baseName).build();
151                     lst.add(Arguments.of(fsInfo, base.getName(), base, "relative", expected));
152 
153                     expected = DocumentName.builder(fsInfo).setRoot(testRoot).setName(fsInfo.mkPath("", "from", "root")).setBaseName(baseName).build();
154                     lst.add(Arguments.of(fsInfo, base.getName(), base, fsInfo.mkPath("", "from", "root"), expected));
155 
156                     expected = DocumentName.builder(fsInfo).setRoot(testRoot).setName(fsInfo.mkPath("dir", "up", "and", "down")).setBaseName(baseName).build();
157                     lst.add(Arguments.of(fsInfo, base.getName(), base, fsInfo.mkPath("..", "up", "and", "down"), expected));
158 
159                     expected = DocumentName.builder(fsInfo).setRoot(testRoot).setName(fsInfo.mkPath("", "from", "root")).setBaseName(baseName).build();
160                     String wrongSeparator = fsInfo.dirSeparator().equals("/") ? "\\" : "/";
161                     lst.add(Arguments.of(fsInfo, base.getName(), base, String.join(wrongSeparator, "", "from", "root"), expected));
162 
163                     expected = DocumentName.builder(fsInfo).setRoot(testRoot).setName(fsInfo.mkPath("dir", "up", "and", "down")).setBaseName(baseName).build();
164                     lst.add(Arguments.of(fsInfo, base.getName(), base, String.join(wrongSeparator, "..", "up", "and", "down"), expected));
165                 }
166             }
167         }
168         return lst.stream();
169     }
170 
171     @Test
172     void resolveWithMultipleRootsTest() {
173         // these tests exist because windows has multiple roots.
174         DocumentName.FSInfo fsInfo = FSInfoTest.WINDOWS;
175         DocumentName base = DocumentName.builder(fsInfo).setName(fsInfo.mkPath("", "dir", fsInfo.toString()))
176                 .setBaseName("").build();
177 
178         String resolveName = fsInfo.roots()[1] + fsInfo.mkPath("dir", fsInfo.toString());
179         assertThatThrownBy(() -> base.resolve(resolveName))
180                 .as(resolveName)
181                 .hasMessageContaining(String.format("%s does not start with %s", resolveName, base.getName()))
182                 .isInstanceOf(IllegalArgumentException.class);
183 
184         String resolveName2 = fsInfo.roots()[0] + fsInfo.mkPath("dir", fsInfo.toString(), "thing");
185         assertThat(base.resolve(resolveName2).getName())
186                 .isEqualTo(resolveName2);
187     }
188 
189     void testNoRootSpecified() {
190         // no root specified
191         assertThat(DocumentName.startsWithRootOrSeparator("X:/candidate", null, "/")).isFalse();
192         assertThat(DocumentName.startsWithRootOrSeparator("X:/candidate", "", "/")).isFalse();
193         assertThat(DocumentName.startsWithRootOrSeparator("X;/candidate", "  ", "/")).isFalse();
194         assertThat(DocumentName.startsWithRootOrSeparator("/candidate", null, "/")).isTrue();
195         assertThat(DocumentName.startsWithRootOrSeparator("/candidate", "", "/")).isTrue();
196         assertThat(DocumentName.startsWithRootOrSeparator("/candidate", "  ", "/")).isTrue();
197         assertThat(DocumentName.startsWithRootOrSeparator("candidate", null, "/")).isFalse();
198         assertThat(DocumentName.startsWithRootOrSeparator("candidate", "", "/")).isFalse();
199         assertThat(DocumentName.startsWithRootOrSeparator("candidate", "  ", "/")).isFalse();
200     }
201 
202     @Test
203     void startsWithRootOrSeparatorTest() {
204         assertThat(DocumentName.startsWithRootOrSeparator("X:/candidate", "X:/", "/")).isTrue();
205         assertThat(DocumentName.startsWithRootOrSeparator("X:/candidate", "Y:/", "/")).isFalse();
206         assertThat(DocumentName.startsWithRootOrSeparator("/candidate", "X:/", "/")).isTrue();
207         assertThat(DocumentName.startsWithRootOrSeparator("\\candidate", "Y:/", "/")).isFalse();
208         assertThat(DocumentName.startsWithRootOrSeparator("candidate", "Y:/", "/")).isFalse();
209         assertThat(DocumentName.startsWithRootOrSeparator("Y:candidate", "Y:/", "/")).isFalse();
210         assertThat(DocumentName.startsWithRootOrSeparator(null, "Y:/", "/")).isFalse();
211         assertThat(DocumentName.startsWithRootOrSeparator("", "Y:/", "/")).isFalse();
212         assertThat(DocumentName.startsWithRootOrSeparator("  ", "Y:/", "/")).isFalse();
213 
214         testNoRootSpecified();
215     }
216 
217     @ParameterizedTest
218     @FieldSource("TEST_SUITE")
219     void localizeTest(FSInfo fsInfo) {
220         DocumentName documentName = DocumentName.builder(fsInfo).setName(
221                 fsInfo.mkPath("", "a", "b", "c"))
222                 .setBaseName(fsInfo.mkPath("", "a")).build();
223 
224         assertThat(documentName.localized()).isEqualTo(fsInfo.mkPath("", "b", "c"));
225         assertThat(documentName.localized("-")).isEqualTo("-b-c");
226 
227         documentName = DocumentName.builder(fsInfo).setName(
228                         fsInfo.mkPath("", "a", "b", "c"))
229                 .setBaseName(fsInfo.mkPath("", "z")).build();
230 
231         assertThat(documentName.localized()).isEqualTo(fsInfo.mkPath("", "a", "b", "c"));
232 
233         if (fsInfo.roots().length > 1) {
234             documentName = DocumentName.builder(fsInfo).setName(
235                             fsInfo.mkPath("", "a", "b", "c"))
236                     .setBaseName(fsInfo.mkPath("", "z"))
237                     .setRoot(fsInfo.roots()[1]).build();
238             assertThat(documentName.localized()).isEqualTo(fsInfo.mkPath("", "a", "b", "c"));
239         }
240     }
241 
242     @Test
243     void asFileTest() throws IOException {
244         File expected = File.createTempFile("docNameTest", ".txt");
245         try (FileWriter fw = new FileWriter(expected, StandardCharsets.UTF_8)) {
246             fw.write("Hello world");
247         }
248         DocumentName underTest = DocumentName.builder(expected).build();
249         File actual = underTest.asFile();
250         try (FileReader fr = new FileReader(actual, StandardCharsets.UTF_8);
251         BufferedReader br = new BufferedReader(fr)) {
252             assertThat(br.readLine()).isEqualTo("Hello world");
253         }
254     }
255 
256     @Test
257     void asPathTest() throws IOException {
258         File expected = File.createTempFile("docNameTest", ".txt");
259         try (FileWriter fw = new FileWriter(expected, StandardCharsets.UTF_8)) {
260             fw.write("Hello world");
261         }
262         DocumentName underTest = DocumentName.builder(expected).build();
263         Path actual = underTest.asPath();
264         Path root = Path.of(underTest.getRoot());
265         File file = root.resolve(actual).toFile();
266         try (FileReader fr = new FileReader(file, StandardCharsets.UTF_8);
267              BufferedReader br = new BufferedReader(fr)) {
268             assertThat(br.readLine()).isEqualTo("Hello world");
269         }
270     }
271 
272     @ParameterizedTest(name = "{index} {0} {1}")
273     @MethodSource("archiveEntryTestData")
274     void archiveEntryNameTest(String os, String testName, DocumentName archiveName, String root, String separator, String baseName,
275                               String localizedName) {
276         assertThat(archiveName.getRoot()).as("root").isEqualTo(root);
277         assertThat(archiveName.getDirectorySeparator()).as("separator").isEqualTo(separator);
278         assertThat(archiveName.getBaseName()).as("baseName").isEqualTo(baseName);
279         assertThat(archiveName.localized()).as("localized").isEqualTo(localizedName);
280         assertThat(archiveName.getName()).as("name").isEqualTo(baseName + localizedName);
281         if (!separator.equals(archiveName.fsInfo().dirSeparator()))
282         {
283             String newBaseName = separator.equals("/") ? baseName.replace('\\', '/') : baseName.replace('/', '\\');
284             assertThat(archiveName.localized(separator)).as("localized(x)").isEqualTo(newBaseName + localizedName);
285         }
286     }
287 
288     static List<Arguments> archiveEntryTestData() {
289         List<Arguments> lst = new ArrayList<>();
290 
291         for (FSInfo fsInfo :  FSInfoTest.TEST_SUITE) {
292             String os = fsInfo.toString();
293             String root = fsInfo.roots()[0];
294             String baseName = root + "archives";
295             String simpleName = String.format("%sanArchive.zip", fsInfo.dirSeparator());
296             String entryName = "./anArchiveEntry.txt";
297             DocumentName archiveName = DocumentName.builder(fsInfo).setName(baseName + simpleName).setBaseName(baseName).build();
298             lst.add(Arguments.of(os, "archive name", archiveName, root, fsInfo.dirSeparator(), baseName, simpleName));
299 
300             ArchiveEntryName archiveEntryName = new ArchiveEntryName(archiveName, entryName);
301             baseName = archiveName.getName() + "#";
302             root = baseName + "/";
303             lst.add(Arguments.of(os, "archive entry name", archiveEntryName, root, "/", baseName, "/anArchiveEntry.txt"));
304 
305             // test with directory
306             entryName = "./someDir/anArchiveEntry.txt";
307             archiveEntryName = new ArchiveEntryName(archiveName, entryName);
308 
309             lst.add(Arguments.of(os, "archive entry with directory", archiveEntryName, root, "/", baseName, "/someDir/anArchiveEntry.txt"));
310         }
311         return lst;
312     }
313 }