1/*
2 * Copyright 2008 the original author or authors.
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 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package org.mockftpserver.fake.filesystem
17
18import java.io.IOException
19import java.io.InputStream
20import java.io.OutputStream
21import java.lang.reflect.InvocationTargetException
22import java.lang.reflect.Method
23import java.util.Collections
24import java.util.HashSet
25import java.util.List
26
27import org.apache.log4j.Logger
28import org.mockftpserver.core.util.IoUtil
29import org.mockftpserver.test.AbstractGroovyTest
30
31/**
32 * Abstract superclass for tests of FileSystem implementation classes. Contains common
33 * tests and test infrastructure.
34 *
35 * @version $Revision: $ - $Date: $
36 *
37 * @author Chris Mair
38 */
39abstract class AbstractFileSystemTest extends AbstractGroovyTest {
40
41     public static final FILENAME1 = "file1.txt"
42     public static final FILENAME2 = "file2.txt"
43     public static final DIR1 = "dir1"
44     public static final NEW_DIRNAME = "testdir"
45     public static final ILLEGAL_FILE = "xx/yy////z!<>?*z.txt"
46     public static final EXISTING_FILE_CONTENTS = "abc 123 %^& xxx"
47
48     // These must be set by the concrete subclass (in its constructor)
49     protected String NEW_DIR = null
50     protected String NEW_FILE = null
51     protected String EXISTING_DIR = null
52     protected String EXISTING_FILE = null
53     protected NO_SUCH_DIR = null
54     protected NO_SUCH_FILE = null
55
56     protected FileSystem fileSystem
57
58     //-------------------------------------------------------------------------
59     // Common Tests
60     //-------------------------------------------------------------------------
61
62     /**
63      * Test the exists() method
64      */
65     void testExists() {
66         assert !fileSystem.exists(NEW_FILE)
67         assert !fileSystem.exists(NEW_DIR)
68         assert !fileSystem.exists(ILLEGAL_FILE)
69         assert fileSystem.exists(EXISTING_FILE)
70         assert fileSystem.exists(EXISTING_DIR)
71
72         shouldFailWithMessageContaining("path") { fileSystem.exists(null) }
73     }
74
75     /**
76      * Test the isDirectory() method
77      */
78     void testIsDirectory() {
79         assert fileSystem.isDirectory(EXISTING_DIR)
80         assert !fileSystem.isDirectory(EXISTING_FILE)
81         assert !fileSystem.isDirectory(NO_SUCH_DIR)
82         assert !fileSystem.isDirectory(NO_SUCH_FILE)
83         assert !fileSystem.isDirectory(ILLEGAL_FILE)
84
85         shouldFailWithMessageContaining("path") { fileSystem.isDirectory(null) }
86     }
87
88     /**
89      * Test the isFile() method
90      */
91     void testIsFile() {
92         assert fileSystem.isFile(EXISTING_FILE)
93         assert !fileSystem.isFile(EXISTING_DIR)
94         assert !fileSystem.isFile(NO_SUCH_DIR)
95         assert !fileSystem.isFile(NO_SUCH_FILE)
96         assert !fileSystem.isFile(ILLEGAL_FILE)
97
98         shouldFailWithMessageContaining("path") { fileSystem.isFile(null) }
99     }
100
101     /**
102      * Test the createDirectory() method
103      */
104     void testCreateDirectory() {
105         assert !fileSystem.exists(NEW_DIR), "Before createDirectory"
106         assert fileSystem.createDirectory(NEW_DIR)
107         assert fileSystem.exists(NEW_DIR), "After createDirectory"
108
109         // Duplicate directory
110         assert !fileSystem.createDirectory(NEW_DIR), "Duplicate directory"
111
112         // The parent of the path does not exist
113         assert !fileSystem.createDirectory(NEW_DIR + "/abc/def"), "Parent does not exist"
114
115         shouldFailWithMessageContaining("path") { fileSystem.createDirectory(null) }
116     }
117
118     /**
119      * Test the createFile() method
120      */
121     void testCreateFile() {
122         assert !fileSystem.exists(NEW_FILE), "Before createFile"
123         assert fileSystem.createFile(NEW_FILE)
124         assert fileSystem.exists(NEW_FILE), "After createFile"
125
126         assert !fileSystem.createFile(NEW_FILE), "Duplicate"
127
128         // The parent of the path does not exist
129         shouldFail(FileSystemException) { fileSystem.createFile(NEW_DIR + "/abc/def") }
130
131         shouldFail(FileSystemException) { fileSystem.createFile(NO_SUCH_DIR) }
132         shouldFail(InvalidFilenameException) { fileSystem.createFile(ILLEGAL_FILE) }
133
134         shouldFailWithMessageContaining("path") { fileSystem.createFile(null) }
135     }
136
137     /**
138      * Test the createInputStream() method
139      */
140     void testCreateInputStream() {
141         InputStream input = fileSystem.createInputStream(EXISTING_FILE)
142         assert EXISTING_FILE_CONTENTS.getBytes() == IoUtil.readBytes(input)
143
144         shouldFail(FileSystemException) { fileSystem.createInputStream(NO_SUCH_FILE) }
145         shouldFail(FileSystemException) { fileSystem.createInputStream(EXISTING_DIR) }
146         shouldFail(FileSystemException) { fileSystem.createInputStream("") }
147
148         shouldFailWithMessageContaining("path") { fileSystem.createInputStream(null) }
149     }
150
151     /**
152      * Test the createOutputStream() method
153      */
154     void testCreateOutputStream() {
155         // New, empty file
156         OutputStream out = fileSystem.createOutputStream(NEW_FILE, false)
157         out.close()
158         verifyFileContents(fileSystem, NEW_FILE, "")
159
160         // Append = false
161         out = fileSystem.createOutputStream(NEW_FILE, false)
162         out.write(EXISTING_FILE_CONTENTS.getBytes())
163         out.close()
164         verifyFileContents(fileSystem, NEW_FILE, EXISTING_FILE_CONTENTS)
165
166         // Append = true
167         out = fileSystem.createOutputStream(NEW_FILE, true)
168         out.write(EXISTING_FILE_CONTENTS.getBytes())
169         String expectedContents = EXISTING_FILE_CONTENTS.concat(EXISTING_FILE_CONTENTS)
170         verifyFileContents(fileSystem, NEW_FILE, expectedContents)
171
172         // Yet another OutputStream, append=true (so should append to accumulated contents)
173         OutputStream out2 = fileSystem.createOutputStream(NEW_FILE, true)
174         out2.write("abc".getBytes())
175         out2.close()
176         expectedContents = expectedContents + "abc"
177         verifyFileContents(fileSystem, NEW_FILE, expectedContents)
178
179         // Write with the previous OutputStream (simulate 2 OututStreams writing "concurrently")
180         out.write("def".getBytes())
181         out.close()
182         expectedContents = expectedContents + "def"
183         verifyFileContents(fileSystem, NEW_FILE, expectedContents)
184     }
185
186     /**
187      * Test the createOutputStream() method, when a FileSystemException is expected
188      */
189     void testCreateOutputStream_FileSystemException() {
190         // Parent directory does not exist
191         shouldFail(FileSystemException) { fileSystem.createOutputStream(NEW_DIR + "/abc.txt", true) }
192
193         shouldFail(FileSystemException) { fileSystem.createOutputStream(EXISTING_DIR, true) }
194         shouldFail(InvalidFilenameException) { fileSystem.createOutputStream(ILLEGAL_FILE, true) }
195         shouldFail(FileSystemException) { fileSystem.createOutputStream("", true) }
196     }
197
198     /**
199      * Test the createOutputStream() method, passing in a null path
200      */
201     void testCreateOutputStream_NullPath() {
202         shouldFailWithMessageContaining("path") { fileSystem.createOutputStream(null, true) }
203     }
204
205     /**
206      * Test the rename() method, passing in a null fromPath
207      */
208     void testRename_NullFromPath() {
209         shouldFailWithMessageContaining("fromPath") { fileSystem.rename(null, FILENAME1) }
210     }
211
212     /**
213      * Test the rename() method, passing in a null toPath
214      */
215     void testRename_NullToPath() {
216         shouldFailWithMessageContaining("toPath") { fileSystem.rename(FILENAME1, null) }
217     }
218
219     /**
220      * Test the listNames() method
221      */
222     void testListNames() {
223         assert fileSystem.createDirectory(NEW_DIR)
224         assert fileSystem.listNames(NEW_DIR) == []
225
226         assert fileSystem.createFile(NEW_DIR + "/" + FILENAME1)
227         assert fileSystem.createFile(NEW_DIR + "/" + FILENAME2)
228         assert fileSystem.createDirectory(NEW_DIR + "/" + DIR1)
229         assert fileSystem.createFile(NEW_DIR + "/" + DIR1 + "/abc.def" )
230
231         List filenames = fileSystem.listNames(NEW_DIR)
232         LOG.info("filenames=" + filenames)
233         assert [FILENAME1, FILENAME2, DIR1] as Set == filenames as Set
234
235         assert [] == fileSystem.listNames(NO_SUCH_DIR)
236         assert [] == fileSystem.listNames(NEW_DIR + "/" + FILENAME1)
237
238         shouldFailWithMessageContaining("path") { fileSystem.listNames(null) }
239     }
240
241     /**
242      * Test listFiles() method
243      */
244     void testListFiles() {
245         final DATE = new Date()
246         assert fileSystem.createDirectory(NEW_DIR)
247         assert [] == fileSystem.listFiles(NEW_DIR)
248
249         assert fileSystem.createFile(p(NEW_DIR,FILENAME1))
250         FileInfo fileInfo1 = FileInfo.forFile(FILENAME1, 0, DATE)
251         assert [fileInfo1] == fileSystem.listFiles(NEW_DIR)
252
253         // Specify a filename instead of a directory name
254         assert [fileInfo1] == fileSystem.listFiles(p(NEW_DIR,FILENAME1))
255
256         assert fileSystem.createFile(p(NEW_DIR, FILENAME2))
257         FileInfo fileInfo2 = FileInfo.forFile(FILENAME2, 0, DATE)
258         assert [fileInfo1, fileInfo2] as Set == fileSystem.listFiles(NEW_DIR) as Set
259
260         // Write to the file to get a non-zero length
261         final byte[] CONTENTS = "1234567890".getBytes()
262         OutputStream out = fileSystem.createOutputStream(NEW_DIR + "/" + FILENAME1, false)
263         out.write(CONTENTS)
264         out.close()
265         fileInfo1 = FileInfo.forFile(FILENAME1, CONTENTS.length, DATE)
266         assert [fileInfo1, fileInfo2] as Set == fileSystem.listFiles(NEW_DIR) as Set
267
268         assert fileSystem.createDirectory(p(NEW_DIR,DIR1))
269         FileInfo fileInfo3 = FileInfo.forDirectory(DIR1, DATE)
270         assert [fileInfo1, fileInfo2, fileInfo3] as Set == fileSystem.listFiles(NEW_DIR) as Set
271
272         assert fileSystem.listFiles(NO_SUCH_DIR) == []
273
274         shouldFailWithMessageContaining("path") { fileSystem.listFiles(null) }
275     }
276
277     /**
278      * Test the delete() method
279      */
280     void testDelete() {
281         assert fileSystem.createFile(NEW_FILE)
282         assert fileSystem.delete(NEW_FILE)
283         assert !fileSystem.exists(NEW_FILE)
284
285         assert !fileSystem.delete(NO_SUCH_FILE)
286
287         assert fileSystem.createDirectory(NEW_DIR)
288         assert fileSystem.delete(NEW_DIR)
289         assert !fileSystem.exists(NEW_DIR)
290
291         assert fileSystem.createDirectory(NEW_DIR)
292         assert fileSystem.createFile(NEW_DIR + "/abc.txt")
293
294         assert !fileSystem.delete(NEW_DIR), "Directory containing files"
295         assert fileSystem.exists(NEW_DIR)
296
297         shouldFailWithMessageContaining("path") { fileSystem.delete(null) }
298     }
299
300     /**
301      * Test the rename() method
302      */
303     void testRename() {
304         final String FROM_FILE = NEW_FILE + "2"
305         assert fileSystem.createFile(FROM_FILE)
306
307         assert fileSystem.rename(FROM_FILE, NEW_FILE)
308         assert fileSystem.exists(NEW_FILE)
309
310         fileSystem.createFile(NEW_FILE)
311         fileSystem.createDirectory(NEW_DIR)
312
313         // Rename existing directory
314         final String TO_DIR = NEW_DIR + "2"
315         assert fileSystem.rename(NEW_DIR, TO_DIR)
316         assert !fileSystem.exists(NEW_DIR)
317         assert fileSystem.exists(TO_DIR)
318
319         // Path of FROM file/dir does not exist
320         final String TO_FILE2 = NEW_FILE + "2"
321         assert !fileSystem.rename(NO_SUCH_FILE, TO_FILE2)
322         assert !fileSystem.exists(TO_FILE2), "After failed rename"
323     }
324
325     /**
326      * Test the rename() method on a directory that contains files
327      */
328     public void testRename_DirectoryContainsFiles() {
329         fileSystem.createDirectory(NEW_DIR)
330         fileSystem.createFile(NEW_DIR + "/a.txt")
331         fileSystem.createFile(NEW_DIR + "/b.txt")
332         fileSystem.createDirectory(NEW_DIR + "/subdir")
333
334         final String TO_DIR = NEW_DIR + "2"
335         assert fileSystem.rename(NEW_DIR, TO_DIR)
336         assert !fileSystem.exists(NEW_DIR)
337         assert !fileSystem.exists(NEW_DIR + "/a.txt")
338         assert !fileSystem.exists(NEW_DIR + "/b.txt")
339         assert !fileSystem.exists(NEW_DIR + "/subdir")
340
341         assert fileSystem.exists(TO_DIR)
342         assert fileSystem.exists(TO_DIR + "/a.txt")
343         assert fileSystem.exists(TO_DIR + "/b.txt")
344         assert fileSystem.exists(TO_DIR + "/subdir")
345     }
346
347     /**
348      * Test the rename() method, when the parent of the TO path does not exist
349      */
350     public void testRename_ParentOfToPathDoesNotExist() throws Exception {
351         final String FROM_FILE = NEW_FILE
352         final String TO_FILE = fileSystem.path(NO_SUCH_DIR, "abc")
353         assert fileSystem.createFile(FROM_FILE)
354
355         assert !fileSystem.rename(FROM_FILE, TO_FILE)
356         assert fileSystem.exists(FROM_FILE)
357         assert !fileSystem.exists(TO_FILE)
358     }
359
360     /**
361      * Test the getName() method, passing in a null
362      */
363     void testGetName_Null() {
364         shouldFailWithMessageContaining("path") { fileSystem.getName(null) }
365     }
366
367     /**
368      * Test the getParent() method, passing in a null
369      */
370     void testGetParent_Null() {
371         shouldFailWithMessageContaining("path") { fileSystem.getParent(null) }
372     }
373
374//     /**
375//      * Test the normalize() method, passing in an illegal filename
376//      */
377//     void testNormalize_InvalidPaths() {
378//        shouldFail(InvalidFilenameException) { fileSystem.normalize(ILLEGAL_FILE) }
379//        LOG.info(fileSystem.normalize(ILLEGAL_FILE))
380//     }
381
382     /**
383      * Test the normalize() method, passing in a null
384      */
385     void testNormalize_Null() {
386         shouldFailWithMessageContaining("path") { fileSystem.normalize(null) }
387     }
388
389     //-------------------------------------------------------------------------
390     // Test setup
391     //-------------------------------------------------------------------------
392
393     /**
394      * @see org.mockftpserver.test.AbstractTest#setUp()
395      */
396     void setUp() {
397         super.setUp()
398         fileSystem = createFileSystem()
399     }
400
401     //-------------------------------------------------------------------------
402     // Helper Methods
403     //-------------------------------------------------------------------------
404
405     /**
406      * Return a new instance of the FileSystem implementation class under test
407      * @return a new FileSystem instance
408      * @throws Exception
409      */
410     protected abstract FileSystem createFileSystem()
411
412     /**
413      * Verify the contents of the file at the specified path read from its InputSteam
414      *
415      * @param fileSystem - the FileSystem instance
416      * @param expectedContents - the expected contents
417      * @throws IOException
418      */
419     protected abstract void verifyFileContents(FileSystem fileSystem, String path, String contents) throws Exception
420
421     protected String p(String[] paths) {
422         return paths.join("/")
423     }
424
425
426}