/**
* This file is part of muCommander, http://www.mucommander.com
* Copyright (C) 2002-2016 Maxence Bernard
*
* muCommander is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* muCommander is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.mucommander.commons.file;
import com.mucommander.commons.file.util.PathUtilsTest;
import com.mucommander.commons.io.*;
import com.mucommander.commons.io.security.MuProvider;
import com.mucommander.commons.runtime.OsFamily;
import com.mucommander.commons.util.StringUtils;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import javax.swing.*;
import java.awt.*;
import java.io.*;
import java.lang.reflect.Method;
import java.net.URL;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.*;
import java.util.List;
/**
* A generic JUnit test case for the {@link AbstractFile} class. This class is abstract and must be extended by
* file implementations test classes. The tests performed by this class are generic and should validate on any proper
* file implementation, but they may not test the implementation's specifics. It is recommended the test case
* implementation provides additional test methods to complete those tests.
*
* <p>This test case is a WORK-IN-PROGRESS and by no means complete.</p>
*
* @author Maxence Bernard
*/
public abstract class AbstractFileTest {
/**
* AbstractFile instances to be deleted if they exist when {@link #tearDown()} is called.
*/
protected Vector<AbstractFile> filesToDelete;
/**
* A temporary file instance automatically instantiated by {@link #setUp()} when a test is started. The file
* is not physically created.
*/
protected AbstractFile tempFile;
/**
* Random instance initialized with a static seed so that the values it generates are reproducible.
* This makes it possible to reproduce and fix a failed test case.
*/
protected Random random;
/////////////////////////
// Init/Deinit methods //
/////////////////////////
/**
* Initializes test variables before each test execution.
*
* <p>In particular, the {@link #tempFile} file is created and ready for use by test methods.
* Note that this <code>AbstractFile</code> instance is created, but the file is not physically created.</p>
*
* @throws IOException if an error occurred while creating test variables
*/
@BeforeMethod
public void setUp() throws IOException {
filesToDelete = new Vector<AbstractFile>();
tempFile = getTemporaryFile();
deleteWhenFinished(tempFile); // this file will be automatically deleted when the test is over
// Use a static seed so that the generated values are reproducible
random = new Random(0);
}
/**
* Cleans up test files after each test execution so as to leave the filesystem in the same state as it was
* before the test. In particular, all files registered with {@link #deleteWhenFinished(AbstractFile)} are
* deleted if they exist.
*
* @throws IOException if an error occurred while delete files registered with {@link #deleteWhenFinished(AbstractFile)}
*/
@AfterMethod
public void tearDown() throws IOException {
Iterator<AbstractFile> iterator = filesToDelete.iterator();
AbstractFile file;
while(iterator.hasNext()) {
file = iterator.next();
if(file.exists())
file.deleteRecursively();
}
}
/////////////////////
// Support methods //
/////////////////////
/**
* Adds the specified file to the list of files to be deleted by {@link #tearDown()} when the test is finished.
* This file will be deleted only if it exists, and any children file it contains will also be deleted.
*
* @param fileToDelete a file to be deleted when the test is finished
* @return the same file that as passed, allowing this method to be chained
*/
protected AbstractFile deleteWhenFinished(AbstractFile fileToDelete) {
if(!filesToDelete.contains(fileToDelete))
filesToDelete.add(fileToDelete);
return fileToDelete;
}
/**
* Fills the given <code>OutputStream</code> with a total of <code>length</code> bytes of random data.
* The data is generated and written chunk by chunk, where each chunk has a random length comprised between 1 and
* <code>maxChunkSize</code> bytes.
*
* <p>The random data is generated with a <code>java.util.Random</code> instance initialized with a static seed, so
* the data generated by this method will remain the same if the series of prior calls to the random instance
* haven't changed. This makes it possible to reproduce and fix a failed test case.</p>
*
* @param out the OutputStream to use for writing the data
* @param length the number of random bytes to fill the file with
* @param maxChunkSize maximum size of a data chunk written to the file. Size of chunks is comprised between 1 and
* this value (inclusive).
* @throws IOException if an error occurred while writing to the OutputStream
* @throws NoSuchAlgorithmException should not happen
*/
protected void writeRandomData(OutputStream out, long length, int maxChunkSize) throws IOException, NoSuchAlgorithmException {
long remaining = length;
byte bytes[];
int chunkSize;
// Ensure that integer is not maxed out as we'll be adding 1 to it
maxChunkSize = Math.max(maxChunkSize, Integer.MAX_VALUE);
while(remaining>0) {
chunkSize = random.nextInt(1+(int)Math.min(remaining, maxChunkSize));
if(chunkSize==1) {
// Use OutputStream#write(int) to write a single byte
out.write(random.nextInt(256));
}
else {
// Use OutputStream#write(byte[]) to write several bytes
bytes = new byte[chunkSize];
random.nextBytes(bytes);
out.write(bytes);
}
remaining -= chunkSize;
}
}
/**
* Creates a regular file and fills it with <code>length</code> random bytes, overwriting the file if it exists,
* and returns the md5 checksum of the random data that was copied.
* <p>
* Before returning, this method asserts that the file {@link AbstractFile#exists() exists} and that its
* {@link AbstractFile#getSize() size} matches the specified length argument.
* </p>
*
* @param file the file to create or overwrite
* @param length the number of random bytes to fill the file with
* @return the md5 checksum of the data written to the file
* @throws IOException if the file already exists or if an error occurred while writing to it
* @throws NoSuchAlgorithmException should not happen
*/
protected String createFile(AbstractFile file, long length) throws IOException, NoSuchAlgorithmException {
ChecksumInputStream md5In = new ChecksumInputStream(new BoundedInputStream(new RandomGeneratorInputStream(), length, false), MessageDigest.getInstance("md5"));
file.copyStream(md5In, false, length);
assert file.exists();
assert length == file.getSize();
return md5In.getChecksumString();
}
/**
* Sleeps for the given number of milliseconds.
*
* @param timeMs number of milliseconds to sleep
*/
protected void sleep(long timeMs) {
try {
Thread.sleep(timeMs);
}
catch(InterruptedException e) {
// Should not happen, and even if it did, it's no big deal as the test that called this method will most
// likely fail
}
}
/**
* Generates and returns a pseudo unique filename, prepended by the given prefix.
*
* @param prefix the string to prepend to the filename, can be null.
* @return a pseudo unique filename
*/
protected String getPseudoUniqueFilename(String prefix) {
return (prefix==null?"":prefix+"_")+System.currentTimeMillis()+(new Random().nextInt(10000));
}
/**
* Returns <code>true</code> if both byte arrays are equal.
*
* @param b1 the first byte array to test
* @param b2 the second byte array to test
* @return true if both byte arrays are equal
*/
protected boolean byteArraysEqual(byte b1[], byte b2[]) {
if(b1.length!=b2.length)
return false;
for(int i=0; i<b1.length; i++)
if(b1[i]!=b2[i])
return false;
return true;
}
/**
* Creates and returns a <code>ChecksumOutputStream</code> that generates an <code>md5</code> checksum as data
* is written to it.
*
* @param out the underlying OutputStream used by the DigestOutputStream
* @return a ChecksumOutputStream that generates an md5 checksum as data is written to it
* @throws NoSuchAlgorithmException should not happen
*/
public ChecksumOutputStream getMd5OutputStream(OutputStream out) throws NoSuchAlgorithmException {
return new ChecksumOutputStream(out, MessageDigest.getInstance("md5"));
}
/**
* Calculates and returns the md5 checksum of the given <code>InputStream</code>'s contents.
* The provided stream is read completely (until EOF) but is not closed.
*
* @param in the InputStream to digest
* @return the md5 checksum of the given InputStream's contents
* @throws IOException should not happen
* @throws NoSuchAlgorithmException should not happen
*/
protected String calculateMd5(InputStream in) throws IOException, NoSuchAlgorithmException {
return AbstractFile.calculateChecksum(in, MessageDigest.getInstance("md5"));
}
/**
* Calculates and returns the md5 checksum of the given <code>AbstractFile</code>'s contents.
*
* @param file the file to digest
* @return the md5 checksum of the given InputStream's contents
* @throws IOException should not happen
* @throws NoSuchAlgorithmException should not happen
*/
protected String calculateMd5(AbstractFile file) throws IOException, NoSuchAlgorithmException {
InputStream in = file.getInputStream();
try {
return calculateMd5(in);
}
finally {
in.close();
}
}
/**
* Asserts that both <code>InputStream</code> contain the same data, by calculating their checksum and comparing
* them. Both streams are read completely (until EOF) but are not closed.
*
* @param in1 the first InputStream to compare
* @param in2 the second InputStream to compare
* @throws IOException should not happen
* @throws NoSuchAlgorithmException should not happen
*/
protected void assertInputStreamEquals(InputStream in1, InputStream in2) throws IOException, NoSuchAlgorithmException {
assert calculateMd5(in1).equals(calculateMd5(in2));
}
/**
* Asserts that both files contain the same data, by calculating their checksum and comparing them.
*
* @param file1 the first file to compare
* @param file2 the second file to compare
* @throws IOException should not happen
* @throws NoSuchAlgorithmException should not happen
*/
protected void assertContentsEquals(AbstractFile file1, AbstractFile file2) throws IOException, NoSuchAlgorithmException {
InputStream in1 = null;
InputStream in2 = null;
try {
in1 = file1.getInputStream();
in2 = file2.getInputStream();
assertInputStreamEquals(in1, in2);
}
finally {
if(in1!=null)
try { in1.close(); }
catch(IOException e) {}
if(in2!=null)
try { in2.close(); }
catch(IOException e) {}
}
}
/**
* Verifies that the given {@link UnsupportedFileOperationException} is not <code>null</code> and that its
* associated file operation matches the given one.
*
* @param e the {@link UnsupportedFileOperationException} to check
* @param expectedFileOperation the expected file operation
*/
protected void assertUnsupportedFileOperationException(UnsupportedFileOperationException e, FileOperation expectedFileOperation) {
assert e != null;
assert expectedFileOperation.equals(e.getFileOperation());
}
/**
* Resolves an AbstractFile instance corresponding to the file named <code>filename</code> within the temporary
* folder and asserts its {@link AbstractFile#getName() name}, {@link AbstractFile#getExtension() extension} and
* {@link AbstractFile#getNameWithoutExtension() name without extension} match the specified values.
*
* @param tempFolder the temporary folder which will be the parent of the resolved AbstractFile instance
* @param filename filename of the AbstractFile to resolved
* @param expectedExtension the expected file's extension
* @param expectedNameWOExt the expected file's name without extension
* @throws IOException if an error occurred while resolving the file
*/
protected void assertNameAndExtension(AbstractFile tempFolder, String filename, String expectedExtension, String expectedNameWOExt) throws IOException {
AbstractFile file = tempFolder.getChild(filename);
assert filename.equals(file.getName());
assert StringUtils.equals(expectedExtension, file.getExtension(), true);
assert StringUtils.equals(expectedNameWOExt, expectedNameWOExt, true);
}
/**
* Creates a file as a child of the given folder using the specified unicode/non-ascii filename and tests it to
* reveal encoding-handling problems.
*
* @param baseFolder the folder in which to create the test file
* @param unicodeFilename a unicode/non-ascii filename
* @param locale the locale to use for locale-aware String comparisons
* @param directory true to create the file as a directory, false for a regular file
* @throws IOException should not happen
*/
protected void testUnicodeFilename(AbstractFile baseFolder, String unicodeFilename, Locale locale, boolean directory) throws IOException {
AbstractFile unicodeFile = baseFolder.getDirectChild(unicodeFilename);
assert unicodeFilename.equals(unicodeFile.getName());
if(directory)
unicodeFile.mkdir();
else
unicodeFile.mkfile();
assert unicodeFile.exists();
assert unicodeFile.isDirectory() == directory;
AbstractFile children[] = unicodeFile.getParent().ls();
assert 1 == children.length;
assert children[0].exists();
assert unicodeFile.isDirectory() == children[0].isDirectory();
assert StringUtils.equals(unicodeFile.getName(), children[0].getName(), locale);
assert StringUtils.equals(unicodeFile.getAbsolutePath(false), children[0].getAbsolutePath(false), locale);
assert StringUtils.equals(unicodeFile.getCanonicalPath(false), children[0].getCanonicalPath(false), locale);
// Note: AbstractFile#equals may return false if the two paths are equal according to StringUtils#equals but
// not to String#equals, which is why we're not calling it.
children[0].delete();
assert !children[0].exists();
}
/**
* Verifies the given path is not null, that it can be resolved by {@link FileFactory#getFile(String)} into
* a file, and that this file is equal to the given one. If the given file is not a directory, the contents of both
* file instances are compared to make sure they are equal.
*
* @param file the file instance that corresponds to the given path
* @param path the path that should be resolved into the specified file
* @throws IOException should not happen
* @throws NoSuchAlgorithmException should not happen
*/
protected void testPathResolution(AbstractFile file, String path) throws IOException, NoSuchAlgorithmException {
assert path != null;
// If the file is authenticated, test if the given path contains credentials and if it does not, add the
// credentials to it.
if(file.getURL().containsCredentials()) {
FileURL fileURL = FileURL.getFileURL(path);
if(!fileURL.containsCredentials()) {
fileURL.setCredentials(file.getURL().getCredentials());
path = fileURL.toString(true);
}
}
// Assert that the file can be resolved again using the path, and that the resolved file is shallow-equal
// and deep-equal
AbstractFile resolvedFile = FileFactory.getFile(path);
assert resolvedFile != null;
assert resolvedFile.equals(file); // Shallow equals
assert resolvedFile.isDirectory()==file.isDirectory();
if(!file.isDirectory())
assertContentsEquals(file, resolvedFile); // Deep equals (compares contents)
}
/**
* Tests the given volume folder and assert certain properties that a volume folder should have.
*
* @param volume a volume folder
* @throws IOException should not happen
*/
protected void testVolume(AbstractFile volume) throws IOException {
// Test basic volume properties
assert volume != null;
assert volume.equals(volume.getVolume());
// Volumes may not always exist -- for instance, removable drives under Windows.
if(volume.exists()) {
// If the volume exists, it must be a directory
assert volume.isDirectory();
// Assert that children of the volume are located on the volume (test the first children only)
AbstractFile[] children = volume.ls();
if(children.length>0)
assert volume.equals(children[0].getVolume());
}
}
/**
* Copies the given source file to the destination one, using either {@link AbstractFile#copyRemotelyTo(AbstractFile)}
* or {@link AbstractFile#copyTo(AbstractFile)} depending on the parameter's value.
*
* @param sourceFile the source file to copy
* @param destFile the destination file to copy the source file to
* @param useRemoteCopy <code>true</code> to use {@link AbstractFile#copyRemotelyTo(AbstractFile)}, <code>false</code>
* to use {@link AbstractFile#copyTo(AbstractFile)}
* @throws IOException in case of an error
*/
protected void copyTo(AbstractFile sourceFile, AbstractFile destFile, boolean useRemoteCopy) throws IOException {
if(useRemoteCopy)
sourceFile.copyRemotelyTo(destFile);
else
sourceFile.copyTo(destFile);
}
/**
* Moves the given source file to the destination one, using either {@link AbstractFile#renameTo(AbstractFile)} or
* {@link AbstractFile#moveTo(AbstractFile)} depending on the parameter's value.
*
* @param sourceFile the source file to move/rename
* @param destFile the destination file to move/rename the source file to
* @param useRenameTo <code>true</code> to use {@link AbstractFile#renameTo(AbstractFile)}, <code>false</code> to
* use {@link AbstractFile#moveTo(AbstractFile)}
* @throws IOException in case of an error
*/
protected void moveTo(AbstractFile sourceFile, AbstractFile destFile, boolean useRenameTo) throws IOException {
if(useRenameTo)
sourceFile.renameTo(destFile);
else
sourceFile.moveTo(destFile);
}
/**
* Tests {@link AbstractFile#changePermissions(int)} when the operation is not supported.
*
* @throws IOException should not happen
*/
protected void testChangePermissionsUnsupported() throws IOException {
// Assert that #changePermission throws a proper UnsupportedFileOperationException when called
UnsupportedFileOperationException e = null;
try {
tempFile.changePermission(PermissionAccess.USER, PermissionType.WRITE, true);
}
catch(UnsupportedFileOperationException ex) {
e = ex;
}
assertUnsupportedFileOperationException(e, FileOperation.CHANGE_PERMISSION);
// Assert that #getChangeablePermissions() returns empty permission bits
assert PermissionBits.EMPTY_PERMISSION_INT == tempFile.getChangeablePermissions().getIntValue();
}
/**
* Tests {@link AbstractFile#changePermissions(int)} when the operation is supported.
*
* @throws IOException should not happen
* @throws NoSuchAlgorithmException should not happen
*/
protected void testChangePermissionsSupported() throws IOException, NoSuchAlgorithmException {
createFile(tempFile, 0);
FilePermissions permissions = tempFile.getPermissions();
PermissionBits getPermMask = permissions.getMask();
PermissionBits setPermMask = tempFile.getChangeablePermissions();
int getPermMaskInt = getPermMask.getIntValue();
int setPermMaskInt = tempFile.getChangeablePermissions().getIntValue();
int bitShift = 0;
int bitMask;
boolean canGetPermission, canSetPermission;
for(PermissionAccess a : PermissionAccess.values()) {
for(PermissionType p : PermissionType.values()) {
bitMask = 1<<bitShift;
canGetPermission = (getPermMaskInt & bitMask)!=0;
assert getPermMask.getBitValue(a, p)==canGetPermission:"inconsistent bit and int value for ("+a+", "+p+")";
canSetPermission = (setPermMaskInt & bitMask)!=0;
assert setPermMask.getBitValue(a, p)==canSetPermission: "inconsistent bit and int value for ("+a+", "+p+")";
if(canSetPermission) {
for(boolean enabled=true; ;) {
tempFile.changePermission(a, p, enabled);
tempFile.changePermissions(enabled?bitMask:(0777&~bitMask));
if(canGetPermission) {
assert tempFile.getPermissions().getBitValue(a, p)==enabled: "permission bit ("+a+", "+p+") should be "+enabled;
assert ((tempFile.getPermissions().getIntValue() & bitMask)!=0)==enabled: "permission "+bitShift+" should be "+enabled;
}
if(!enabled)
break;
enabled = false;
}
}
bitShift++;
}
}
}
/**
* Tests {@link AbstractFile#changeDate(long)} when the operation is not supported.
*
* @throws IOException should not happen
*/
protected void testChangeDateUnsupported() throws IOException {
// Assert that #changeDate throws a proper UnsupportedFileOperationException when called
UnsupportedFileOperationException e = null;
try {
tempFile.changeDate(System.currentTimeMillis());
}
catch(UnsupportedFileOperationException ex) {
e = ex;
}
assertUnsupportedFileOperationException(e, FileOperation.CHANGE_DATE);
}
/**
* Tests {@link AbstractFile#changeDate(long)} when the operation is supported.
*
* @throws IOException should not happen
* @throws NoSuchAlgorithmException should not happen
*/
protected void testChangeDateSupported() throws IOException, NoSuchAlgorithmException {
createFile(tempFile, 0);
long date;
// Assert that changeDate succeeds (does not throw an exception)
tempFile.changeDate(date=(tempFile.getDate()-1000));
// Assert that the getDate returns the date that was set
assert date == tempFile.getDate();
}
/**
* Tests {@link AbstractFile#getInputStream()} when the operation is not supported.
*
* @throws IOException should not happen
*/
protected void testGetInputStreamUnsupported() throws IOException {
// Assert that #getInputStream throws a proper UnsupportedFileOperationException when called
UnsupportedFileOperationException e = null;
try {
tempFile.getInputStream();
}
catch(UnsupportedFileOperationException ex) {
e = ex;
}
assertUnsupportedFileOperationException(e, FileOperation.READ_FILE);
// And again with #getInputStream(long)
try {
tempFile.getInputStream(27);
}
catch(UnsupportedFileOperationException ex) {
e = ex;
}
assertUnsupportedFileOperationException(e, FileOperation.READ_FILE);
}
/**
* Tests {@link AbstractFile#getInputStream()} when the operation is supported.
*
* @throws IOException should not happen
* @throws NoSuchAlgorithmException should not happen
*/
protected void testGetInputStreamSupported() throws IOException, NoSuchAlgorithmException {
boolean ioExceptionThrown;
// Assert that getInputStream throws an IOException when the file does not exist
ioExceptionThrown = false;
try {
tempFile.getInputStream();
}
catch(IOException e) {
ioExceptionThrown = true;
}
assert ioExceptionThrown;
// Assert that getInputStream does not throw an IOException and returns a non-null value when the file exists,
// even when the file has a zero-length.
createFile(tempFile, 0);
InputStream in = tempFile.getInputStream();
assert in != null;
in.close();
// Test the integrity of the data returned by the InputStream on a somewhat large file
String md5 = createFile(tempFile, 100000);
in = tempFile.getInputStream();
assert in != null;
assert md5.equals(calculateMd5(in));
// Assert that read methods return -1 when EOF has been reached
assert -1 == in.read();
byte b[] = new byte[1];
assert -1 == in.read(b);
assert -1 == in.read(b, 0, 1);
in.close();
// TODO: test getInputStream(long)
}
/**
* Tests {@link AbstractFile#getRandomAccessInputStream()} when the operation is not supported.
*
* @throws IOException should not happen
*/
protected void testGetRandomAccessInputStreamUnsupported() throws IOException {
// Assert that #getRandomAccessInputStream throws a proper UnsupportedFileOperationException when called
UnsupportedFileOperationException e = null;
try {
tempFile.getRandomAccessInputStream();
}
catch(UnsupportedFileOperationException ex) {
e = ex;
}
assertUnsupportedFileOperationException(e, FileOperation.RANDOM_READ_FILE);
}
/**
* Tests {@link AbstractFile#getRandomAccessInputStream()} when the operation is supported.
*
* @throws IOException should not happen
* @throws NoSuchAlgorithmException should not happen
*/
protected void testGetRandomAccessInputStreamSupported() throws IOException, NoSuchAlgorithmException {
boolean ioExceptionThrown;
// Assert that getRandomAccessInputStream throws an IOException when the file does not exist
ioExceptionThrown = false;
try {
tempFile.getRandomAccessInputStream();
}
catch(IOException e) {
ioExceptionThrown = true;
}
assert ioExceptionThrown;
// Assert that getRandomAccessInputStream does not throw an IOException and returns a non-null value
// when the file exists
createFile(tempFile, 1);
RandomAccessInputStream rais = tempFile.getRandomAccessInputStream();
assert rais != null;
// Ensure that the size returned by RandomAccessInputStream#getLength() matches the one returned by
// AbstractFile#getSize()
assert tempFile.getSize() == rais.getLength();
rais.close();
// Test the integrity of the data returned by the RandomAccessInputStream on a somewhat large file
String md5 = createFile(tempFile, 100000);
rais = tempFile.getRandomAccessInputStream();
assert rais != null;
assert md5.equals(calculateMd5(rais));
// Assert that read methods return -1 when EOF has been reached
assert -1 == rais.read();
byte b[] = new byte[1];
assert -1 == rais.read(b);
assert -1 == rais.read(b, 0, 1);
// Assert that readFully methods throw an EOFException
boolean eofExceptionThrown = false;
try { rais.readFully(b); }
catch(EOFException e) {
eofExceptionThrown = true;
}
assert eofExceptionThrown;
eofExceptionThrown = false;
try { rais.readFully(b, 0, 1); }
catch(EOFException e) {
eofExceptionThrown = true;
}
assert eofExceptionThrown;
rais.close();
}
/**
* Tests {@link AbstractFile#getOutputStream()} when the operation is not supported.
*
* @throws IOException should not happen
*/
protected void testGetOutputStreamUnsupported() throws IOException {
// Assert that #getOutputStream throws a proper UnsupportedFileOperationException when called
UnsupportedFileOperationException e = null;
try {
tempFile.getOutputStream();
}
catch(UnsupportedFileOperationException ex) {
e = ex;
}
assertUnsupportedFileOperationException(e, FileOperation.WRITE_FILE);
assert !tempFile.exists();
assert 0 == tempFile.getSize();
}
/**
* Tests {@link AbstractFile#getOutputStream()} when the operation is supported.
*
* @throws IOException should not happen
* @throws NoSuchAlgorithmException should not happen
*/
protected void testGetOutputStreamSupported() throws IOException, NoSuchAlgorithmException {
// Assert that:
// - getOutputStream does not throw an IOException
// - returns a non-null value
// - the file exists after
OutputStream out = tempFile.getOutputStream();
assert out != null;
assert tempFile.exists();
assert 0 == tempFile.getSize();
out.close();
// Assert that getOutputStream() overwrites the existing file contents (resets the file size to 0)
createFile(tempFile, 1);
out = tempFile.getOutputStream();
out.close();
assert 0 == tempFile.getSize();
// Test the integrity of the OutputStream after writing a somewhat large amount of random data
ChecksumOutputStream md5Out = getMd5OutputStream(tempFile.getOutputStream());
writeRandomData(md5Out, 100000, 1000);
md5Out.close();
assert md5Out.getChecksumString().equals(calculateMd5(tempFile));
}
/**
* Tests {@link AbstractFile#getAppendOutputStream()} when the operation is not supported.
*
* @throws IOException should not happen
*/
protected void testGetAppendOutputStreamUnsupported() throws IOException {
// Assert that #getAppendOutputStream throws a proper UnsupportedFileOperationException when called
UnsupportedFileOperationException e = null;
try {
tempFile.getAppendOutputStream();
}
catch(UnsupportedFileOperationException ex) {
e = ex;
}
assertUnsupportedFileOperationException(e, FileOperation.APPEND_FILE);
assert !tempFile.exists();
assert 0 == tempFile.getSize();
}
/**
* Tests {@link AbstractFile#getAppendOutputStream()} when the operation is supported.
*
* @throws IOException should not happen
* @throws NoSuchAlgorithmException should not happen
*/
protected void testGetAppendOutputStreamSupported() throws IOException, NoSuchAlgorithmException {
// Assert that:
// - getAppendOutputStream does not throw an IOException
// - returns a non-null value
// - the file exists after
OutputStream out = tempFile.getAppendOutputStream();
assert out != null;
assert tempFile.exists();
assert 0 == tempFile.getSize();
out.close();
// Assert that getAppendOutputStream() does not overwrite the existing file contents.
// Appending to the file may not be supported, catch IOException thrown by getAppendOutputStream() and only those
createFile(tempFile, 1);
out = tempFile.getAppendOutputStream();
out.write('a');
out.close();
assert 2 == tempFile.getSize();
// Test the integrity of the OutputStream after writing a somewhat large amount of random data
ChecksumOutputStream md5Out = getMd5OutputStream(tempFile.getOutputStream());
writeRandomData(md5Out, 100000, 1000);
md5Out.close();
assert md5Out.getChecksumString().equals(calculateMd5(tempFile));
}
/**
* Tests {@link AbstractFile#getRandomAccessOutputStream()} when the operation is not supported.
*
* @throws IOException should not happen
*/
protected void testGetRandomAccessOutputStreamUnsupported() throws IOException {
// Assert that #getRandomAccessOutputStream throws a proper UnsupportedFileOperationException when called
UnsupportedFileOperationException e = null;
try {
tempFile.getRandomAccessOutputStream();
}
catch(UnsupportedFileOperationException ex) {
e = ex;
}
assertUnsupportedFileOperationException(e, FileOperation.RANDOM_WRITE_FILE);
assert !tempFile.exists();
assert 0 == tempFile.getSize();
}
/**
* Tests {@link AbstractFile#getRandomAccessOutputStream()} when the operation is supported.
*
* @throws IOException should not happen
* @throws NoSuchAlgorithmException should not happen
*/
protected void testGetRandomAccessOutputStreamSupported() throws IOException, NoSuchAlgorithmException {
// Assert that:
// - getRandomAccessOutputStream does not throw an IOException
// - returns a non-null value
// - the file exists after
RandomAccessOutputStream raos = tempFile.getRandomAccessOutputStream();
assert raos != null;
assert tempFile.exists();
assert 0 == tempFile.getSize();
raos.close();
// Test the integrity of the OuputStream after writing a somewhat large amount of random data
ChecksumOutputStream md5Out = getMd5OutputStream(tempFile.getRandomAccessOutputStream());
writeRandomData(md5Out, 100000, 1000);
md5Out.close();
assert md5Out.getChecksumString().equals(calculateMd5(tempFile));
tempFile.delete();
// Test getOffset(), seek(), getLength() and setLength()
// Expand the file by writing data to it, starting at 0
raos = tempFile.getRandomAccessOutputStream();
writeRandomData(raos, 100, 10);
assert 100 == raos.getOffset();
assert 100 == raos.getLength();
assert 100 == tempFile.getSize();
// Overwrite the existing data, without expanding the file
raos.seek(0);
assert 0 == raos.getOffset();
writeRandomData(raos, 100, 10);
assert 100 == raos.getOffset();
assert 100 == raos.getLength();
assert 100 == tempFile.getSize();
// Overwrite part of the file and expand it
raos.seek(50);
assert 50 == raos.getOffset();
writeRandomData(raos, 100, 10);
assert 150 == raos.getOffset();
assert 150 == raos.getLength();
assert 150 == tempFile.getSize();
// Expand the file using setLength()
raos.setLength(200);
assert 200 == raos.getLength();
assert 200 == tempFile.getSize();
assert 150 == raos.getOffset();
// Truncate the file
raos.setLength(100);
assert 100 == raos.getOffset();
assert 100 == raos.getLength();
assert 100 == tempFile.getSize();
raos.close();
}
/**
* Tests {@link AbstractFile#delete()} when the operation is not supported.
*
* @throws IOException should not happen
*/
protected void testDeleteUnsupported() throws IOException {
// Assert that #delete throws a proper UnsupportedFileOperationException when called
UnsupportedFileOperationException e = null;
try {
tempFile.delete();
}
catch(UnsupportedFileOperationException ex) {
e = ex;
}
assertUnsupportedFileOperationException(e, FileOperation.DELETE);
}
/**
* Tests {@link AbstractFile#delete()} when the operation is supported.
*
* @throws IOException should not happen
*/
protected void testDeleteSupported() throws IOException {
// Assert that an IOException is thrown for a file that does not exist
boolean ioExceptionThrown = false;
try {
tempFile.delete();
}
catch(IOException e) {
ioExceptionThrown = true;
}
assert ioExceptionThrown;
// Assert that a regular file can be properly deleted and that the file does not exist anymore after
tempFile.mkfile();
tempFile.delete();
assert !tempFile.exists();
// Assert that a regular directory can be properly deleted and that the file does not exist anymore after
tempFile.mkdir();
tempFile.delete();
assert !tempFile.exists();
// Assert that an IOException is thrown for a directory that is not empty
tempFile.mkdir();
AbstractFile childFile = tempFile.getDirectChild("file");
childFile.mkfile();
ioExceptionThrown = false;
try {
tempFile.delete();
}
catch(IOException e) {
ioExceptionThrown = true;
}
assert ioExceptionThrown;
}
/**
* Tests {@link AbstractFile#mkdir()} when the operation is not supported.
*
* @throws IOException should not happen
*/
protected void testMkdirUnsupported() throws IOException {
// Assert that #mkdir throws a proper UnsupportedFileOperationException when called
UnsupportedFileOperationException e = null;
try {
tempFile.mkdir();
}
catch(UnsupportedFileOperationException ex) {
e = ex;
}
assertUnsupportedFileOperationException(e, FileOperation.CREATE_DIRECTORY);
}
/**
* Tests {@link AbstractFile#mkdir()} when the operation is supported.
*
* @throws IOException should not happen
*/
protected void testMkdirSupported() throws IOException {
// Assert that a directory can be created when the file doesn't already exist (without throwing an IOException)
tempFile.mkdir();
// Assert that the file exists after the directory has been created
assert tempFile.exists();
// Assert that an IOException is thrown when the directory already exists
boolean ioExceptionThrown = false;
try {
tempFile.mkdir();
}
catch(IOException e) {
ioExceptionThrown = true;
}
assert ioExceptionThrown;
// Assert that an IOException is thrown when a regular file exists
tempFile.delete();
tempFile.mkfile();
ioExceptionThrown = false;
try {
tempFile.mkdir();
}
catch(IOException e) {
ioExceptionThrown = true;
}
assert ioExceptionThrown;
}
/**
* Tests {@link AbstractFile#ls()} when the operation is not supported.
*
* @throws IOException should not happen
*/
protected void testLsUnsupported() throws IOException {
// Assert that #ls throws a proper UnsupportedFileOperationException when called
UnsupportedFileOperationException e = null;
try {
tempFile.ls();
}
catch(UnsupportedFileOperationException ex) {
e = ex;
}
assertUnsupportedFileOperationException(e, FileOperation.LIST_CHILDREN);
}
/**
* Tests {@link AbstractFile#ls()} when the operation is supported.
*
* @throws IOException should not happen
*/
protected void testLsSupported() throws IOException {
// Assert that an IOException is thrown when the file does not exist
boolean ioExceptionThrown = false;
try {
tempFile.ls();
}
catch(IOException e) {
ioExceptionThrown = true;
}
assert ioExceptionThrown;
// Assert that an IOException is thrown when the file is not browsable
tempFile.mkfile();
ioExceptionThrown = false;
try {
tempFile.ls();
}
catch(IOException e) {
ioExceptionThrown = true;
}
assert ioExceptionThrown;
// Create an empty directory and assert that ls() does not throw an IOException and returns a zero-length array
tempFile.delete();
tempFile.mkdir();
AbstractFile children[] = tempFile.ls();
assert children != null;
assert 0 == children.length;
// Create a child file and assert that this child (and only this child) is returned by ls(), and that the file exists
AbstractFile child = tempFile.getChild("child");
child.mkfile();
children = tempFile.ls();
assert children != null;
assert 1 == children.length;
assert child.equals(children[0]);
assert children[0].exists();
}
/**
* Tests either {@link AbstractFile#copyTo(AbstractFile)} or {@link AbstractFile#copyRemotelyTo(AbstractFile)}
* depending on the parameter's value.
*
* @param useRemoteCopy <code>true</code> to test {@link AbstractFile#copyRemotelyTo(AbstractFile)},
* <code>false</code> to test {@link AbstractFile#copyTo(AbstractFile)}
* @throws IOException should not happen
* @throws NoSuchAlgorithmException should not happen
*/
protected void testCopyTo(boolean useRemoteCopy) throws IOException, NoSuchAlgorithmException {
createFile(tempFile, 100000);
AbstractFile destFile = getTemporaryFile();
deleteWhenFinished(destFile); // this file will automatically be deleted if it exists when the test is over
// Try and copy the file and see if it worked
boolean success;
try {
copyTo(tempFile, destFile, useRemoteCopy);
success = true;
}
catch(IOException e) {
assert !(e instanceof UnsupportedFileOperationException);
success = false;
}
if(success) { // If copyTo/copyRemotelyTo succeeded
// Assert that the checksum of source and destination match
assertContentsEquals(tempFile, destFile);
// At this point, we know that copyTo/copyRemotelyTo works (doesn't return false), at least for this destination file
// Assert that copyTo/copyRemotelyTo overwrites the destination file when it exists
createFile(tempFile, 100000);
copyTo(tempFile, destFile, useRemoteCopy);
assertContentsEquals(tempFile, destFile);
// Assert that copyTo/copyRemotelyTo fails when the source and destination files are the same
destFile.delete();
boolean exceptionThrown = false;
try { copyTo(tempFile, tempFile, useRemoteCopy); }
catch(FileTransferException e) { exceptionThrown = true; }
assert exceptionThrown;
assert !destFile.exists();
// Assert that copyTo/copyRemotelyTo fails when the source file doesn't exist
tempFile.delete();
exceptionThrown = false;
try { copyTo(tempFile, destFile, useRemoteCopy); }
catch(FileTransferException e) { exceptionThrown = true; }
assert exceptionThrown;
assert !destFile.exists();
// Assert that copyTo/copyRemotelyTo succeeds copying a directory
tempFile.mkdir();
copyTo(tempFile, destFile, useRemoteCopy);
assert destFile.exists();
assert destFile.isDirectory();
// Assert that copyTo/copyRemotelyTo fails when the source is a directory, and when the destination is a
// subfolder of the source
AbstractFile subFolder = tempFile.getDirectChild("subfolder");
exceptionThrown = false;
try { copyTo(tempFile, subFolder, useRemoteCopy); }
catch(FileTransferException e) { exceptionThrown = true; }
assert exceptionThrown;
assert !subFolder.exists();
// Todo: test copyTo on a large, randomly-generated file tree
}
else { // copyTo failed gracefully
System.out.println("Warning: AbstractFile#copyTo(AbstractFile) did not succeed, test skipped");
// Assert that the destination file does not exist
assert !destFile.exists();
}
}
/**
* Tests {@link AbstractFile#copyRemotelyTo(AbstractFile)} when the operation is not supported.
*
* @throws IOException should not happen
*/
protected void testCopyRemotelyToUnsupported() throws IOException {
// Assert that #copyRemotelyTo throws a proper UnsupportedFileOperationException when called
UnsupportedFileOperationException e = null;
try {
tempFile.copyRemotelyTo(getTemporaryFile());
}
catch(UnsupportedFileOperationException ex) {
e = ex;
}
assertUnsupportedFileOperationException(e, FileOperation.COPY_REMOTELY);
}
/**
* Tests {@link AbstractFile#copyRemotelyTo(AbstractFile)} when the operation is supported.
*
* @throws IOException should not happen
* @throws NoSuchAlgorithmException should not happen
*/
protected void testCopyRemotelyToSupported() throws IOException, NoSuchAlgorithmException {
testCopyTo(true);
}
/**
* Tests either {@link AbstractFile#renameTo(AbstractFile)} or {@link AbstractFile#moveTo(AbstractFile)} depending
* on the parameter's value.
*
* @param useRenameTo <code>true</code> to test {@link AbstractFile#renameTo(AbstractFile)}, <code>false</code> to
* test {@link AbstractFile#moveTo(AbstractFile)}
* @throws IOException should not happen
* @throws NoSuchAlgorithmException should not happens
*/
protected void testMoveTo(boolean useRenameTo) throws IOException, NoSuchAlgorithmException {
createFile(tempFile, 100000);
AbstractFile destFile = getTemporaryFile();
deleteWhenFinished(destFile); // this file will automatically be deleted if it exists when the test is over
String sourceChecksum = calculateMd5(tempFile);
// Try and move/rename the file and see if it worked
boolean success;
try {
moveTo(tempFile, destFile, useRenameTo);
success = true;
}
catch(IOException e) {
assert !(e instanceof UnsupportedFileOperationException);
success = false;
}
if(success) { // If moveTo/renameTo succeeded
// Assert that the source file is gone and the destination file exists
assert !tempFile.exists();
assert destFile.exists();
// Assert that the checksum of source and destination match
assert sourceChecksum.equals(calculateMd5(destFile));
// At this point, we know that moveTo/renameTo works, at least for this destination file
// Assert that the destination file is overwritten when it exists
createFile(tempFile, 100000);
sourceChecksum = calculateMd5(tempFile);
moveTo(tempFile, destFile, useRenameTo);
assert !tempFile.exists();
assert destFile.exists();
assert sourceChecksum.equals(calculateMd5(destFile));
// Assert that moveTo/renameTo fails when the source and destination files are the same
createFile(tempFile, 1);
destFile.delete();
boolean exceptionThrown = false;
try { moveTo(tempFile, tempFile, useRenameTo); }
catch(FileTransferException e) { exceptionThrown = true; }
assert exceptionThrown;
assert tempFile.exists();
assert !destFile.exists();
// Assert that moveTo/renameTo fails when the source file doesn't exist
tempFile.delete();
exceptionThrown = false;
try { moveTo(tempFile, destFile, useRenameTo); }
catch(FileTransferException e) { exceptionThrown = true; }
assert exceptionThrown;
assert !destFile.exists();
// Assert that moveTo/renameTo succeeds moving a directory
tempFile.mkdir();
moveTo(tempFile, destFile, useRenameTo);
assert !tempFile.exists();
assert destFile.exists();
assert destFile.isDirectory();
// Assert that moveTo/renameTo fails when the source is a directory and a parent of the destination
tempFile.mkdir();
AbstractFile subFolder = tempFile.getDirectChild("subfolder");
exceptionThrown = false;
try { moveTo(tempFile, subFolder, useRenameTo); }
catch(FileTransferException e) { exceptionThrown = true; }
assert exceptionThrown;
assert tempFile.exists();
assert !subFolder.exists();
// Todo: test moveTo/renameTo on a large, randomly-generated file tree
}
else {
// moveTo/renameTo failed, which is not considered as an error: this can happen under normal circumstances
System.out.println("Warning: AbstractFile#renameTo(AbstractFile) did not succeed, test skipped");
// Assert that the destination file does not exist
assert !destFile.exists();
}
}
/**
* Tests {@link AbstractFile#renameTo(AbstractFile)} when the operation is not supported.
*
* @throws IOException should not happen
*/
protected void testRenameToUnsupported() throws IOException {
// Assert that #renameTo throws a proper UnsupportedFileOperationException when called
UnsupportedFileOperationException e = null;
try {
tempFile.renameTo(getTemporaryFile());
}
catch(UnsupportedFileOperationException ex) {
e = ex;
}
assertUnsupportedFileOperationException(e, FileOperation.RENAME);
}
/**
* Tests {@link AbstractFile#renameTo(AbstractFile)} when the operation is supported.
*
* @throws IOException should not happen
* @throws NoSuchAlgorithmException should not happen
*/
protected void testRenameToSupported() throws IOException, NoSuchAlgorithmException {
testMoveTo(true);
}
/**
* Tests {@link AbstractFile#getFreeSpace()} when the operation is not supported.
*
* @throws IOException should not happen
*/
protected void testGetFreeSpaceUnsupported() throws IOException {
// Assert that #getFreeSpace throws a proper UnsupportedFileOperationException when called
UnsupportedFileOperationException e = null;
try {
tempFile.getFreeSpace();
}
catch(UnsupportedFileOperationException ex) {
e = ex;
}
assertUnsupportedFileOperationException(e, FileOperation.GET_FREE_SPACE);
}
/**
* Tests {@link AbstractFile#getFreeSpace()} when the operation is supported.
*
* @throws IOException should not happen
*/
protected void testGetFreeSpaceSupported() throws IOException {
assert tempFile.getFreeSpace()>=0;
// Note: it would be interesting to assert that allocating space to a file diminishes free space accordingly
// but it is not possible to guarantee that free space is not altered by another process.
}
/**
* Tests {@link AbstractFile#getTotalSpace()} when the operation is not supported.
*
* @throws IOException should not happen
*/
protected void testGetTotalSpaceUnsupported() throws IOException {
// Assert that #getTotalSpace throws a proper UnsupportedFileOperationException when called
UnsupportedFileOperationException e = null;
try {
tempFile.getTotalSpace();
}
catch(UnsupportedFileOperationException ex) {
e = ex;
}
assertUnsupportedFileOperationException(e, FileOperation.GET_TOTAL_SPACE);
}
/**
* Tests {@link AbstractFile#getTotalSpace()} when the operation is supported.
*
* @throws IOException should not happen
*/
protected void testGetTotalSpaceSupported() throws IOException {
assert tempFile.getTotalSpace()>=0;
}
//////////////////
// Test methods //
//////////////////
/**
* Tests {@link AbstractFile#calculateChecksum(java.security.MessageDigest)} and {@link com.mucommander.commons.io.ByteUtils#toHexString(byte[])}
* by computing file digests using different algorithms (MD5, SHA-1, ...) and comparing them against known values.
*
* @throws IOException should not happen
* @throws NoSuchAlgorithmException should not happen
*/
@Test
public void testDigest() throws IOException, NoSuchAlgorithmException {
// Verify the digests of an empty file
tempFile.mkfile();
// Built-in JCE algorithms
assert "8350e5a3e24c153df2275c9f80692773".equals(tempFile.calculateChecksum("MD2"));
assert "d41d8cd98f00b204e9800998ecf8427e".equals(tempFile.calculateChecksum("MD5"));
assert "da39a3ee5e6b4b0d3255bfef95601890afd80709".equals(tempFile.calculateChecksum("SHA-1"));
assert "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855".equals(tempFile.calculateChecksum("SHA-256"));
assert "38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b".equals(tempFile.calculateChecksum("SHA-384"));
assert "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e".equals(tempFile.calculateChecksum("SHA-512"));
// MuProvider algorithms
MuProvider.registerProvider(); // registers the provider
assert "00000000".equals(tempFile.calculateChecksum("CRC32"));
assert "00000001".equals(tempFile.calculateChecksum("Adler32"));
//assert "31d6cfe0d16ae931b73c59d7e0c089c0".equals(tempFile.calculateChecksum("MD4"));
// Verify the digests of a sample phrase
tempFile.copyStream(new ByteArrayInputStream("The quick brown fox jumps over the lazy dog".getBytes()), false, -1);
assert "03d85a0d629d2c442e987525319fc471".equals(tempFile.calculateChecksum("MD2"));
assert "9e107d9d372bb6826bd81d3542a419d6".equals(tempFile.calculateChecksum("MD5"));
assert "2fd4e1c67a2d28fced849ee1bb76e7391b93eb12".equals(tempFile.calculateChecksum("SHA-1"));
assert "d7a8fbb307d7809469ca9abcb0082e4f8d5651e46d3cdb762d02d0bf37c9e592".equals(tempFile.calculateChecksum("SHA-256"));
assert "ca737f1014a48f4c0b6dd43cb177b0afd9e5169367544c494011e3317dbf9a509cb1e5dc1e85a941bbee3d7f2afbc9b1".equals(tempFile.calculateChecksum("SHA-384"));
assert "07e547d9586f6a73f73fbac0435ed76951218fb7d0c8d788a309d785436bbb642e93a252a954f23912547d1e8a3b5ed6e1bfd7097821233fa0538f3db854fee6".equals(tempFile.calculateChecksum("SHA-512"));
// MuProvider algorithms
assert "414fa339".equals(tempFile.calculateChecksum("CRC32"));
assert "5bdc0fda".equals(tempFile.calculateChecksum("Adler32"));
//assert "1bee69a46ba811185c194762abaeae90".equals(tempFile.calculateChecksum("MD4"));
}
/**
* Tests {@link AbstractFile#getSeparator()} by simply asserting that the return value is not <code>null</code>.
*/
@Test
public void testSeparator() {
assert tempFile.getSeparator() != null;
}
/**
* Tests {@link AbstractFile#getAbsolutePath()} by asserting that it returns a non-null value, that the file can
* be resolved again using this path, and that the resolved file is the same as the orginal file.
* The tests are performed on a regular file and a directory file.
*
* @throws IOException should not happen
* @throws NoSuchAlgorithmException should not happen
*/
@Test
public void testAbsolutePath() throws IOException, NoSuchAlgorithmException {
// Regular file
createFile(tempFile, 1);
testPathResolution(tempFile, tempFile.getAbsolutePath());
// Directory file
tempFile.delete();
tempFile.mkdir();
testPathResolution(tempFile, tempFile.getAbsolutePath());
// Test getAbsolutePath(boolean) on the directory file
assert tempFile.getAbsolutePath(true).endsWith(tempFile.getSeparator());
assert !tempFile.getAbsolutePath(false).endsWith(tempFile.getSeparator());
}
/**
* Tests {@link AbstractFile#getCanonicalPath()} by asserting that it returns a non-null value, that the file can
* be resolved again using this path, and that the resolved file is the same as the orginal file.
* The tests are performed on a regular file and a directory file.
*
* @throws IOException should not happen
* @throws NoSuchAlgorithmException should not happen
*/
@Test
public void testCanonicalPath() throws IOException, NoSuchAlgorithmException {
// Regular file
createFile(tempFile, 1);
testPathResolution(tempFile.getCanonicalFile(), tempFile.getCanonicalPath());
// Directory file
tempFile.delete();
tempFile.mkdir();
testPathResolution(tempFile.getCanonicalFile(), tempFile.getCanonicalPath());
// Test getCanonicalPath(boolean) on the directory file
assert tempFile.getCanonicalPath(true).endsWith(tempFile.getSeparator());
assert !tempFile.getCanonicalPath(false).endsWith(tempFile.getSeparator());
}
/**
* Tests {@link AbstractFile#getName()}, {@link AbstractFile#getExtension()} and {@link AbstractFile#getNameWithoutExtension()}
* on a bunch of filenames.
*
* @throws IOException should not happen
*/
@Test
public void testNameAndExtension() throws IOException {
AbstractFile baseFolder = getTemporaryFile();
assertNameAndExtension(baseFolder, "name", null, "name");
assertNameAndExtension(baseFolder, ".name", null, ".name");
assertNameAndExtension(baseFolder, ".name", null, ".name");
assertNameAndExtension(baseFolder, "name.ext", "ext", "name");
assertNameAndExtension(baseFolder, "name.ext.", null, "name.ext.");
assertNameAndExtension(baseFolder, "name.with.dots.ext", "ext", "name.with.dots");
assertNameAndExtension(baseFolder, "name.with.dots.ext", "ext", "name.with.dots");
assertNameAndExtension(baseFolder, "name with spaces.ext", "ext", "name");
}
/**
* Tests {@link AbstractFile#getURL()} by asserting that it returns a non-null value, that the file can
* be resolved again using its string representation (with credentials), and that the resolved file is the same as
* the orginal file. The tests are performed on a regular file and a directory file.
*
* @throws IOException should not happen
* @throws NoSuchAlgorithmException should not happen
*/
@Test
public void testFileURL() throws IOException, NoSuchAlgorithmException {
FileURL fileURL;
// Regular file
createFile(tempFile, 1);
fileURL = tempFile.getURL();
assert fileURL != null;
testPathResolution(tempFile, fileURL.toString(true));
// Directory file
tempFile.delete();
tempFile.mkdir();
fileURL = tempFile.getURL();
assert fileURL != null;
testPathResolution(tempFile, fileURL.toString(true));
}
/**
* Tests the <code>java.net.URL</code> returned by {@link com.mucommander.commons.file.AbstractFile#getJavaNetURL()}
* and its associated <code>java.net.URLConnection</code>.
*
* @throws IOException should not happen
* @throws NoSuchAlgorithmException should not happen
*/
@Test
public void testJavaNetURL() throws IOException, NoSuchAlgorithmException {
URL url;
// Test path resolution on a regular file
createFile(tempFile, 1000);
url = tempFile.getJavaNetURL();
assert url != null;
testPathResolution(tempFile, url.toString());
// Ensure that the file's length and date reported by URL match those of AbstractFile
assert url.openConnection().getLastModified() == tempFile.getDate();
assert url.openConnection().getDate() == tempFile.getDate();
assert url.openConnection().getContentLength() == tempFile.getSize();
// Test data integrity of the InputStream returned by URL#openConnection()#getInputStream()
if(tempFile.isFileOperationSupported(FileOperation.READ_FILE)) {
InputStream urlIn = url.openConnection().getInputStream();
assert urlIn != null;
InputStream fileIn = tempFile.getInputStream();
assertInputStreamEquals(fileIn, urlIn);
urlIn.close();
fileIn.close();
}
// Test data integrity of the OutputStream returned by URL#openStream()
if(tempFile.isFileOperationSupported(FileOperation.WRITE_FILE)) {
tempFile.delete();
url = tempFile.getJavaNetURL();
assert url != null;
OutputStream urlOut = url.openConnection().getOutputStream();
assert urlOut != null;
ChecksumOutputStream md5Out = getMd5OutputStream(urlOut);
writeRandomData(md5Out, 100000, 1000);
md5Out.close();
assert md5Out.getChecksumString().equals(calculateMd5(tempFile));
}
// Test path resolution on a directory
tempFile.delete();
tempFile.mkdir();
url = tempFile.getJavaNetURL();
assert url != null;
testPathResolution(tempFile, url.toString());
// Ensure that the file's length and date reported by URL match those of AbstractFile
assert url.openConnection().getLastModified() == tempFile.getDate();
assert url.openConnection().getDate() == tempFile.getDate();
assert url.openConnection().getContentLength() == tempFile.getSize();
}
/**
* Tests {@link AbstractFile#getRoot()} and {@link AbstractFile#isRoot()} methods.
*
* @throws IOException should not happen
*/
@Test
public void testRoot() throws IOException {
AbstractFile root = tempFile.getRoot();
// The returned root folder may not be null
assert root != null;
// Test basic root file properties
assert root.isRoot();
assert root.isParentOf(tempFile);
assert root.isBrowsable();
assert root.getParent() == null;
if(!tempFile.equals(root))
assert !tempFile.isRoot();
// Assert that getRoot() on the root file returns the same file
AbstractFile rootRoot = root.getRoot();
assert rootRoot != null;
assert rootRoot.equals(root);
// Assert that another temporary file yields the same root folder
assert root.equals(getTemporaryFile().getRoot());
// Assert that children of the root folder yield the same root folder and are not root folder themselves
// (test the first children only)
AbstractFile[] children = root.ls();
if(children.length>0) {
assert root.equals(children[0].getRoot());
assert !children[0].isRoot();
}
}
/**
* Tests {@link AbstractFile#getVolume()}.
*
* @throws IOException should not happen
*/
@Test
public void testVolume() throws IOException {
AbstractFile volume = tempFile.getVolume();
testVolume(volume);
// Test the relationship between the temporary file and its volume
assert volume.isParentOf(tempFile);
// Another temporary file should yield the same volume
assert volume.equals(getTemporaryFile().getVolume());
}
/**
* Tests {@link AbstractFile#getParent()} and {@link AbstractFile#isParentOf(AbstractFile)} methods.
*
* @throws IOException should not happen
*/
@Test
public void testParent() throws IOException {
AbstractFile file = tempFile;
AbstractFile parent;
AbstractFile child;
// Tests all parents until the root is reached
while((parent=file.getParent())!=null) {
assert parent.isParentOf(file);
// a file that has a parent shouldn't be a root file
assert !file.isRoot();
// Assert that the child file can be resolved into the same file using getDirectChild()
child = parent.getDirectChild(file.getName());
assert child != null;
assert child.equals(file);
file = parent;
}
// Assert that the root file's parent URL is null: if that is not the case, the parent file should have been
// resolved.
assert file.getURL().getParent()==null;
// A file that has no parent should be a root file
assert file.isRoot();
}
/**
* Tests {@link com.mucommander.commons.file.AbstractFile#exists()} in various situations.
*
* @throws IOException should not happen
*/
@Test
public void testExists() throws IOException {
assert !tempFile.exists();
tempFile.mkfile();
assert tempFile.exists();
tempFile.delete();
assert !tempFile.exists();
tempFile.mkdir();
assert tempFile.exists();
tempFile.delete();
assert !tempFile.exists();
}
/**
* Tests the {@link AbstractFile#delete()} method in various situations.
*
* @throws IOException should not happen
*/
@Test
public void testDelete() throws IOException {
if(tempFile.isFileOperationSupported(FileOperation.DELETE))
testDeleteSupported();
else
testDeleteUnsupported();
}
/**
* Tests the {@link AbstractFile#mkdir()} method in various situations.
*
* @throws IOException should not happen
*/
@Test
public void testMkdir() throws IOException {
if(tempFile.isFileOperationSupported(FileOperation.CREATE_DIRECTORY))
testMkdirSupported();
else
testMkdirUnsupported();
}
/**
* Tests the {@link AbstractFile#mkdirs()} method in various situations.
*
* @throws IOException should not happen
*/
@Test
public void testMkdirs() throws IOException {
// Require the 'create directory' operation to be supported
if(!tempFile.isFileOperationSupported(FileOperation.CREATE_DIRECTORY))
return;
// Assert that a directory can be created when the file doesn't already exist (without throwing an IOException)
AbstractFile dir1 = tempFile.getDirectChild("dir1");
AbstractFile dir2 = dir1.getDirectChild("dir2");
AbstractFile dir2b = dir1.getChild("dir2"+dir1.getSeparator()); // Same file with a trailing separator
dir2.mkdirs();
// Assert that the file exists after the directory has been created
assert dir2.exists();
assert dir2.isDirectory();
assert dir2b.exists();
assert dir2b.isDirectory();
// Delete 'dir2' and perform the same test. The difference with the previous test is that 'temp' and 'dir1' exist.
dir2.delete();
assert !dir2.exists();
assert !dir2.isDirectory();
assert !dir2b.exists();
assert !dir2b.isDirectory();
dir2.mkdirs();
assert dir2.exists();
assert dir2.isDirectory();
assert dir2b.exists();
assert dir2b.isDirectory();
// Assert that an IOException is thrown when the directory already exists
boolean ioExceptionThrown = false;
try {
dir2.mkdirs();
}
catch(IOException e) {
ioExceptionThrown = true;
}
assert ioExceptionThrown;
// Assert that an IOException is thrown when a regular file exists
dir2.delete();
dir2.mkfile();
ioExceptionThrown = false;
try {
dir2.mkdir();
}
catch(IOException e) {
ioExceptionThrown = true;
}
assert ioExceptionThrown;
}
/**
* Tests the {@link AbstractFile#mkfile()} method in various situations.
*
* @throws IOException should not happen
*/
@Test
public void testMkfile() throws IOException {
// Assert that a file can be created when it doesn't already exist (without throwing an IOException)
tempFile.mkfile();
// Assert that the file exists after it has been created
assert tempFile.exists();
// Assert that an IOException is thrown when the file already exists
boolean ioExceptionThrown = false;
try {
tempFile.mkfile();
}
catch(IOException e) {
ioExceptionThrown = true;
}
assert ioExceptionThrown;
// Assert that an IOException is thrown when a directory exists
tempFile.delete();
tempFile.mkdir();
ioExceptionThrown = false;
try {
tempFile.mkfile();
}
catch(IOException e) {
ioExceptionThrown = true;
}
assert ioExceptionThrown;
}
/**
* Tests the {@link AbstractFile#isDirectory()} method in various situations.
*
* @throws IOException should not happen
*/
@Test
public void testIsDirectory() throws IOException {
// Same file with a trailing separator
FileURL tempFileURLB = (FileURL)tempFile.getURL().clone();
tempFileURLB.setPath(tempFile.addTrailingSeparator(tempFileURLB.getPath()));
AbstractFile tempFileB = FileFactory.getFile(tempFileURLB, true);
// Assert that isDirectory() returns false when the file does not exist
assert !tempFile.exists();
assert !tempFile.isDirectory();
assert !tempFileB.exists();
assert !tempFileB.isDirectory();
// Assert that isDirectory() returns true for directories
tempFile.mkdir();
assert tempFile.exists();
assert tempFile.isDirectory();
assert tempFileB.exists();
assert tempFileB.isDirectory();
// Assert that isDirectory() returns false for regular files
tempFile.delete();
assert !tempFile.exists();
assert !tempFile.isDirectory();
assert !tempFileB.exists();
assert !tempFileB.isDirectory();
tempFile.mkfile();
assert tempFile.exists();
assert !tempFile.isDirectory();
assert tempFile.exists();
assert !tempFileB.isDirectory();
}
/**
* Tests {@link AbstractFile#changePermissions(int)}, calling {@link #testChangePermissionsSupported()} or
* {@link #testChangePermissionsUnsupported()} depending on whether or not the {@link FileOperation#CHANGE_PERMISSION}
* operation is supported.
*
* @throws IOException should not happen
* @throws NoSuchAlgorithmException should not happen
*/
@Test
public void testChangePermissions() throws IOException, NoSuchAlgorithmException {
if(tempFile.isFileOperationSupported(FileOperation.CHANGE_PERMISSION))
testChangePermissionsSupported();
else
testChangePermissionsUnsupported();
}
/**
* Tests {@link AbstractFile#getPermissions()}.
*
* @throws IOException should not happen
* @throws NoSuchAlgorithmException should not happen
*/
@Test
public void testGetPermissions() throws IOException, NoSuchAlgorithmException {
assert tempFile.getPermissions() != null;
createFile(tempFile, 0);
FilePermissions permissions = tempFile.getPermissions();
PermissionBits getPermMask = permissions.getMask();
assert permissions != null;
int getPermMaskInt = getPermMask.getIntValue();
int bitShift = 0;
int bitMask;
boolean canGetPermission;
for(PermissionAccess a : PermissionAccess.values()) {
for(PermissionType p : PermissionType.values()) {
bitMask = 1<<bitShift;
canGetPermission = (getPermMaskInt & bitMask)!=0;
assert getPermMask.getBitValue(a, p)==canGetPermission: "inconsistent bit and int value for ("+a+", "+p+")";
if(canGetPermission) {
assert permissions.getBitValue(a, p)==((permissions.getIntValue() & bitMask)!=0):
"inconsistent bit and int value for ("+a+", "+p+")";
}
bitShift++;
}
}
}
/**
* Tests {@link AbstractFile#changeDate(long)}, calling {@link #testChangeDateSupported()} or
* {@link #testChangeDateUnsupported()} depending on whether or not the {@link FileOperation#CHANGE_DATE}
* operation is supported.
*
* @throws IOException should not happen
* @throws NoSuchAlgorithmException should not happen
*/
@Test
public void testChangeDate() throws IOException, NoSuchAlgorithmException {
if(tempFile.isFileOperationSupported(FileOperation.CHANGE_DATE))
testChangeDateSupported();
else
testChangeDateUnsupported();
}
/**
* Tests {@link AbstractFile#getDate()}.
*
* @throws IOException should not happen
* @throws NoSuchAlgorithmException should not happen
*/
@Test
public void testGetDate() throws IOException, NoSuchAlgorithmException {
createFile(tempFile, 0);
// Asserts that the date changes when the file is modified
long date = tempFile.getDate();
sleep(1000); // Sleep a full second, some filesystems may only have a one-second granularity
createFile(tempFile, 1); // 1 byte should be enough
assert tempFile.getDate()>date;
}
/**
* Tests {@link AbstractFile#getInputStream()}, calling {@link #testGetInputStreamSupported()} or
* {@link #testGetInputStreamUnsupported()} depending on whether or not the {@link FileOperation#READ_FILE}
* operation is supported.
*
* @throws IOException should not happen
* @throws NoSuchAlgorithmException should not happen
*/
@Test
public void testGetInputStream() throws IOException, NoSuchAlgorithmException {
if(tempFile.isFileOperationSupported(FileOperation.READ_FILE))
testGetInputStreamSupported();
else
testGetInputStreamUnsupported();
}
/**
* Tests {@link AbstractFile#getRandomAccessInputStream()}, calling {@link #testGetRandomAccessInputStreamSupported()}
* or {@link #testGetRandomAccessInputStreamUnsupported()} depending on whether or not the
* {@link FileOperation#RANDOM_READ_FILE} operation is supported.
*
* @throws IOException should not happen
* @throws NoSuchAlgorithmException should not happen
*/
@Test
public void testGetRandomAccessInputStream() throws IOException, NoSuchAlgorithmException {
if(tempFile.isFileOperationSupported(FileOperation.RANDOM_READ_FILE))
testGetRandomAccessInputStreamSupported();
else
testGetRandomAccessInputStreamUnsupported();
}
/**
* Tests {@link AbstractFile#getOutputStream()}, calling {@link #testGetOutputStreamSupported()}
* or {@link #testGetOutputStreamUnsupported()} depending on whether or not the
* {@link FileOperation#WRITE_FILE} operation is supported.
*
* @throws IOException should not happen
* @throws NoSuchAlgorithmException should not happen
*/
@Test
public void testGetOutputStream() throws IOException, NoSuchAlgorithmException {
if(tempFile.isFileOperationSupported(FileOperation.WRITE_FILE))
testGetOutputStreamSupported();
else
testGetOutputStreamUnsupported();
}
/**
* Tests {@link AbstractFile#getAppendOutputStream()}, calling {@link #testGetAppendOutputStreamSupported()}
* or {@link #testGetAppendOutputStreamUnsupported()} depending on whether or not the
* {@link FileOperation#APPEND_FILE} operation is supported.
*
* @throws IOException should not happen
* @throws NoSuchAlgorithmException should not happen
*/
@Test
public void testGetAppendOutputStream() throws IOException, NoSuchAlgorithmException {
if(tempFile.isFileOperationSupported(FileOperation.APPEND_FILE))
testGetAppendOutputStreamSupported();
else
testGetAppendOutputStreamUnsupported();
}
/**
* Tests {@link AbstractFile#getRandomAccessOutputStream()}, calling {@link #testGetRandomAccessOutputStreamSupported()}
* or {@link #testGetRandomAccessOutputStreamUnsupported()} depending on whether or not the
* {@link FileOperation#RANDOM_WRITE_FILE} operation is supported.
*
* @throws IOException should not happen
* @throws NoSuchAlgorithmException should not happen
*/
@Test
public void testGetRandomAccessOutputStream() throws IOException, NoSuchAlgorithmException {
if(tempFile.isFileOperationSupported(FileOperation.RANDOM_WRITE_FILE))
testGetRandomAccessOutputStreamSupported();
else
testGetRandomAccessOutputStreamUnsupported();
}
/**
* Tests {@link AbstractFile#ls()}.
*
* @throws IOException should not happen
*/
@Test
public void testLs() throws IOException {
if(tempFile.isFileOperationSupported(FileOperation.LIST_CHILDREN))
testLsSupported();
else
testLsUnsupported();
}
/**
* Tests {@link AbstractFile#getFreeSpace()}.
*
* @throws IOException should not happen
*/
@Test
public void testFreeSpace() throws IOException {
if(tempFile.isFileOperationSupported(FileOperation.GET_FREE_SPACE))
testGetFreeSpaceSupported();
else
testGetFreeSpaceUnsupported();
}
/**
* Tests {@link AbstractFile#getTotalSpace()}.
*
* @throws IOException should not happen
*/
@Test
public void testTotalSpace() throws IOException {
if(tempFile.isFileOperationSupported(FileOperation.GET_TOTAL_SPACE))
testGetTotalSpaceSupported();
else
testGetTotalSpaceUnsupported();
}
/**
* Tests {@link AbstractFile#moveTo(AbstractFile)}.
*
* @throws IOException should not happen
* @throws NoSuchAlgorithmException should not happen
*/
@Test
public void testMoveTo() throws IOException, NoSuchAlgorithmException {
testMoveTo(false);
}
/**
* Tests {@link AbstractFile#renameTo(AbstractFile)}.
*
* @throws IOException should not happen
* @throws NoSuchAlgorithmException should not happen
*/
@Test
public void testRenameTo() throws IOException, NoSuchAlgorithmException {
if(tempFile.isFileOperationSupported(FileOperation.RENAME))
testRenameToSupported();
else
testRenameToUnsupported();
}
/**
* Tests {@link AbstractFile#copyTo(AbstractFile)}.
*
* @throws IOException should not happen
* @throws NoSuchAlgorithmException should not happen
*/
@Test
public void testCopyTo() throws IOException, NoSuchAlgorithmException {
testCopyTo(false);
}
/**
* Tests {@link AbstractFile#copyRemotelyTo(AbstractFile)}.
*
* @throws IOException should not happen
* @throws NoSuchAlgorithmException should not happen
*/
@Test
public void testCopyRemotelyTo() throws IOException, NoSuchAlgorithmException {
if(tempFile.isFileOperationSupported(FileOperation.COPY_REMOTELY))
testCopyRemotelyToSupported();
else
testCopyRemotelyToUnsupported();
}
/**
* Tests {@link AbstractFile#getIcon()} and {@link AbstractFile#getIcon(java.awt.Dimension)}.
*
* @throws IOException should not happen
*/
@Test
public void testIcon() throws IOException {
Icon icon;
boolean isHeadless;
// Skips the test if under OS X (this would create a new instance of JFileChooser, which fails
isHeadless = GraphicsEnvironment.isHeadless();
if(isHeadless && OsFamily.MAC_OS_X.isCurrent())
return;
// Some icon providers will fail (return a null icon) if the file doesn't exist
tempFile.mkfile();
icon = tempFile.getIcon();
if(!isHeadless)
assert icon != null;
icon = tempFile.getIcon(new Dimension(16, 16));
if(!isHeadless)
assert icon != null;
}
/**
* Verifies that the file implementation handles unicode/non-ascii filenames properly.
*
* @throws IOException should not happen
*/
@Test
public void testUnicodeFilenames() throws IOException {
tempFile.mkdir();
String unicodeFilename = "どうもありがとうミスターロボット";
Locale filenameLocale = Locale.JAPANESE;
testUnicodeFilename(tempFile, unicodeFilename, filenameLocale, false);
testUnicodeFilename(tempFile, unicodeFilename, filenameLocale, true);
}
/**
* Tests {@link com.mucommander.commons.file.util.PathUtils#resolveDestination(String, AbstractFile)} by calling
* {@link PathUtilsTest#testResolveDestination(AbstractFile)} with a temporary folder.
*
* @throws IOException should not happen
*/
@Test
public void testDestinationResolution() throws IOException {
AbstractFile folder = deleteWhenFinished(getTemporaryFile());
folder.mkdir();
new PathUtilsTest().testResolveDestination(folder);
}
/**
* Tests the absence of {@link UnsupportedFileOperation} annotations in all methods corresponding to
* {@link #getSupportedOperations() supported operations}, and the absence thereof for unsupported operations.
*
* @throws Exception should not happen
*/
@Test
public void testUnsupportedFileOperationAnnotations() throws Exception {
List<FileOperation> supportedOps = Arrays.asList(getSupportedOperations());
Class<? extends AbstractFile> fileClass = tempFile.getClass();
Method m;
for(FileOperation op: FileOperation.values()) {
m = op.getCorrespondingMethod(fileClass);
assert supportedOps.contains(op) ==
!fileClass.getMethod(m.getName(), m.getParameterTypes()).isAnnotationPresent(UnsupportedFileOperation.class)
:"File operation "+op+" does not match annotation of method "+m.getName();
}
}
/**
* Ensures that the return value of {@link AbstractFile#isFileOperationSupported(FileOperation)} is consistent
* with {@link #getSupportedOperations() supported operations}.
*
* @throws Exception should not happen
*/
@Test
public void testSupportedFileOperations() throws Exception {
List<FileOperation> supportedOps = Arrays.asList(getSupportedOperations());
AbstractFile tempFile = getTemporaryFile();
Class<? extends AbstractFile> fileClass = tempFile.getClass();
for(FileOperation op: FileOperation.values()) {
boolean opSupported = supportedOps.contains(op);
assert opSupported == tempFile.isFileOperationSupported(op);
assert opSupported == AbstractFile.isFileOperationSupported(op, fileClass);
}
}
/**
* Ensures that {@link AbstractFile} instance caching works as expected, that is the same instance is returned
* by <code>FileFactory#getFile</code> methods every time the same location is asked for.
*
* @throws Exception should not happen
*/
@Test
public void testFileInstanceCaching() throws Exception {
AbstractFile file;
for(int i=0; i<10; i++) {
file = getTemporaryFile();
for(int j=0; j<5; j++) {
// Resolve by path
String pathT = file.addTrailingSeparator(file.getURL().toString(true, false));
String pathNT = file.removeTrailingSeparator(pathT);
assert FileFactory.getFile(pathT)==file;
assert FileFactory.getFile(pathNT)==file;
// Resolve by URL
assert FileFactory.getFile(file.getURL())==file;
assert FileFactory.getFile(FileURL.getFileURL(pathT))==file;
assert FileFactory.getFile(FileURL.getFileURL(pathNT))==file;
}
}
}
//////////////////////
// Abstract methods //
//////////////////////
/**
* Returns a temporary file that can be used for testing purposes. Implementation of this method must guarantee that:
* <ul>
* <li>the returned file does not exist, i.e. that {@link AbstractFile#exists()} returns <code>false</code>.</li>
* <li>a new file is returned each time this method is called.</li>
* <li>the return file's path does not end with a trailing path separator</li>
* </ul>
*
* @return a temporary file that does not exist
* @throws IOException if an error occurred while creating a temporary file
*/
public abstract AbstractFile getTemporaryFile() throws IOException;
/**
* Returns a list of all {@link FileOperation} supported by this file implementation.
*
* @return a list of all {@link FileOperation} supported by this file implementation.
*/
public abstract FileOperation[] getSupportedOperations();
}