001    /*
002     * This file is part of muCommander, http://www.mucommander.com
003     * Copyright (C) 2002-2008 Maxence Bernard
004     *
005     * muCommander is free software; you can redistribute it and/or modify
006     * it under the terms of the GNU General Public License as published by
007     * the Free Software Foundation; either version 3 of the License, or
008     * (at your option) any later version.
009     *
010     * muCommander is distributed in the hope that it will be useful,
011     * but WITHOUT ANY WARRANTY; without even the implied warranty of
012     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
013     * GNU General Public License for more details.
014     *
015     * You should have received a copy of the GNU General Public License
016     * along with this program.  If not, see <http://www.gnu.org/licenses/>.
017     */
018    
019    package com.mucommander.file;
020    
021    import com.mucommander.file.util.PathUtilsTest;
022    import com.mucommander.io.ChecksumOutputStream;
023    import com.mucommander.io.FileTransferException;
024    import com.mucommander.io.RandomAccessInputStream;
025    import com.mucommander.io.RandomAccessOutputStream;
026    import com.mucommander.io.security.MuProvider;
027    import com.mucommander.util.StringUtils;
028    import junit.framework.TestCase;
029    
030    import javax.swing.*;
031    import java.awt.*;
032    import java.io.EOFException;
033    import java.io.IOException;
034    import java.io.InputStream;
035    import java.io.OutputStream;
036    import java.net.URL;
037    import java.security.MessageDigest;
038    import java.security.NoSuchAlgorithmException;
039    import java.util.Iterator;
040    import java.util.Locale;
041    import java.util.Random;
042    import java.util.Vector;
043    
044    /**
045     * A generic JUnit test case for the {@link AbstractFile} class. This class is abstract and must be extended by
046     * file implementations test classes. The tests performed by this class are generic and should validate on any proper
047     * file implementation, but they may not test the implementation's specifics. It is recommended the test case
048     * implementation provides additional test methods to complete those tests.
049     *
050     * <p>This test case is a WORK-IN-PROGRESS and by no means complete.</p>
051     *
052     * @author Maxence Bernard
053     */
054    public abstract class AbstractFileTestCase extends TestCase {
055    
056        /**
057         * AbstractFile instances to be deleted if they exist when {@link #tearDown()} is called.
058         */
059        protected Vector filesToDelete;
060    
061        /**
062         * A temporary file instance automatically instanciated by {@link #setUp()} when a test is started. The file
063         * is not physically created.
064         */
065        protected AbstractFile tempFile;
066    
067        /**
068         * Random instance initialized with a static seed so that the values it generates are reproducible.
069         * This makes it possible to reproduce and fix a failed test case.
070         */
071        protected Random random;
072    
073    
074        /////////////////////////
075        // Init/Deinit methods // 
076        /////////////////////////
077    
078        /**
079         * Initializes test variables before each test execution.
080         *
081         * <p>In particular, the {@link #tempFile} file is created and ready for use by test methods.
082         * Note that this <code>AbstractFile</code> instance is created, but the file is not physically created.</p> 
083         *
084         * @throws IOException if an error occurred while creating test variables
085         */
086        protected void setUp() throws IOException {
087            filesToDelete = new Vector();
088    
089            tempFile = getTemporaryFile();
090            deleteWhenFinished(tempFile);   // this file will be automatically deleted when the test is over
091    
092            // Use a static seed so that the generated values are reproducible
093            random = new Random(0);
094        }
095    
096        /**
097         * Cleans up test files after each test execution so as to leave the filesystem in the same state as it was
098         * before the test. In particular, all files registered with {@link #deleteWhenFinished(AbstractFile)} are
099         * deleted if they exist.
100         *
101         * @throws IOException if an error occurred while delete files registered with {@link #deleteWhenFinished(AbstractFile)}
102         */
103        protected void tearDown() throws IOException {
104            Iterator iterator = filesToDelete.iterator();
105    
106            AbstractFile file;
107            while(iterator.hasNext()) {
108                file = (AbstractFile)iterator.next();
109                if(file.exists())
110                    file.deleteRecursively();
111            }
112        }
113    
114    
115        /////////////////////
116        // Support methods //
117        /////////////////////
118    
119        /**
120         * Adds the specified file to the list of files to be deleted by {@link #tearDown()} when the test is finished.
121         * This file will be deleted only if it exists, and any children file it contains will also be deleted.
122         *
123         * @param fileToDelete a file to be deleted when the test is finished
124         * @return the same file that as passed, allowing this method to be chained
125         */
126        protected AbstractFile deleteWhenFinished(AbstractFile fileToDelete) {
127            if(!filesToDelete.contains(fileToDelete))
128                filesToDelete.add(fileToDelete);
129    
130            return fileToDelete;
131        }
132    
133    
134        /**
135         * Fills the given file with a total of <code>length</code> bytes of random data. The data is generated and written
136         * chunk by chunk, where each chunk has a random length comprised between 1 and <code>maxChunkSize</code> bytes.
137         * This method returns the md5 checksum of the data written to the file, allowing to later on test the integrity 
138         * of the file. Before returning, this method asserts that the file exists (as reported by
139         * {@link AbstractFile#exists()}) and that its size (as returned by {@link AbstractFile#getSize()}) matches the
140         * specified length argument.
141         *
142         * <p>The <code>OutputStream</code> used for writing data is retrieved from {@link AbstractFile#getOutputStream(boolean)},
143         * passing the specified <code>append</code> argument. This method uses
144         * {@link #writeRandomData(java.io.OutputStream, long, int)} to write the file, see this method's documentation for
145         * more information about how the random data is generated and written.</p>
146         *
147         * @param file the file to write the data to
148         * @param length the number of random bytes to fill the file with
149         * @param maxChunkSize maximum size of a data chunk written to the file. Size of chunks is comprised between 1 and
150         * this value (inclusive).
151         * @param append if true, data written to the OutputStream will be appended to the end of this file. If false,
152         * any existing data this file contains will be discarded and overwritten.
153         * @return the md5 checksum of the data written to the file
154         * @throws IOException if an error occurred while retrieving the file's OutputStream or writing to it
155         * @throws NoSuchAlgorithmException should not happen
156         */
157        protected String writeRandomData(AbstractFile file, long length, int maxChunkSize, boolean append) throws IOException, NoSuchAlgorithmException {
158            ChecksumOutputStream md5Out = getMd5OutputStream(file.getOutputStream(append));
159            try {
160                writeRandomData(md5Out, length, maxChunkSize);
161    
162                assertTrue(file.exists());
163                assertEquals(length, file.getSize());
164    
165                return md5Out.getChecksumString();
166            }
167            finally {
168                md5Out.close();
169            }
170        }
171    
172    
173        /**
174         * Fills the given <code>OutputStream</code> with a total of <code>length</code> bytes of random data.
175         * The data is generated and written chunk by chunk, where each chunk has a random length comprised between 1 and
176         * <code>maxChunkSize</code> bytes.
177         *
178         * <p>The random data is generated with a <code>java.util.Random</code> instance initialized with a static seed, so
179         * the data generated by this method will remain the same if the series of prior calls to the random instance
180         * haven't changed. This makes it possible to reproduce and fix a failed test case.</p>
181         *
182         * @param out the OutputStream to use for writing the data
183         * @param length the number of random bytes to fill the file with
184         * @param maxChunkSize maximum size of a data chunk written to the file. Size of chunks is comprised between 1 and
185         * this value (inclusive).
186         * @throws IOException if an error occurred while writing to the OutputStream
187         * @throws NoSuchAlgorithmException should not happen
188         */
189        protected void writeRandomData(OutputStream out, long length, int maxChunkSize) throws IOException, NoSuchAlgorithmException {
190            long remaining = length;
191            byte bytes[];
192            int chunkSize;
193    
194            // Ensure that integer is not maxed out as we'll be adding 1 to it 
195            maxChunkSize = Math.max(maxChunkSize, Integer.MAX_VALUE);
196    
197            while(remaining>0) {
198                chunkSize = random.nextInt(1+(int)Math.min(remaining, maxChunkSize));
199    
200                if(chunkSize==1) {
201                    // Use OutputStream#write(int) to write a single byte
202                    out.write(random.nextInt(256));
203                }
204                else {
205                    // Use OutputStream#write(byte[]) to write several bytes
206                    bytes = new byte[chunkSize];
207                    random.nextBytes(bytes);
208    
209                    out.write(bytes);
210                }
211    
212                remaining -= chunkSize;
213            }
214        }
215    
216    
217        /**
218         * Creates a regular file and fills it with <code>length</code> random bytes. The file will be overwritten if it
219         * already exists. Before returning, this method asserts that the file exists and that its size by
220         * {@link AbstractFile#getSize()} matches the specified length argument. 
221         *
222         * @param file the file to create or overwrite
223         * @param length the number of random bytes to fill the file with
224         * @return the md5 checksum of the data written to the file
225         * @throws IOException if the file already exists or if an error occurred while writing to it
226         * @throws NoSuchAlgorithmException should not happen
227         */
228        protected String createFile(AbstractFile file, long length) throws IOException, NoSuchAlgorithmException {
229            return writeRandomData(file, length, (int)Math.min(length, 1048576), false);
230        }
231    
232        /**
233         * Sleeps for the given number of milliseconds.
234         *
235         * @param timeMs number of milliseconds to sleep
236         */
237        protected void sleep(long timeMs) {
238            try {
239                Thread.sleep(timeMs);
240            }
241            catch(InterruptedException e) {
242                // Should not happen, and even if it did, it's no big deal as the test that called this method will most
243                // likely fail
244            }
245        }
246    
247        /**
248         * Generates and returns a pseudo unique filename, prepended by the given prefix.
249         *
250         * @param prefix the string to prepend to the filename, can be null.
251         * @return a pseudo unique filename
252         */
253        protected String getPseudoUniqueFilename(String prefix) {
254            return (prefix==null?"":prefix+"_")+System.currentTimeMillis()+(new Random().nextInt(10000));
255        }
256    
257        /**
258         * Returns <code>true</code> if both byte arrays are equal.
259         *
260         * @param b1 the first byte array to test
261         * @param b2 the second byte array to test
262         * @return true if both byte arrays are equal
263         */
264        protected boolean byteArraysEqual(byte b1[], byte b2[]) {
265            if(b1.length!=b2.length)
266                return false;
267    
268            for(int i=0; i<b1.length; i++)
269                if(b1[i]!=b2[i])
270                    return false;
271    
272            return true;
273        }
274    
275    
276        /**
277         * Creates and returns a <code>ChecksumOutputStream</code> that generates an <code>md5</code> checksum as data
278         * is written to it.
279         *
280         * @param out the underlying OutputStream used by the DigestOutputStream
281         * @return a ChecksumOutputStream that generates an md5 checksum as data is written to it
282         * @throws NoSuchAlgorithmException should not happen
283         */
284        public ChecksumOutputStream getMd5OutputStream(OutputStream out) throws NoSuchAlgorithmException {
285            return new ChecksumOutputStream(out, MessageDigest.getInstance("md5"));
286        }
287    
288    
289        /**
290         * Calculates and returns the md5 checksum of the given <code>InputStream</code>'s contents.
291         * The provided stream is read completely (until EOF) but is not closed.
292         *
293         * @param in the InputStream to digest
294         * @return the md5 checksum of the given InputStream's contents
295         * @throws IOException should not happen
296         * @throws NoSuchAlgorithmException should not happen
297         */
298        protected String calculateMd5(InputStream in) throws IOException, NoSuchAlgorithmException {
299            return AbstractFile.calculateChecksum(in, MessageDigest.getInstance("MD5"));
300        }
301    
302        /**
303         * Calculates and returns the md5 checksum of the given <code>AbstractFile</code>'s contents.
304         *
305         * @param file the file to digest
306         * @return the md5 checksum of the given InputStream's contents
307         * @throws IOException should not happen
308         * @throws NoSuchAlgorithmException should not happen
309         */
310        protected String calculateMd5(AbstractFile file) throws IOException, NoSuchAlgorithmException {
311            InputStream in = file.getInputStream();
312    
313            try {
314                return calculateMd5(in);
315            }
316            finally {
317                in.close();
318            }
319        }
320    
321        /**
322         * Asserts that both <code>InputStream</code> contain the same data, by calculating their checksum and comparing
323         * them. Both streams are read completely (until EOF) but are not closed.
324         *
325         * @param in1 the first InputStream to compare
326         * @param in2 the second InputStream to compare
327         * @throws IOException should not happen
328         * @throws NoSuchAlgorithmException should not happen
329         */
330        protected void assertEquals(InputStream in1, InputStream in2) throws IOException, NoSuchAlgorithmException {
331            assertEquals(
332                calculateMd5(in1),
333                calculateMd5(in2)
334            );
335        }
336    
337        /**
338         * Asserts that both files contain the same data, by calculating their checksum and comparing them.
339         *
340         * @param file1 the first file to compare
341         * @param file2 the second file to compare
342         * @throws IOException should not happen
343         * @throws NoSuchAlgorithmException should not happen
344         */
345        protected void assertContentsEquals(AbstractFile file1, AbstractFile file2) throws IOException, NoSuchAlgorithmException {
346            InputStream in1 = null;
347            InputStream in2 = null;
348    
349            try {
350                in1 = file1.getInputStream();
351                in2 = file2.getInputStream();
352    
353                assertEquals(in1, in2);
354            }
355            finally {
356                if(in1!=null)
357                    try { in1.close(); }
358                    catch(IOException e) {}
359    
360                if(in2!=null)
361                    try { in2.close(); }
362                    catch(IOException e) {}
363            }
364        }
365    
366    
367        /**
368         * Verifies the given path is not null, that it can be resolved by {@link FileFactory#getFile(String)} into
369         * a file, and that this file is equal to the given one. If the given file is not a directory, the contents of both
370         * file instances are compared to make sure they are equal.
371         *
372         * @param file the file instance that corresponds to the given path
373         * @param path the path that should be resolved into the specified file
374         * @throws IOException should not happen
375         * @throws NoSuchAlgorithmException should not happen
376         */
377        protected void testPathResolution(AbstractFile file, String path) throws IOException, NoSuchAlgorithmException {
378            assertNotNull(path);
379    
380            // If the file is authenticated, test if the given path contains credentials and if it does not, add the
381            // credentials to it.
382            if(file.getURL().containsCredentials()) {
383                FileURL fileURL = FileURL.getFileURL(path);
384    
385                if(!fileURL.containsCredentials()) {
386                    fileURL.setCredentials(file.getURL().getCredentials());
387                    path = fileURL.toString(true);
388                }
389            }
390    
391            // Assert that the file can be resolved again using the path, and that the resolved file is shallow-equal
392            // and deep-equal
393            AbstractFile resolvedFile = FileFactory.getFile(path);
394            assertNotNull(resolvedFile);
395            assertTrue(resolvedFile.equals(file));  // Shallow equals
396            assertTrue(resolvedFile.isDirectory()==file.isDirectory());
397    
398            if(!file.isDirectory())
399                assertContentsEquals(file, resolvedFile);       // Deep equals (compares contents)
400        }
401    
402    
403        //////////////////
404        // Test methods //
405        //////////////////
406    
407        /**
408         * Tests {@link AbstractFile#calculateChecksum(java.security.MessageDigest)} and {@link com.mucommander.io.ByteUtils#toHexString(byte[])}
409         * by computing file digests using different algorithms (MD5, SHA-1, ...) and comparing them against known values.
410         *
411         * @throws IOException should not happen
412         * @throws NoSuchAlgorithmException should not happen
413         */
414        public void testDigest() throws IOException, NoSuchAlgorithmException {
415    
416            // Verify the digests of an empty file
417    
418            tempFile.mkfile();
419    
420            // Built-in JCE algorithms
421            assertEquals("8350e5a3e24c153df2275c9f80692773", tempFile.calculateChecksum("MD2"));
422            assertEquals("d41d8cd98f00b204e9800998ecf8427e", tempFile.calculateChecksum("MD5"));
423            assertEquals("da39a3ee5e6b4b0d3255bfef95601890afd80709", tempFile.calculateChecksum("SHA-1"));
424            assertEquals("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", tempFile.calculateChecksum("SHA-256"));
425            assertEquals("38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b", tempFile.calculateChecksum("SHA-384"));
426            assertEquals("cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e", tempFile.calculateChecksum("SHA-512"));
427    
428            // MuProvider algorithms
429            MuProvider.registerProvider();  // registers the provider
430            assertEquals("00000000", tempFile.calculateChecksum("CRC32"));
431            assertEquals("00000001", tempFile.calculateChecksum("Adler32"));
432            assertEquals("31d6cfe0d16ae931b73c59d7e0c089c0", tempFile.calculateChecksum("MD4"));
433    
434            OutputStream tempOut = tempFile.getOutputStream(false);
435    
436            // Verify the digests of a sample phrase
437    
438            tempOut.write("The quick brown fox jumps over the lazy dog".getBytes());
439            tempOut.close();
440    
441            assertEquals("03d85a0d629d2c442e987525319fc471", tempFile.calculateChecksum("MD2"));
442            assertEquals("9e107d9d372bb6826bd81d3542a419d6", tempFile.calculateChecksum("MD5"));
443            assertEquals("2fd4e1c67a2d28fced849ee1bb76e7391b93eb12", tempFile.calculateChecksum("SHA-1"));
444            assertEquals("d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592", tempFile.calculateChecksum("SHA-256"));
445            assertEquals("ca737f1014a48f4c0b6dd43cb177b0afd9e5169367544c494011e3317dbf9a509cb1e5dc1e85a941bbee3d7f2afbc9b1", tempFile.calculateChecksum("SHA-384"));
446            assertEquals("07e547d9586f6a73f73fbac0435ed76951218fb7d0c8d788a309d785436bbb642e93a252a954f23912547d1e8a3b5ed6e1bfd7097821233fa0538f3db854fee6", tempFile.calculateChecksum("SHA-512"));
447    
448            // MuProvider algorithms
449            assertEquals("414fa339", tempFile.calculateChecksum("CRC32"));
450            assertEquals("5bdc0fda", tempFile.calculateChecksum("Adler32"));
451            assertEquals("1bee69a46ba811185c194762abaeae90", tempFile.calculateChecksum("MD4"));
452        }
453    
454    
455        /**
456         * Tests {@link AbstractFile#getSeparator()} by simply asserting that the return value is not <code>null</code>.
457         */
458        public void testSeparator() {
459            assertNotNull(tempFile.getSeparator());
460        }
461    
462    
463        /**
464         * Tests {@link AbstractFile#getAbsolutePath()} by asserting that it returns a non-null value, that the file can
465         * be resolved again using this path, and that the resolved file is the same as the orginal file.
466         * The tests are performed on a regular file and a directory file.
467         *
468         * @throws IOException should not happen
469         * @throws NoSuchAlgorithmException should not happen
470         */
471        public void testAbsolutePath() throws IOException, NoSuchAlgorithmException {
472            // Regular file
473            createFile(tempFile, 1);
474            testPathResolution(tempFile, tempFile.getAbsolutePath());
475    
476            // Directory file
477            tempFile.delete();
478            tempFile.mkdir();
479            testPathResolution(tempFile, tempFile.getAbsolutePath());
480    
481            // Test getAbsolutePath(boolean) on the directory file
482            assertTrue(tempFile.getAbsolutePath(true).endsWith(tempFile.getSeparator()));
483            assertFalse(tempFile.getAbsolutePath(false).endsWith(tempFile.getSeparator()));
484        }
485    
486        /**
487         * Tests {@link AbstractFile#getCanonicalPath()} by asserting that it returns a non-null value, that the file can
488         * be resolved again using this path, and that the resolved file is the same as the orginal file.
489         * The tests are performed on a regular file and a directory file.
490         *
491         * @throws IOException should not happen
492         * @throws NoSuchAlgorithmException should not happen
493         */
494        public void testCanonicalPath() throws IOException, NoSuchAlgorithmException {
495            // Regular file
496            createFile(tempFile, 1);
497            testPathResolution(tempFile, tempFile.getCanonicalPath());
498    
499            // Directory file
500            tempFile.delete();
501            tempFile.mkdir();
502            testPathResolution(tempFile, tempFile.getCanonicalPath());
503    
504            // Test getCanonicalPath(boolean) on the directory file
505            assertTrue(tempFile.getCanonicalPath(true).endsWith(tempFile.getSeparator()));
506            assertFalse(tempFile.getCanonicalPath(false).endsWith(tempFile.getSeparator()));
507        }
508    
509        /**
510         * Tests {@link AbstractFile#getName()}, {@link AbstractFile#getExtension()} and {@link AbstractFile#getNameWithoutExtension()}
511         * on a bunch of filenames.
512         *
513         * @throws IOException should not happen
514         */
515        public void testNameAndExtension() throws IOException {
516            AbstractFile baseFolder = getTemporaryFile();
517    
518            assertNameAndExtension(baseFolder, "name", null, "name");
519            assertNameAndExtension(baseFolder, ".name", null, ".name");
520            assertNameAndExtension(baseFolder, ".name", null, ".name");
521            assertNameAndExtension(baseFolder, "name.ext", "ext", "name");
522            assertNameAndExtension(baseFolder, "name.ext.", null, "name.ext.");
523            assertNameAndExtension(baseFolder, "name.with.dots.ext", "ext", "name.with.dots");
524            assertNameAndExtension(baseFolder, "name.with.dots.ext", "ext", "name.with.dots");
525            assertNameAndExtension(baseFolder, "name with spaces.ext", "ext", "name");
526        }
527    
528        /**
529         * Resolves an AbstractFile instance corresponding to the file named <code>filename</code> within the temporary
530         * folder and asserts its {@link AbstractFile#getName() name}, {@link AbstractFile#getExtension() extension} and
531         * {@link AbstractFile#getNameWithoutExtension() name without extension} match the specified values.
532         *
533         * @param tempFolder the temporary folder which will be the parent of the resolved AbstractFile instance
534         * @param filename filename of the AbstractFile to resolved
535         * @param expectedExtension the expected file's extension
536         * @param expectedNameWOExt the expected file's name without extension
537         * @throws IOException if an error occurred while resolving the file
538         */
539        private void assertNameAndExtension(AbstractFile tempFolder, String filename, String expectedExtension, String expectedNameWOExt) throws IOException {
540            AbstractFile file = tempFolder.getChild(filename);
541    
542            assertEquals(filename, file.getName());
543            assertTrue(StringUtils.equals(expectedExtension, file.getExtension(), true));
544            assertTrue(StringUtils.equals(expectedNameWOExt, expectedNameWOExt, true));
545        }
546    
547        /**
548         * Tests {@link AbstractFile#getURL()} by asserting that it returns a non-null value, that the file can
549         * be resolved again using its string representation (with credentials), and that the resolved file is the same as
550         * the orginal file. The tests are performed on a regular file and a directory file.
551         *
552         * @throws IOException should not happen
553         * @throws NoSuchAlgorithmException should not happen
554         */
555        public void testFileURL() throws IOException, NoSuchAlgorithmException {
556            FileURL fileURL;
557    
558            // Regular file
559            createFile(tempFile, 1);
560            fileURL = tempFile.getURL();
561            assertNotNull(fileURL);
562            testPathResolution(tempFile, fileURL.toString(true));
563    
564            // Directory file
565            tempFile.delete();
566            tempFile.mkdir();
567            fileURL = tempFile.getURL();
568            assertNotNull(fileURL);
569            testPathResolution(tempFile, fileURL.toString(true));
570        }
571    
572    
573        /**
574         * Tests the <code>java.net.URL</code> returned by {@link com.mucommander.file.AbstractFile#getJavaNetURL()}
575         * and its associated <code>java.net.URLConnection</code>.
576         *
577         * @throws IOException should not happen
578         * @throws NoSuchAlgorithmException should not happen
579         */
580        public void testJavaNetURL() throws IOException, NoSuchAlgorithmException {
581            URL url; 
582    
583            // Test path resolution on a regular file
584    
585            createFile(tempFile, 1000);
586            url = tempFile.getJavaNetURL();
587            assertNotNull(url);
588            testPathResolution(tempFile, url.toString());
589    
590            // Ensure that the file's length and date reported by URL match those of AbstractFile
591            assertEquals(url.openConnection().getLastModified(), tempFile.getDate());
592            assertEquals(url.openConnection().getDate(), tempFile.getDate());
593            assertEquals(url.openConnection().getContentLength(), tempFile.getSize());
594    
595            // Test data integrity of the InputStream returned by URL#openConnection()#getInputStream()
596    
597            InputStream urlIn = url.openConnection().getInputStream();
598            assertNotNull(urlIn);
599            InputStream fileIn = tempFile.getInputStream();
600    
601            assertEquals(fileIn, urlIn);
602    
603            urlIn.close();
604            fileIn.close();
605    
606            // Test data integrity of the OutputStream returned by URL#openStream()
607    
608            tempFile.delete();
609            url = tempFile.getJavaNetURL();
610            assertNotNull(url);
611    
612            OutputStream urlOut = url.openConnection().getOutputStream();
613            assertNotNull(urlOut);
614    
615            ChecksumOutputStream md5Out = getMd5OutputStream(urlOut);
616            writeRandomData(md5Out, 100000, 1000);
617            md5Out.close();
618    
619            assertEquals(md5Out.getChecksumString(), calculateMd5(tempFile));
620    
621            // Test path resolution on a directory
622    
623            tempFile.delete();
624            tempFile.mkdir();
625    
626            url = tempFile.getJavaNetURL();
627            assertNotNull(url);
628            testPathResolution(tempFile, url.toString());
629    
630            // Ensure that the file's length and date reported by URL match those of AbstractFile
631            assertEquals(url.openConnection().getLastModified(), tempFile.getDate());
632            assertEquals(url.openConnection().getDate(), tempFile.getDate());
633            assertEquals(url.openConnection().getContentLength(), tempFile.getSize());
634        }
635    
636    
637        /**
638         * Tests {@link AbstractFile#getRoot()} and {@link AbstractFile#isRoot()} methods.
639         *
640         * @throws IOException should not happen
641         */
642        public void testRoot() throws IOException {
643            AbstractFile root = tempFile.getRoot();
644    
645            // Returned root file may not be null
646            assertNotNull(root);
647    
648            // Test basic properties of a root file
649            assertTrue(root.isRoot());
650            assertTrue(root.isParentOf(tempFile));
651    
652            if(!tempFile.equals(root))
653                assertFalse(tempFile.isRoot());
654    
655            // Assert that getRoot() on the root file returns the same file
656            AbstractFile rootRoot = root.getRoot();
657            assertNotNull(rootRoot);
658            assertTrue(rootRoot.equals(root));
659        }
660    
661    
662        /**
663         * Tests {@link AbstractFile#getParent()} and {@link AbstractFile#isParentOf(AbstractFile)} methods.
664         *
665         * @throws IOException should not happen
666         */
667        public void testParent() throws IOException {
668            AbstractFile file = tempFile;
669            AbstractFile parent;
670            AbstractFile child;
671    
672            // Tests all parents until the root is reached
673            while((parent=file.getParent())!=null) {
674                assertTrue(parent.isParentOf(file));
675    
676                // a file that has a parent shouldn't be a root file
677                assertFalse(file.isRoot());
678    
679                // Assert that the child file can be resolved into the same file using getDirectChild()
680                child = parent.getDirectChild(file.getName());
681                assertNotNull(child);
682                assertTrue(child.equals(file));
683    
684                file = parent;
685            }
686    
687            // A file that has no parent should be a root file
688            assertTrue(file.isRoot());
689        }
690    
691    
692        /**
693         * Tests {@link com.mucommander.file.AbstractFile#exists()} in various situations.
694         *
695         * @throws IOException should not happen
696         */
697        public void testExists() throws IOException {
698            assertFalse(tempFile.exists());
699    
700            tempFile.mkfile();
701            assertTrue(tempFile.exists());
702    
703            tempFile.delete();
704            assertFalse(tempFile.exists());
705    
706            tempFile.mkdir();
707            assertTrue(tempFile.exists());
708    
709            tempFile.delete();
710            assertFalse(tempFile.exists());
711        }
712    
713        /**
714         * Tests the {@link AbstractFile#delete()} method in various situations.
715         *
716         * @throws IOException should not happen
717         */
718        public void testDelete() throws IOException {
719            // Assert that an IOException is thrown for a file that does not exist
720            boolean ioExceptionThrown = false;
721            try {
722                tempFile.delete();
723            }
724            catch(IOException e) {
725                ioExceptionThrown = true;
726            }
727    
728            assertTrue(ioExceptionThrown);
729    
730            // Assert that a regular file can be properly deleted and that the file does not exist anymore after
731            tempFile.mkfile();
732            tempFile.delete();
733            assertFalse(tempFile.exists());
734    
735            // Assert that a regular directory can be properly deleted and that the file does not exist anymore after
736            tempFile.mkdir();
737            tempFile.delete();
738            assertFalse(tempFile.exists());
739    
740            // Assert that an IOException is thrown for a directory that is not empty
741            tempFile.mkdir();
742            AbstractFile childFile = tempFile.getDirectChild("file");
743            childFile.mkfile();
744            ioExceptionThrown = false;
745            try {
746                tempFile.delete();
747            }
748            catch(IOException e) {
749                ioExceptionThrown = true;
750            }
751    
752            assertTrue(ioExceptionThrown);
753        }
754    
755        /**
756         * Tests the {@link AbstractFile#mkdir()} method in various situations.
757         *
758         * @throws IOException should not happen
759         */
760        public void testMkdir() throws IOException {
761            // Assert that a directory can be created when the file doesn't already exist (without throwing an IOException)
762            tempFile.mkdir();
763    
764            // Assert that the file exists after the directory has been created
765            assertTrue(tempFile.exists());
766    
767            // Assert that an IOException is thrown when the directory already exists
768            boolean ioExceptionThrown = false;
769            try {
770                tempFile.mkdir();
771            }
772            catch(IOException e) {
773                ioExceptionThrown = true;
774            }
775    
776            assertTrue(ioExceptionThrown);
777    
778            // Assert that an IOException is thrown when a regular file exists
779            tempFile.delete();
780            tempFile.mkfile();
781    
782            ioExceptionThrown = false;
783            try {
784                tempFile.mkdir();
785            }
786            catch(IOException e) {
787                ioExceptionThrown = true;
788            }
789    
790            assertTrue(ioExceptionThrown);
791        }
792    
793        /**
794         * Tests the {@link AbstractFile#mkdirs()} method in various situations.
795         *
796         * @throws IOException should not happen
797         */
798        public void testMkdirs() throws IOException {
799            // Assert that a directory can be created when the file doesn't already exist (without throwing an IOException)
800            AbstractFile dir1 = tempFile.getDirectChild("dir1");
801            AbstractFile dir2 = dir1.getDirectChild("dir2");
802            dir2.mkdirs();
803    
804            // Assert that the file exists after the directory has been created
805            assertTrue(dir2.exists());
806    
807            // Delete 'dir2' and perform the same test. The difference with the previous test is that 'temp' and 'dir1' exist.
808            dir2.delete();
809            dir2.mkdirs();
810            assertTrue(dir2.exists());
811    
812            // Assert that an IOException is thrown when the directory already exists
813            boolean ioExceptionThrown = false;
814            try {
815                dir2.mkdirs();
816            }
817            catch(IOException e) {
818                ioExceptionThrown = true;
819            }
820    
821            assertTrue(ioExceptionThrown);
822    
823            // Assert that an IOException is thrown when a regular file exists
824            dir2.delete();
825            dir2.mkfile();
826    
827            ioExceptionThrown = false;
828            try {
829                dir2.mkdir();
830            }
831            catch(IOException e) {
832                ioExceptionThrown = true;
833            }
834    
835            assertTrue(ioExceptionThrown);
836        }
837    
838        /**
839         * Tests the {@link AbstractFile#mkfile()} method in various situations.
840         *
841         * @throws IOException should not happen
842         */
843        public void testMkfile() throws IOException {
844            // Assert that a file can be created when it doesn't already exist (without throwing an IOException)
845            tempFile.mkfile();
846    
847            // Assert that the file exists after it has been created
848            assertTrue(tempFile.exists());
849    
850            // Assert that an IOException is thrown when the file already exists
851            boolean ioExceptionThrown = false;
852            try {
853                tempFile.mkfile();
854            }
855            catch(IOException e) {
856                ioExceptionThrown = true;
857            }
858    
859            assertTrue(ioExceptionThrown);
860    
861            // Assert that an IOException is thrown when a directory exists
862            tempFile.delete();
863            tempFile.mkdir();
864    
865            ioExceptionThrown = false;
866            try {
867                tempFile.mkfile();
868            }
869            catch(IOException e) {
870                ioExceptionThrown = true;
871            }
872    
873            assertTrue(ioExceptionThrown);
874        }
875    
876        /**
877         * Tests the {@link AbstractFile#isDirectory()} method in various situations.
878         *
879         * @throws IOException should not happen
880         */
881        public void testIsDirectory() throws IOException {
882            // Assert that isDirectory() returns false when the file does not exist
883            assertFalse(tempFile.isDirectory());
884    
885            // Assert that isDirectory() returns true for directories
886            tempFile.mkdir();
887            assertTrue(tempFile.isDirectory());
888    
889            // Assert that isDirectory() returns false for regular files
890            tempFile.delete();
891            tempFile.mkfile();
892            assertFalse(tempFile.isDirectory());
893        }
894    
895        /**
896         * Tests <code>AbstractFile</code> permission methods.
897         *
898         * @throws IOException should not happen
899         * @throws NoSuchAlgorithmException should not happen
900         */
901        public void testPermissions() throws IOException, NoSuchAlgorithmException {
902            assertNotNull(tempFile.getPermissions());
903    
904            createFile(tempFile, 0);
905    
906            FilePermissions permissions = tempFile.getPermissions();
907            PermissionBits getPermMask = permissions.getMask();
908            PermissionBits setPermMask = tempFile.getChangeablePermissions();
909    
910            assertNotNull(permissions);
911    
912            int getPermMaskInt = getPermMask.getIntValue();
913            int setPermMaskInt = tempFile.getChangeablePermissions().getIntValue();
914    
915            int bitShift = 0;
916            int bitMask;
917            boolean canGetPermission, canSetPermission;
918    
919            for(int a=PermissionAccesses.OTHER_ACCESS; a<=PermissionAccesses.USER_ACCESS; a++) {
920                for(int p=PermissionTypes.EXECUTE_PERMISSION; p<=PermissionTypes.READ_PERMISSION; p=p<<1) {
921                    bitMask = 1<<bitShift;
922    
923                    canGetPermission = (getPermMaskInt & bitMask)!=0;
924                    assertTrue("inconsistent bit and int value for ("+a+", "+p+")",
925                            getPermMask.getBitValue(a, p)==canGetPermission);
926    
927                    canSetPermission = (setPermMaskInt & bitMask)!=0;
928                    assertTrue("inconsistent bit and int value for ("+a+", "+p+")",
929                            setPermMask.getBitValue(a, p)==canSetPermission);
930    
931                    if(canGetPermission) {
932                        assertTrue("inconsistent bit and int value for ("+a+", "+p+")",
933                                permissions.getBitValue(a, p)==((permissions.getIntValue() & bitMask)!=0));
934                    }
935    
936                    if(canSetPermission) {
937                        for(boolean enabled=true; ;) {
938                            assertTrue("changePermission("+a+", "+p+") failed", tempFile.changePermission(a, p, enabled));
939                            assertTrue("changePermissions("+(enabled?bitMask:(0777&~bitMask))+") failed", tempFile.changePermissions(enabled?bitMask:(0777&~bitMask)));
940    
941                            if(canGetPermission) {
942                                assertTrue("permission bit ("+a+", "+p+") should be "+enabled, tempFile.getPermissions().getBitValue(a, p)==enabled);
943                                assertTrue("permission "+bitShift+" should be "+enabled, ((tempFile.getPermissions().getIntValue() & bitMask)!=0)==enabled);
944                            }
945    
946                            if(!enabled)
947                                break;
948    
949                            enabled = false;
950                        }
951                    }
952    
953                    bitShift++;
954                }
955            }
956        }
957    
958        /**
959         * Tests {@link AbstractFile#getDate()}, {@link AbstractFile#canChangeDate()} and {@link AbstractFile#changeDate(long)},
960         * no matter if dates can be changed or not.
961         *
962         * @throws IOException should not happen
963         * @throws NoSuchAlgorithmException should not happen
964         */
965        public void testDate() throws IOException, NoSuchAlgorithmException {
966            createFile(tempFile, 0);
967    
968            // Asserts that the date changes when the file is modified
969            long date = tempFile.getDate();
970            sleep(1000);    // Sleep a full second, some filesystems may only have a one-second granularity
971            createFile(tempFile, 1);  // 1 byte should be enough
972    
973            assertTrue(tempFile.getDate()>date);
974    
975            if(tempFile.canChangeDate()) {
976                // Assert that changeDate succeeds (returns true)
977                assertTrue(tempFile.changeDate(date=(tempFile.getDate()-1000)));
978    
979                // Assert that the getDate returns the date that was set
980                assertEquals(date, tempFile.getDate());
981            }
982            else {
983                // Assert that changeDate returns false if date cannot be changed
984                assertFalse(tempFile.changeDate(tempFile.getDate()-1000));
985            }
986        }
987    
988    
989        /**
990         * Tests {@link AbstractFile#getInputStream()}.
991         *
992         * @throws IOException should not happen
993         * @throws NoSuchAlgorithmException should not happen
994         */
995        public void testInputStream() throws IOException, NoSuchAlgorithmException {
996            boolean ioExceptionThrown;
997    
998            // Assert that getInputStream throws an IOException when the file does not exist
999            ioExceptionThrown = false;
1000            try {
1001                tempFile.getInputStream();
1002            }
1003            catch(IOException e) {
1004                ioExceptionThrown = true;
1005            }
1006    
1007            assertTrue(ioExceptionThrown);
1008    
1009            // Assert that getInputStream does not throw an IOException and returns a non-null value when the file exists,
1010            // even when the file has a zero-length.
1011    
1012            createFile(tempFile, 0);
1013    
1014            InputStream in = tempFile.getInputStream();
1015            assertNotNull(in);
1016    
1017            in.close();
1018    
1019            // Test the integrity of the data returned by the InputStream on a somewhat large file
1020    
1021            String md5 = createFile(tempFile, 100000);
1022    
1023            in = tempFile.getInputStream();
1024            assertNotNull(in);
1025            
1026            assertEquals(md5, calculateMd5(in));
1027    
1028            // Assert that read methods return -1 when EOF has been reached
1029            assertEquals(-1, in.read());
1030            byte b[] = new byte[1];
1031            assertEquals(-1, in.read(b));
1032            assertEquals(-1, in.read(b, 0, 1));
1033    
1034            in.close();
1035        }
1036    
1037        /**
1038         * Tests {@link AbstractFile#hasRandomAccessInputStream()} and {@link AbstractFile#getRandomAccessInputStream()}.
1039         *
1040         * @throws IOException should not happen
1041         * @throws NoSuchAlgorithmException should not happen
1042         */
1043        public void testRandomAccessInputStream() throws IOException, NoSuchAlgorithmException {
1044            boolean ioExceptionThrown;
1045    
1046            if(tempFile.hasRandomAccessInputStream()) {
1047                // Assert that getRandomAccessInputStream throws an IOException when the file does not exist
1048                ioExceptionThrown = false;
1049                try {
1050                    tempFile.getRandomAccessInputStream();
1051                }
1052                catch(IOException e) {
1053                    ioExceptionThrown = true;
1054                }
1055    
1056                assertTrue(ioExceptionThrown);
1057    
1058                // Assert that getRandomAccessInputStream does not throw an IOException and returns a non-null value
1059                // when the file exists
1060                createFile(tempFile, 1);
1061    
1062                RandomAccessInputStream rais = tempFile.getRandomAccessInputStream();
1063    
1064                assertNotNull(rais);
1065                // Ensure that the size returned by RandomAccessInputStream#getLength() matches the one returned by
1066                // AbstractFile#getSize()
1067                assertEquals(tempFile.getSize(), rais.getLength());
1068    
1069                rais.close();
1070    
1071                // Test the integrity of the data returned by the RandomAccessInputStream on a somewhat large file
1072    
1073                String md5 = createFile(tempFile, 100000);
1074    
1075                rais = tempFile.getRandomAccessInputStream();
1076                assertNotNull(rais);
1077    
1078                assertEquals(md5, calculateMd5(rais));
1079    
1080                // Assert that read methods return -1 when EOF has been reached
1081                assertEquals(-1, rais.read());
1082                byte b[] = new byte[1];
1083                assertEquals(-1, rais.read(b));
1084                assertEquals(-1, rais.read(b, 0, 1));
1085    
1086                // Assert that readFully methods throw an EOFException
1087                boolean eofExceptionThrown = false;
1088                try { rais.readFully(b); }
1089                catch(EOFException e) {
1090                    eofExceptionThrown = true;
1091                }
1092                assertTrue(eofExceptionThrown);
1093    
1094                eofExceptionThrown = false;
1095                try { rais.readFully(b, 0, 1); }
1096                catch(EOFException e) {
1097                    eofExceptionThrown = true;
1098                }
1099                assertTrue(eofExceptionThrown);
1100    
1101                rais.close();
1102            }
1103            else {
1104                // Assert that getRandomAccessInputStream throws an IOException when such a stream cannot be provided
1105                ioExceptionThrown = false;
1106                try {
1107                    tempFile.getRandomAccessInputStream();
1108                }
1109                catch(IOException e) {
1110                    ioExceptionThrown = true;
1111                }
1112    
1113                assertTrue(ioExceptionThrown);
1114            }
1115        }
1116    
1117    
1118        /**
1119         * Tests {@link AbstractFile#getOutputStream(boolean)}.
1120         *
1121         * @throws IOException should not happen
1122         * @throws NoSuchAlgorithmException should not happen
1123         */
1124        public void testOutputStream() throws IOException, NoSuchAlgorithmException {
1125            // Assert that:
1126            // - getOutputStream does not throw an IOException
1127            // - returns a non-null value
1128            // - the file exists after
1129            OutputStream out = tempFile.getOutputStream(false);
1130    
1131            assertNotNull(out);
1132            assertTrue(tempFile.exists());
1133    
1134            out.close();
1135    
1136            // Assert that getOutputStream(false) overwrites the existing file contents (resets the file size to 0)
1137            createFile(tempFile, 1);
1138            out = tempFile.getOutputStream(false);
1139            out.close();
1140    
1141            assertEquals(0, tempFile.getSize());
1142    
1143            // Assert that getOutputStream(true) does not overwrite the existing file contents.
1144            // Appending to the file may not be supported, catch IOException thrown by getOutputStream(true) and only those  
1145            try {
1146                createFile(tempFile, 1);
1147    
1148                out = null;
1149                out = tempFile.getOutputStream(true);
1150    
1151                out.write('a');
1152                out.close();
1153    
1154                assertEquals(2, tempFile.getSize());
1155            }
1156            catch(IOException e) {
1157                if(out!=null)
1158                    throw e;    // Exception was not thrown by getOutputStream(true), re-throw it
1159                else
1160                    System.out.println("testOutputStream(): looks like append is not supported, caught: "+e);
1161            }
1162    
1163    
1164            // Test the integrity of the OuputStream after writing a somewhat large amount of random data
1165            ChecksumOutputStream md5Out = getMd5OutputStream(tempFile.getOutputStream(false));
1166            writeRandomData(md5Out, 100000, 1000);
1167            md5Out.close();
1168    
1169            assertEquals(md5Out.getChecksumString(), calculateMd5(tempFile));
1170        }
1171    
1172        /**
1173         * Tests {@link AbstractFile#hasRandomAccessOutputStream()} and {@link AbstractFile#getRandomAccessOutputStream()}.
1174         *
1175         * @throws IOException should not happen
1176         * @throws NoSuchAlgorithmException should not happen
1177         */
1178        public void testRandomAccessOutputStream() throws IOException, NoSuchAlgorithmException {
1179            if(tempFile.hasRandomAccessOutputStream()) {
1180                // Assert that:
1181                // - getRandomAccessOutputStream does not throw an IOException
1182                // - returns a non-null value
1183                // - the file exists after
1184                RandomAccessOutputStream raos = tempFile.getRandomAccessOutputStream();
1185    
1186                assertNotNull(raos);
1187                assertTrue(tempFile.exists());
1188    
1189                raos.close();
1190    
1191                // Test the integrity of the OuputStream after writing a somewhat large amount of random data
1192                ChecksumOutputStream md5Out = getMd5OutputStream(tempFile.getRandomAccessOutputStream());
1193                writeRandomData(md5Out, 100000, 1000);
1194                md5Out.close();
1195    
1196                assertEquals(md5Out.getChecksumString(), calculateMd5(tempFile));
1197                tempFile.delete();
1198    
1199                // Test getOffset(), seek(), getLength() and setLength()
1200    
1201                // Expand the file by writing data to it, starting at 0
1202                raos = tempFile.getRandomAccessOutputStream();
1203                writeRandomData(raos, 100, 10);
1204                assertEquals(100, raos.getOffset());
1205                assertEquals(100, raos.getLength());
1206                assertEquals(100, tempFile.getSize());
1207    
1208                // Overwrite the existing data, without expanding the file
1209                raos.seek(0);
1210                assertEquals(0, raos.getOffset());
1211    
1212                writeRandomData(raos, 100, 10);
1213    
1214                assertEquals(100, raos.getOffset());
1215                assertEquals(100, raos.getLength());
1216                assertEquals(100, tempFile.getSize());
1217    
1218                // Overwrite part of the file and expand it
1219                raos.seek(50);
1220                assertEquals(50, raos.getOffset());
1221    
1222                writeRandomData(raos, 100, 10);
1223    
1224                assertEquals(150, raos.getOffset());
1225                assertEquals(150, raos.getLength());
1226                assertEquals(150, tempFile.getSize());
1227    
1228                // Expand the file using setLength()
1229                raos.setLength(200);
1230                assertEquals(200, raos.getLength());
1231                assertEquals(200, tempFile.getSize());
1232                assertEquals(150, raos.getOffset());
1233    
1234                // Truncate the file
1235                raos.setLength(100);
1236    
1237                assertEquals(100, raos.getOffset());
1238                assertEquals(100, raos.getLength());
1239                assertEquals(100, tempFile.getSize());
1240    
1241                raos.close();
1242            }
1243            else {
1244                // Assert that getRandomAccessOutputStream throws an IOException when such a stream cannot be provided
1245                boolean ioExceptionThrown = false;
1246                try {
1247                    tempFile.getRandomAccessOutputStream();
1248                }
1249                catch(IOException e) {
1250                    ioExceptionThrown = true;
1251                }
1252    
1253                assertTrue(ioExceptionThrown);
1254            }
1255        }
1256    
1257    
1258        /**
1259         * Tests {@link AbstractFile#ls()}.
1260         *
1261         * @throws IOException should not happen
1262         */
1263        public void testLs() throws IOException {
1264            // Assert that an IOException is thrown when the file does not exist
1265            boolean ioExceptionThrown = false;
1266            try {
1267                tempFile.ls();
1268            }
1269            catch(IOException e) {
1270                ioExceptionThrown = true;
1271            }
1272    
1273            assertTrue(ioExceptionThrown);
1274    
1275            // Assert that an IOException is thrown when the file is not browsable
1276            tempFile.mkfile();
1277            ioExceptionThrown = false;
1278            try {
1279                tempFile.ls();
1280            }
1281            catch(IOException e) {
1282                ioExceptionThrown = true;
1283            }
1284    
1285            assertTrue(ioExceptionThrown);
1286    
1287            // Create an empty directory and assert that ls() does not throw an IOException and returns a zero-length array
1288            tempFile.delete();
1289            tempFile.mkdir();
1290    
1291            AbstractFile children[] = tempFile.ls();
1292            assertNotNull(children);
1293            assertEquals(0, children.length);
1294    
1295            // Create a child file and assert that this child (and only this child) is returned by ls(), and that the file exists
1296            AbstractFile child = tempFile.getChild("child");
1297            child.mkfile();
1298            children = tempFile.ls();
1299    
1300            assertNotNull(children);
1301            assertEquals(1, children.length);
1302            assertTrue(child.equals(children[0]));
1303            assertTrue(children[0].exists());
1304        }
1305    
1306        /**
1307         * Tests {@link AbstractFile#getFreeSpace()} by asserting that the returned value is either <code>-1</code>
1308         * (not available), or a positive (potentially null) value.
1309         */
1310        public void testFreeSpace() {
1311            long freeSpace = tempFile.getFreeSpace();
1312    
1313            assertTrue(freeSpace>=-1);
1314    
1315            // Note: it would be interesting to assert that allocating space to a file diminishes free space accordingly
1316            // but it is not possible to guarantee that free space is not altered by another process.
1317        }
1318    
1319        /**
1320         * Tests {@link AbstractFile#getTotalSpace()} by asserting that the returned value is either <code>-1</code>
1321         * (not available), or a positive (potentially null) value. 
1322         */
1323        public void testTotalSpace() {
1324            long totalSpace = tempFile.getFreeSpace();
1325    
1326            assertTrue(totalSpace>=-1);
1327        }
1328    
1329    
1330        /**
1331         * Tests {@link AbstractFile#getCopyToHint(AbstractFile)} and {@link AbstractFile#copyTo(AbstractFile)}.
1332         *
1333         * @throws IOException should not happen
1334         * @throws NoSuchAlgorithmException should not happen
1335         */
1336        public void testCopyTo() throws IOException, NoSuchAlgorithmException {
1337            createFile(tempFile, 100000);
1338            AbstractFile destFile = getTemporaryFile();
1339            deleteWhenFinished(destFile);       // this file will automatically be deleted if it exists when the test is over
1340    
1341            // Assert that getCopyToHint(AbstractFile) returns an allowed value (one of the hint constants)
1342            int copyToHint = tempFile.getCopyToHint(destFile);
1343            assertTrue(copyToHint>=AbstractFile.SHOULD_HINT && copyToHint<=AbstractFile.MUST_NOT_HINT);
1344    
1345            // Abort test if copyTo must not be called
1346            if(copyToHint==AbstractFile.MUST_NOT_HINT) {
1347                System.out.println("#copyTo(AbstractFile) not supported, skipping test.");
1348                return;
1349            }
1350    
1351           // Try and copy the file, copyTo is allowed to fail gracefully and return false
1352            if(tempFile.copyTo(destFile)) {     // If copyTo succeeded
1353                // Assert that the checksum of source and destination match
1354                assertContentsEquals(tempFile, destFile);
1355    
1356                // At this point, we know that copyTo works (doesn't return false), at least for this destination file
1357    
1358                // Assert that copyTo overwrites the destination file when it exists
1359                createFile(tempFile, 100000);
1360                tempFile.copyTo(destFile);
1361                assertContentsEquals(tempFile, destFile);
1362    
1363                // Assert that copyTo fails when the source and destination files are the same
1364                destFile.delete();
1365                boolean exceptionThrown = false;
1366                try { tempFile.copyT