[VFS] Add support for "no_push" to VFS recursive iterators.

The "regular" file system has a useful feature that makes it possible to
stop recursing when using the recursive directory iterators. This
functionality was missing for the VFS recursive iterator and this patch
adds that.

Differential revision: https://reviews.llvm.org/D53465

llvm-svn: 345793
This commit is contained in:
Jonas Devlieghere 2018-10-31 23:36:10 +00:00
parent e7c7934a11
commit 41fb951f87
3 changed files with 115 additions and 20 deletions

View File

@ -193,14 +193,22 @@ public:
class FileSystem;
namespace detail {
/// Keeps state for the recursive_directory_iterator.
struct RecDirIterState {
std::stack<directory_iterator, std::vector<directory_iterator>> Stack;
bool HasNoPushRequest = false;
};
} // end namespace detail
/// An input iterator over the recursive contents of a virtual path,
/// similar to llvm::sys::fs::recursive_directory_iterator.
class recursive_directory_iterator {
using IterState =
std::stack<directory_iterator, std::vector<directory_iterator>>;
FileSystem *FS;
std::shared_ptr<IterState> State; // Input iterator semantics on copy.
std::shared_ptr<detail::RecDirIterState>
State; // Input iterator semantics on copy.
public:
recursive_directory_iterator(FileSystem &FS, const Twine &Path,
@ -212,8 +220,8 @@ public:
/// Equivalent to operator++, with an error code.
recursive_directory_iterator &increment(std::error_code &EC);
const directory_entry &operator*() const { return *State->top(); }
const directory_entry *operator->() const { return &*State->top(); }
const directory_entry &operator*() const { return *State->Stack.top(); }
const directory_entry *operator->() const { return &*State->Stack.top(); }
bool operator==(const recursive_directory_iterator &Other) const {
return State == Other.State; // identity
@ -224,9 +232,12 @@ public:
/// Gets the current level. Starting path is at level 0.
int level() const {
assert(!State->empty() && "Cannot get level without any iteration state");
return State->size() - 1;
assert(!State->Stack.empty() &&
"Cannot get level without any iteration state");
return State->Stack.size() - 1;
}
void no_push() { State->HasNoPushRequest = true; }
};
/// The virtual file system interface.

View File

@ -2157,28 +2157,33 @@ vfs::recursive_directory_iterator::recursive_directory_iterator(
: FS(&FS_) {
directory_iterator I = FS->dir_begin(Path, EC);
if (I != directory_iterator()) {
State = std::make_shared<IterState>();
State->push(I);
State = std::make_shared<detail::RecDirIterState>();
State->Stack.push(I);
}
}
vfs::recursive_directory_iterator &
recursive_directory_iterator::increment(std::error_code &EC) {
assert(FS && State && !State->empty() && "incrementing past end");
assert(!State->top()->path().empty() && "non-canonical end iterator");
assert(FS && State && !State->Stack.empty() && "incrementing past end");
assert(!State->Stack.top()->path().empty() && "non-canonical end iterator");
vfs::directory_iterator End;
if (State->top()->type() == sys::fs::file_type::directory_file) {
vfs::directory_iterator I = FS->dir_begin(State->top()->path(), EC);
if (I != End) {
State->push(I);
return *this;
if (State->HasNoPushRequest)
State->HasNoPushRequest = false;
else {
if (State->Stack.top()->type() == sys::fs::file_type::directory_file) {
vfs::directory_iterator I = FS->dir_begin(State->Stack.top()->path(), EC);
if (I != End) {
State->Stack.push(I);
return *this;
}
}
}
while (!State->empty() && State->top().increment(EC) == End)
State->pop();
while (!State->Stack.empty() && State->Stack.top().increment(EC) == End)
State->Stack.pop();
if (State->empty())
if (State->Stack.empty())
State.reset(); // end iterator
return *this;

View File

@ -478,6 +478,85 @@ TEST(VirtualFileSystemTest, BasicRealFSRecursiveIteration) {
EXPECT_EQ(1, Counts[3]); // d
}
TEST(VirtualFileSystemTest, BasicRealFSRecursiveIterationNoPush) {
ScopedDir TestDirectory("virtual-file-system-test", /*Unique*/ true);
ScopedDir _a(TestDirectory + "/a");
ScopedDir _ab(TestDirectory + "/a/b");
ScopedDir _c(TestDirectory + "/c");
ScopedDir _cd(TestDirectory + "/c/d");
ScopedDir _e(TestDirectory + "/e");
ScopedDir _ef(TestDirectory + "/e/f");
ScopedDir _g(TestDirectory + "/g");
IntrusiveRefCntPtr<vfs::FileSystem> FS = vfs::getRealFileSystem();
// Test that calling no_push on entries without subdirectories has no effect.
{
std::error_code EC;
auto I = vfs::recursive_directory_iterator(*FS, Twine(TestDirectory), EC);
ASSERT_FALSE(EC);
std::vector<std::string> Contents;
for (auto E = vfs::recursive_directory_iterator(); !EC && I != E;
I.increment(EC)) {
Contents.push_back(I->path());
char last = I->path().back();
switch (last) {
case 'b':
case 'd':
case 'f':
case 'g':
I.no_push();
break;
default:
break;
}
}
EXPECT_EQ(7U, Contents.size());
}
// Test that calling no_push skips subdirectories.
{
std::error_code EC;
auto I = vfs::recursive_directory_iterator(*FS, Twine(TestDirectory), EC);
ASSERT_FALSE(EC);
std::vector<std::string> Contents;
for (auto E = vfs::recursive_directory_iterator(); !EC && I != E;
I.increment(EC)) {
Contents.push_back(I->path());
char last = I->path().back();
switch (last) {
case 'a':
case 'c':
case 'e':
I.no_push();
break;
default:
break;
}
}
// Check contents, which may be in any order
EXPECT_EQ(4U, Contents.size());
int Counts[7] = {0, 0, 0, 0, 0, 0, 0};
for (const std::string &Name : Contents) {
ASSERT_FALSE(Name.empty());
int Index = Name[Name.size() - 1] - 'a';
ASSERT_TRUE(Index >= 0 && Index < 7);
Counts[Index]++;
}
EXPECT_EQ(1, Counts[0]); // a
EXPECT_EQ(0, Counts[1]); // b
EXPECT_EQ(1, Counts[2]); // c
EXPECT_EQ(0, Counts[3]); // d
EXPECT_EQ(1, Counts[4]); // e
EXPECT_EQ(0, Counts[5]); // f
EXPECT_EQ(1, Counts[6]); // g
}
}
#ifdef LLVM_ON_UNIX
TEST(VirtualFileSystemTest, BrokenSymlinkRealFSRecursiveIteration) {
ScopedDir TestDirectory("virtual-file-system-test", /*Unique*/ true);