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