blob: 4529cc8b4e621c57ae9abbe8a3cf95ee9b9c9743 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hadoop.fs.contract;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.List;
import org.apache.hadoop.fs.FileStatus;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.FilterFileSystem;
import org.apache.hadoop.fs.LocatedFileStatus;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.fs.PathFilter;
import org.apache.hadoop.fs.RemoteIterator;
import org.apache.hadoop.test.LambdaTestUtils;
import org.junit.Test;
import static org.apache.hadoop.fs.contract.ContractTestUtils.*;
import static org.apache.hadoop.test.LambdaTestUtils.intercept;
/**
* Test getFileStatus and related listing operations.
*/
public abstract class AbstractContractGetFileStatusTest extends
AbstractFSContractTestBase {
private Path testPath;
private Path target;
// the tree parameters. Kept small to avoid killing object store test
// runs too much.
private static final int TREE_DEPTH = 2;
private static final int TREE_WIDTH = 3;
private static final int TREE_FILES = 4;
private static final int TREE_FILESIZE = 512;
@Override
public void setup() throws Exception {
super.setup();
skipIfUnsupported(SUPPORTS_GETFILESTATUS);
//delete the test directory
testPath = path("test");
target = new Path(testPath, "target");
}
@Test
public void testGetFileStatusNonexistentFile() throws Throwable {
try {
FileStatus status = getFileSystem().getFileStatus(target);
//got here: trouble
fail("expected a failure, got " + status);
} catch (FileNotFoundException e) {
//expected
handleExpectedException(e);
}
}
@Test
public void testGetFileStatusRoot() throws Throwable {
ContractTestUtils.assertIsDirectory(
getFileSystem().getFileStatus(new Path("/")));
}
@Test
public void testListStatusEmptyDirectory() throws IOException {
describe("List status on an empty directory");
Path subfolder = createDirWithEmptySubFolder();
FileSystem fs = getFileSystem();
Path path = getContract().getTestPath();
new TreeScanResults(fs.listStatus(path))
.assertSizeEquals("listStatus(" + path + ")", 0, 1, 0);
describe("Test on empty subdirectory");
new TreeScanResults(fs.listStatus(subfolder))
.assertSizeEquals("listStatus(empty subfolder)", 0, 0, 0);
}
@Test
public void testListFilesEmptyDirectoryNonrecursive() throws IOException {
listFilesOnEmptyDir(false);
}
@Test
public void testListFilesEmptyDirectoryRecursive() throws IOException {
listFilesOnEmptyDir(true);
}
/**
* Call listFiles on an directory with an empty subdir.
* @param recursive should the list be recursive?
* @throws IOException IO Problems
*/
private void listFilesOnEmptyDir(boolean recursive) throws IOException {
describe("Invoke listFiles(recursive=" + recursive + ")" +
" on empty directories, expect nothing found");
Path subfolder = createDirWithEmptySubFolder();
FileSystem fs = getFileSystem();
new TreeScanResults(fs.listFiles(getContract().getTestPath(), recursive))
.assertSizeEquals("listFiles(test dir, " + recursive + ")", 0, 0, 0);
describe("Test on empty subdirectory");
new TreeScanResults(fs.listFiles(subfolder, recursive))
.assertSizeEquals("listFiles(empty subfolder, " + recursive + ")",
0, 0, 0);
}
@Test
public void testListLocatedStatusEmptyDirectory() throws IOException {
describe("Invoke listLocatedStatus() on empty directories;" +
" expect directories to be found");
Path subfolder = createDirWithEmptySubFolder();
FileSystem fs = getFileSystem();
new TreeScanResults(fs.listLocatedStatus(getContract().getTestPath()))
.assertSizeEquals("listLocatedStatus(test dir)", 0, 1, 0);
describe("Test on empty subdirectory");
new TreeScanResults(fs.listLocatedStatus(subfolder))
.assertSizeEquals("listLocatedStatus(empty subfolder)", 0, 0, 0);
}
/**
* All tests cases against complex directories are aggregated into one, so
* that the setup and teardown costs against object stores can be shared.
* @throws Throwable
*/
@Test
public void testComplexDirActions() throws Throwable {
TreeScanResults tree = createTestTree();
checkListStatusStatusComplexDir(tree);
checkListLocatedStatusStatusComplexDir(tree);
checkListFilesComplexDirNonRecursive(tree);
checkListFilesComplexDirRecursive(tree);
}
/**
* Test {@link FileSystem#listStatus(Path)} on a complex
* directory tree.
* @param tree directory tree to list.
* @throws Throwable
*/
protected void checkListStatusStatusComplexDir(TreeScanResults tree)
throws Throwable {
describe("Expect listStatus to list all entries in top dir only");
FileSystem fs = getFileSystem();
TreeScanResults listing = new TreeScanResults(
fs.listStatus(tree.getBasePath()));
listing.assertSizeEquals("listStatus()", TREE_FILES, TREE_WIDTH, 0);
}
/**
* Test {@link FileSystem#listLocatedStatus(Path)} on a complex
* directory tree.
* @param tree directory tree to list.
* @throws Throwable
*/
protected void checkListLocatedStatusStatusComplexDir(TreeScanResults tree)
throws Throwable {
describe("Expect listLocatedStatus to list all entries in top dir only");
FileSystem fs = getFileSystem();
TreeScanResults listing = new TreeScanResults(
fs.listLocatedStatus(tree.getBasePath()));
listing.assertSizeEquals("listLocatedStatus()", TREE_FILES, TREE_WIDTH, 0);
verifyFileStats(fs.listLocatedStatus(tree.getBasePath()));
// listLocatedStatus and listStatus must return the same files.
TreeScanResults listStatus = new TreeScanResults(
fs.listStatus(tree.getBasePath()));
listing.assertEquivalent(listStatus);
// now check without using
List<LocatedFileStatus> statusThroughNext = toListThroughNextCallsAlone(
fs.listLocatedStatus(tree.getBasePath())
);
TreeScanResults resultsThroughNext = new TreeScanResults(statusThroughNext);
listStatus.assertFieldsEquivalent("files", listing,
listStatus.getFiles(),
resultsThroughNext.getFiles());
}
/**
* Test {@link FileSystem#listFiles(Path, boolean)} on a complex
* directory tree and the recursive flag set to false.
* @param tree directory tree to list.
* @throws Throwable
*/
protected void checkListFilesComplexDirNonRecursive(TreeScanResults tree)
throws Throwable {
describe("Expect non-recursive listFiles(false) to list all entries" +
" in top dir only");
FileSystem fs = getFileSystem();
TreeScanResults listing = new TreeScanResults(
fs.listFiles(tree.getBasePath(), false));
listing.assertSizeEquals("listFiles(false)", TREE_FILES, 0, 0);
verifyFileStats(fs.listFiles(tree.getBasePath(), false));
// the files listed should match the set of files in a listStatus() call.
// the directories are not checked
TreeScanResults listStatus = new TreeScanResults(
fs.listStatus(tree.getBasePath()));
listStatus.assertFieldsEquivalent("files", listing,
listStatus.getFiles(),
listing.getFiles());
List<LocatedFileStatus> statusThroughNext = toListThroughNextCallsAlone(
fs.listFiles(tree.getBasePath(), false));
TreeScanResults resultsThroughNext = new TreeScanResults(statusThroughNext);
listStatus.assertFieldsEquivalent("files", listing,
listStatus.getFiles(),
resultsThroughNext.getFiles());
}
/**
* Test {@link FileSystem#listFiles(Path, boolean)} on a complex
* directory tree and the recursive flag set to true.
* @param tree directory tree to list.
* @throws Throwable
*/
protected void checkListFilesComplexDirRecursive(TreeScanResults tree)
throws Throwable {
describe("Expect recursive listFiles(true) to" +
" list all files down the tree");
FileSystem fs = getFileSystem();
TreeScanResults listing = new TreeScanResults(
fs.listFiles(tree.getBasePath(), true));
// files are checked, but not the directories.
tree.assertFieldsEquivalent("files", listing, tree.getFiles(),
listing.getFiles());
int count = verifyFileStats(fs.listFiles(tree.getBasePath(), true));
// assert that the content matches that of a tree walk
describe("verifying consistency with treewalk's files");
TreeScanResults treeWalk = treeWalk(fs, tree.getBasePath());
treeWalk.assertFieldsEquivalent("files", listing,
treeWalk.getFiles(),
listing.getFiles());
assertEquals("Size of status list through next() calls",
count,
toListThroughNextCallsAlone(
fs.listFiles(tree.getBasePath(), true)).size());
}
@Test
public void testListFilesNoDir() throws Throwable {
describe("test the listFiles calls on a path which is not present");
Path path = path("missing");
try {
RemoteIterator<LocatedFileStatus> iterator
= getFileSystem().listFiles(path, false);
fail("Expected an exception, got an iterator: " + iterator);
} catch (FileNotFoundException expected) {
// expected
}
try {
RemoteIterator<LocatedFileStatus> iterator
= getFileSystem().listFiles(path, true);
fail("Expected an exception, got an iterator: " + iterator);
} catch (FileNotFoundException expected) {
// expected
}
}
@Test
public void testLocatedStatusNoDir() throws Throwable {
describe("test the LocatedStatus call on a path which is not present");
intercept(FileNotFoundException.class,
new LambdaTestUtils.VoidCallable() {
@Override
public void call() throws Exception {
getFileSystem().listLocatedStatus(path("missing"));
}
});
}
@Test
public void testListStatusNoDir() throws Throwable {
describe("test the listStatus(path) call on a path which is not present");
intercept(FileNotFoundException.class,
new LambdaTestUtils.VoidCallable() {
@Override
public void call() throws Exception {
getFileSystem().listStatus(path("missing"));
}
});
}
@Test
public void testListStatusFilteredNoDir() throws Throwable {
describe("test the listStatus(path, filter) call on a missing path");
intercept(FileNotFoundException.class,
new LambdaTestUtils.VoidCallable() {
@Override
public void call() throws Exception {
getFileSystem().listStatus(path("missing"), ALL_PATHS);
}
});
}
@Test
public void testListStatusFilteredFile() throws Throwable {
describe("test the listStatus(path, filter) on a file");
Path f = touchf("liststatus");
assertEquals(0, getFileSystem().listStatus(f, NO_PATHS).length);
}
@Test
public void testListStatusFile() throws Throwable {
describe("test the listStatus(path) on a file");
Path f = touchf("liststatusfile");
verifyStatusArrayMatchesFile(f, getFileSystem().listStatus(f));
}
@Test
public void testListFilesFile() throws Throwable {
describe("test the listStatus(path) on a file");
Path f = touchf("listfilesfile");
List<LocatedFileStatus> statusList = toList(
getFileSystem().listFiles(f, false));
assertEquals("size of file list returned", 1, statusList.size());
assertIsNamedFile(f, statusList.get(0));
List<LocatedFileStatus> statusList2 = toListThroughNextCallsAlone(
getFileSystem().listFiles(f, false));
assertEquals("size of file list returned through next() calls",
1, statusList2.size());
assertIsNamedFile(f, statusList2.get(0));
}
@Test
public void testListFilesFileRecursive() throws Throwable {
describe("test the listFiles(path, true) on a file");
Path f = touchf("listfilesRecursive");
List<LocatedFileStatus> statusList = toList(
getFileSystem().listFiles(f, true));
assertEquals("size of file list returned", 1, statusList.size());
assertIsNamedFile(f, statusList.get(0));
List<LocatedFileStatus> statusList2 = toListThroughNextCallsAlone(
getFileSystem().listFiles(f, true));
assertEquals("size of file list returned", 1, statusList2.size());
}
@Test
public void testListLocatedStatusFile() throws Throwable {
describe("test the listLocatedStatus(path) on a file");
Path f = touchf("listLocatedStatus");
List<LocatedFileStatus> statusList = toList(
getFileSystem().listLocatedStatus(f));
assertEquals("size of file list returned", 1, statusList.size());
assertIsNamedFile(f, statusList.get(0));
List<LocatedFileStatus> statusList2 = toListThroughNextCallsAlone(
getFileSystem().listLocatedStatus(f));
assertEquals("size of file list returned through next() calls",
1, statusList2.size());
}
/**
* Verify a returned status array matches a single named file.
* @param f filename
* @param status status array
*/
private void verifyStatusArrayMatchesFile(Path f, FileStatus[] status) {
assertEquals(1, status.length);
FileStatus fileStatus = status[0];
assertIsNamedFile(f, fileStatus);
}
/**
* Verify that a file status refers to a file at the given path.
* @param f filename
* @param fileStatus status to validate
*/
private void assertIsNamedFile(Path f, FileStatus fileStatus) {
assertEquals("Wrong pathname in " + fileStatus, f, fileStatus.getPath());
assertTrue("Not a file: " + fileStatus, fileStatus.isFile());
}
/**
* Touch a file with a given name; return the path.
* @param name name
* @return the full name
* @throws IOException IO Problems
*/
Path touchf(String name) throws IOException {
Path path = path(name);
ContractTestUtils.touch(getFileSystem(), path);
return path;
}
/**
* Clear the test directory and add an empty subfolder.
* @return the path to the subdirectory
* @throws IOException
*/
private Path createDirWithEmptySubFolder() throws IOException {
// remove the test directory
FileSystem fs = getFileSystem();
Path path = getContract().getTestPath();
fs.delete(path, true);
// create a - non-qualified - Path for a subdir
Path subfolder = path.suffix('/' + this.methodName.getMethodName());
mkdirs(subfolder);
return subfolder;
}
/**
* Create a test tree.
* @return the details about the created tree. The files and directories
* are those created under the path, not the base directory created.
* @throws IOException
*/
private TreeScanResults createTestTree() throws IOException {
return createSubdirs(getFileSystem(), path(methodName.getMethodName()),
TREE_DEPTH, TREE_WIDTH, TREE_FILES, TREE_FILESIZE);
}
/**
* Scan through a filestatus iterator, get the status of every element and
* verify core attributes. This should identify a situation where the
* attributes of a file/dir retrieved in a listing operation do not
* match the values individually retrieved. That is: the metadata returned
* in a directory listing is different from the explicitly retrieved data.
*
* Timestamps are not compared.
* @param results iterator to scan
* @return the number of entries in the result set
* @throws IOException any IO problem
*/
private int verifyFileStats(RemoteIterator<LocatedFileStatus> results)
throws IOException {
describe("verifying file statuses");
int count = 0;
while (results.hasNext()) {
count++;
LocatedFileStatus next = results.next();
FileStatus fileStatus = getFileSystem().getFileStatus(next.getPath());
assertEquals("isDirectory", fileStatus.isDirectory(), next.isDirectory());
assertEquals("isFile", fileStatus.isFile(), next.isFile());
assertEquals("getLen", fileStatus.getLen(), next.getLen());
assertEquals("getOwner", fileStatus.getOwner(), next.getOwner());
}
return count;
}
@Test
public void testListStatusFiltering() throws Throwable {
describe("Call listStatus() against paths and directories with filtering");
Path file1 = touchf("file-1.txt");
touchf("file-2.txt");
Path parent = file1.getParent();
FileStatus[] result;
verifyListStatus(0, parent, NO_PATHS);
verifyListStatus(2, parent, ALL_PATHS);
MatchesNameFilter file1Filter = new MatchesNameFilter("file-1.txt");
result = verifyListStatus(1, parent, file1Filter);
assertEquals(file1, result[0].getPath());
verifyListStatus(0, file1, NO_PATHS);
result = verifyListStatus(1, file1, ALL_PATHS);
assertEquals(file1, result[0].getPath());
result = verifyListStatus(1, file1, file1Filter);
assertEquals(file1, result[0].getPath());
// empty subdirectory
Path subdir = path("subdir");
mkdirs(subdir);
verifyListStatus(0, subdir, NO_PATHS);
verifyListStatus(0, subdir, ALL_PATHS);
verifyListStatus(0, subdir, new MatchesNameFilter("subdir"));
}
@Test
public void testListLocatedStatusFiltering() throws Throwable {
describe("Call listLocatedStatus() with filtering");
describe("Call listStatus() against paths and directories with filtering");
Path file1 = touchf("file-1.txt");
Path file2 = touchf("file-2.txt");
Path parent = file1.getParent();
FileSystem fs = getFileSystem();
touch(fs, file1);
touch(fs, file2);
// this is not closed: ignore any IDE warnings.
ExtendedFilterFS xfs = new ExtendedFilterFS(fs);
List<LocatedFileStatus> result;
verifyListStatus(0, parent, NO_PATHS);
verifyListStatus(2, parent, ALL_PATHS);
MatchesNameFilter file1Filter = new MatchesNameFilter("file-1.txt");
result = verifyListLocatedStatus(xfs, 1, parent, file1Filter);
assertEquals(file1, result.get(0).getPath());
verifyListLocatedStatus(xfs, 0, file1, NO_PATHS);
verifyListLocatedStatus(xfs, 1, file1, ALL_PATHS);
assertEquals(file1, result.get(0).getPath());
verifyListLocatedStatus(xfs, 1, file1, file1Filter);
assertEquals(file1, result.get(0).getPath());
verifyListLocatedStatusNextCalls(xfs, 1, file1, file1Filter);
// empty subdirectory
Path subdir = path("subdir");
mkdirs(subdir);
verifyListLocatedStatus(xfs, 0, subdir, NO_PATHS);
verifyListLocatedStatus(xfs, 0, subdir, ALL_PATHS);
verifyListLocatedStatusNextCalls(xfs, 0, subdir, ALL_PATHS);
verifyListLocatedStatus(xfs, 0, subdir, new MatchesNameFilter("subdir"));
}
/**
* Execute {@link FileSystem#listStatus(Path, PathFilter)},
* verify the length of the result, then return the listing.
* @param expected expected length
* @param path path to list
* @param filter filter to apply
* @return the listing
* @throws IOException IO Problems
*/
private FileStatus[] verifyListStatus(int expected,
Path path,
PathFilter filter) throws IOException {
FileStatus[] result = getFileSystem().listStatus(path, filter);
assertEquals("length of listStatus(" + path + ", " + filter + " )",
expected, result.length);
return result;
}
/**
* Execute {@link FileSystem#listLocatedStatus(Path, PathFilter)},
* generate a list from the iterator, verify the length of the list returned
* and then return it.
* @param expected expected length
* @param path path to list
* @param filter filter to apply
* @return the listing
* @throws IOException IO Problems
*/
private List<LocatedFileStatus> verifyListLocatedStatus(ExtendedFilterFS xfs,
int expected,
Path path,
PathFilter filter) throws IOException {
RemoteIterator<LocatedFileStatus> it = xfs.listLocatedStatus(path, filter);
List<LocatedFileStatus> result = toList(it);
assertEquals("length of listLocatedStatus(" + path + ", " + filter + " )",
expected, result.size());
return result;
}
/**
* Execute {@link FileSystem#listLocatedStatus(Path, PathFilter)},
* generate a list from the iterator, verify the length of the list returned
* and then return it.
* Uses {@link ContractTestUtils#toListThroughNextCallsAlone(RemoteIterator)}
* to stress the iteration process.
* @param expected expected length
* @param path path to list
* @param filter filter to apply
* @return the listing
* @throws IOException IO Problems
*/
private List<LocatedFileStatus> verifyListLocatedStatusNextCalls(
ExtendedFilterFS xfs,
int expected,
Path path,
PathFilter filter) throws IOException {
RemoteIterator<LocatedFileStatus> it = xfs.listLocatedStatus(path, filter);
List<LocatedFileStatus> result = toListThroughNextCallsAlone(it);
assertEquals("length of listLocatedStatus(" + path + ", " + filter + " )",
expected, result.size());
return result;
}
private static final PathFilter ALL_PATHS = new AllPathsFilter();
private static final PathFilter NO_PATHS = new NoPathsFilter();
/**
* Accept everything.
*/
private static final class AllPathsFilter implements PathFilter {
@Override
public boolean accept(Path path) {
return true;
}
}
/**
* Accept nothing.
*/
private static final class NoPathsFilter implements PathFilter {
@Override
public boolean accept(Path path) {
return false;
}
}
/**
* Path filter which only expects paths whose final name element
* equals the {@code match} field.
*/
private static final class MatchesNameFilter implements PathFilter {
private final String match;
MatchesNameFilter(String match) {
this.match = match;
}
@Override
public boolean accept(Path path) {
return match.equals(path.getName());
}
}
/**
* A filesystem filter which exposes the protected method
* {@link #listLocatedStatus(Path, PathFilter)}.
*/
protected static final class ExtendedFilterFS extends FilterFileSystem {
public ExtendedFilterFS(FileSystem fs) {
super(fs);
}
@Override
public RemoteIterator<LocatedFileStatus> listLocatedStatus(Path f,
PathFilter filter)
throws IOException {
return super.listLocatedStatus(f, filter);
}
}
}