/*
* The Alluxio Open Foundation licenses this work under the Apache License, version 2.0
* (the "License"). You may not use this work except in compliance with the License, which is
* available at www.apache.org/licenses/LICENSE-2.0
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied, as more fully set forth in the License.
*
* See the NOTICE file distributed with this work for information regarding copyright ownership.
*/
package alluxio.util.io;
import alluxio.AlluxioURI;
import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.rules.TemporaryFolder;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.attribute.PosixFilePermissions;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
/**
* Tests for the {@link FileUtils} class.
*/
public class FileUtilsTest {
/**
* The temporary folder.
*/
@Rule
public TemporaryFolder mTestFolder = new TemporaryFolder();
/**
* The expected exception thrown during a test.
*/
@Rule
public final ExpectedException mException = ExpectedException.none();
/**
* Tests the {@link FileUtils#changeLocalFilePermission(String, String)} method.
*/
@Test
public void changeLocalFilePermission() throws IOException {
File tempFile = mTestFolder.newFile("perm.txt");
FileUtils.changeLocalFilePermission(tempFile.getAbsolutePath(), "---------");
Assert.assertFalse(tempFile.canRead() || tempFile.canWrite() || tempFile.canExecute());
FileUtils.changeLocalFilePermission(tempFile.getAbsolutePath(), "rwxrwxrwx");
Assert.assertTrue(tempFile.canRead() && tempFile.canWrite() && tempFile.canExecute());
// File deletion should fail, because we don't have write permissions
FileUtils.changeLocalFilePermission(tempFile.getAbsolutePath(), "r--r--r--");
Assert.assertTrue(tempFile.canRead());
Assert.assertFalse(tempFile.canWrite());
Assert.assertFalse(tempFile.canExecute());
// expect a file permission error when we open it for writing
mException.expect(IOException.class);
@SuppressWarnings({"unused", "resource"})
FileWriter fw = new FileWriter(tempFile);
Assert.fail("opening a read-only file for writing should have failed");
}
/**
* Tests the {@link FileUtils#changeLocalFilePermission(String, String)} method for a non-existent
* file to thrown an exception.
*/
@Test
public void changeNonExistentFile() throws IOException {
// ghostFile is never created, so changing permission should fail
File ghostFile = new File(mTestFolder.getRoot(), "ghost.txt");
mException.expect(IOException.class);
FileUtils.changeLocalFilePermission(ghostFile.getAbsolutePath(), "rwxrwxrwx");
Assert.fail("changing permissions of a non-existent file should have failed");
}
/**
* Tests the {@link FileUtils#changeLocalFilePermission(String, String)} method for a directory.
*/
@Test
public void changeLocalDirPermissionTests() throws IOException {
File tempFile = mTestFolder.newFile("perm.txt");
// Change permission on directories
FileUtils.changeLocalFilePermission(mTestFolder.getRoot().getAbsolutePath(), "r--r--r--");
Assert.assertFalse(tempFile.delete());
FileUtils.changeLocalFilePermission(mTestFolder.getRoot().getAbsolutePath(), "rwxr--r--");
Assert.assertTrue(tempFile.delete());
}
/**
* Tests the {@link FileUtils#move(String, String)} method.
*/
@Test
public void moveFile() throws IOException {
File fromFile = mTestFolder.newFile("from.txt");
File toFile = mTestFolder.newFile("to.txt");
// Move a file and verify
FileUtils.move(fromFile.getAbsolutePath(), toFile.getAbsolutePath());
Assert.assertFalse(fromFile.exists());
Assert.assertTrue(toFile.exists());
}
/**
* Tests the {@link FileUtils#move(String, String)} method to thrown an exception when trying to
* move a non-existent file.
*/
@Test
public void moveNonExistentFile() throws IOException {
// ghostFile is never created, so deleting should fail
File ghostFile = new File(mTestFolder.getRoot(), "ghost.txt");
File toFile = mTestFolder.newFile("to.txt");
mException.expect(IOException.class);
FileUtils.move(ghostFile.getAbsolutePath(), toFile.getAbsolutePath());
Assert.fail("moving a non-existent file should have failed");
}
/**
* Tests the {@link FileUtils#delete(String)} method when trying to delete a file and a directory.
*/
@Test
public void deleteFile() throws IOException {
File tempFile = mTestFolder.newFile("fileToDelete");
File tempFolder = mTestFolder.newFolder("dirToDelete");
// Delete a file and a directory
FileUtils.delete(tempFile.getAbsolutePath());
FileUtils.delete(tempFolder.getAbsolutePath());
Assert.assertFalse(tempFile.exists());
Assert.assertFalse(tempFolder.exists());
}
/**
* Tests the {@link FileUtils#deletePathRecursively(String)} method when trying to delete
* directories.
*/
@Test
public void deletePathRecursively() throws IOException {
File tmpDir = mTestFolder.newFolder("dir");
File tmpDir1 = mTestFolder.newFolder("dir", "dir1");
File tmpDir2 = mTestFolder.newFolder("dir", "dir2");
File tmpFile1 = mTestFolder.newFile("dir/dir1/file1");
File tmpFile2 = mTestFolder.newFile("dir/dir1/file2");
File tmpFile3 = mTestFolder.newFile("dir/file3");
// Delete all of these.
FileUtils.deletePathRecursively(tmpDir.getAbsolutePath());
Assert.assertFalse(tmpDir.exists());
Assert.assertFalse(tmpDir1.exists());
Assert.assertFalse(tmpDir2.exists());
Assert.assertFalse(tmpFile1.exists());
Assert.assertFalse(tmpFile2.exists());
Assert.assertFalse(tmpFile3.exists());
}
/**
* Tests the {@link FileUtils#delete(String)} method to throw an exception when trying to delete a
* non-existent file.
*/
@Test
public void deleteNonExistentFile() throws IOException {
// ghostFile is never created, so deleting should fail
File ghostFile = new File(mTestFolder.getRoot(), "ghost.txt");
mException.expect(IOException.class);
FileUtils.delete(ghostFile.getAbsolutePath());
Assert.fail("deleting a non-existent file should have failed");
}
/**
* Tests the {@link FileUtils#setLocalDirStickyBit(String)} method.
*/
@Test
public void setLocalDirStickyBit() throws IOException {
File tempFolder = mTestFolder.newFolder("dirToModify");
// Only test this functionality of the absolute path of the temporary directory starts with "/",
// which implies the host should support "chmod".
if (tempFolder.getAbsolutePath().startsWith(AlluxioURI.SEPARATOR)) {
FileUtils.setLocalDirStickyBit(tempFolder.getAbsolutePath());
List<String> commands = new ArrayList<>();
commands.add("/bin/ls");
commands.add("-ld");
commands.add(tempFolder.getAbsolutePath());
try {
ProcessBuilder builder = new ProcessBuilder(commands);
Process process = builder.start();
process.waitFor();
BufferedReader stdInput = new BufferedReader(new
InputStreamReader(process.getInputStream()));
String line = stdInput.readLine();
// we are just concerned about the first and the last permission bits
Assert.assertTrue(line.matches("^d[rwx-]{8}t.*$"));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
* Tests the {@link FileUtils#createBlockPath(String)} method.
*/
@Test
public void createBlockPath() throws IOException {
String absolutePath = PathUtils.concatPath(mTestFolder.getRoot(), "tmp", "bar");
File tempFile = new File(absolutePath);
FileUtils.createBlockPath(tempFile.getAbsolutePath());
Assert.assertTrue(FileUtils.exists(tempFile.getParent()));
}
/**
* Tests the {@link FileUtils#createFile(String)} method.
*/
@Test
public void createFile() throws IOException {
File tempFile = new File(mTestFolder.getRoot(), "tmp");
FileUtils.createFile(tempFile.getAbsolutePath());
Assert.assertTrue(FileUtils.exists(tempFile.getAbsolutePath()));
Assert.assertTrue(tempFile.delete());
}
/**
* Tests the {@link FileUtils#createDir(String)} method.
*/
@Test
public void createDir() throws IOException {
File tempDir = new File(mTestFolder.getRoot(), "tmp");
FileUtils.createDir(tempDir.getAbsolutePath());
Assert.assertTrue(FileUtils.exists(tempDir.getAbsolutePath()));
Assert.assertTrue(tempDir.delete());
}
/**
* Tests the {@link FileUtils#getLocalFileMode(String)}} method.
*/
@Test
public void getLocalFileMode() throws IOException {
File tmpDir = mTestFolder.newFolder("dir");
File tmpFile777 = mTestFolder.newFile("dir/0777");
tmpFile777.setReadable(true, false /* owner only */);
tmpFile777.setWritable(true, false /* owner only */);
tmpFile777.setExecutable(true, false /* owner only */);
File tmpFile755 = mTestFolder.newFile("dir/0755");
tmpFile755.setReadable(true, false /* owner only */);
tmpFile755.setWritable(false, false /* owner only */);
tmpFile755.setExecutable(true, false /* owner only */);
tmpFile755.setWritable(true, true /* owner only */);
File tmpFile444 = mTestFolder.newFile("dir/0444");
tmpFile444.setReadOnly();
Assert.assertEquals((short) 0777, FileUtils.getLocalFileMode(tmpFile777.getPath()));
Assert.assertEquals((short) 0755, FileUtils.getLocalFileMode(tmpFile755.getPath()));
Assert.assertEquals((short) 0444, FileUtils.getLocalFileMode(tmpFile444.getPath()));
// Delete all of these.
FileUtils.deletePathRecursively(tmpDir.getAbsolutePath());
}
/**
* Tests {@link FileUtils#createBlockPath} method when storage dir exists or doesn't exist.
*/
@Test
public void createStorageDirPath() throws IOException {
File storageDir = new File(mTestFolder.getRoot(), "storageDir");
File blockFile = new File(storageDir, "200");
// When storage dir doesn't exist
FileUtils.createBlockPath(blockFile.getAbsolutePath());
Assert.assertTrue(FileUtils.exists(storageDir.getAbsolutePath()));
Assert.assertEquals(
PosixFilePermissions.fromString("rwxrwxrwx"),
Files.getPosixFilePermissions(Paths.get(storageDir.getAbsolutePath())));
// When storage dir exists
FileUtils.createBlockPath(blockFile.getAbsolutePath());
Assert.assertTrue(FileUtils.exists(storageDir.getAbsolutePath()));
}
/**
* Tests invoking {@link FileUtils#createBlockPath} method concurrently. This simulates the case
* when multiple blocks belonging to the same storage dir get created concurrently.
*/
@Test
public void concurrentCreateStorageDirPath() throws Exception {
/**
* A class provides multiple concurrent threads to invoke {@link FileUtils#createBlockPath}.
*/
class ConcurrentCreator implements Callable<Void> {
private final String mPath;
private final CyclicBarrier mBarrier;
ConcurrentCreator(String path, CyclicBarrier barrier) {
mPath = path;
mBarrier = barrier;
}
@Override
public Void call() throws Exception {
mBarrier.await(); // Await until all threads submitted
FileUtils.createBlockPath(mPath);
return null;
}
}
final int numCreators = 5;
List<Future<Void>> futures = new ArrayList<>(numCreators);
for (int iteration = 0; iteration < 5; iteration++) {
final ExecutorService executor = Executors.newFixedThreadPool(numCreators);
final CyclicBarrier barrier = new CyclicBarrier(numCreators);
try {
File storageDir = new File(mTestFolder.getRoot(), "tmp" + iteration);
for (int i = 0; i < numCreators; i++) {
File blockFile = new File(storageDir, String.valueOf(i));
futures.add(executor.submit(new ConcurrentCreator(blockFile.getAbsolutePath(), barrier)));
}
for (Future<Void> f : futures) {
f.get();
}
Assert.assertTrue(FileUtils.exists(storageDir.getAbsolutePath()));
} finally {
executor.shutdown();
}
}
}
}