Improves Clang's virtual file handling.

This patch contains:

- making some of the existing comments more accurate in the presence
of virtual files/directories.

- renaming some private data members of FileManager to match their roles better.

- creating 'DirectorEntry's for the parent directories of virtual
files, such that we can tell whether two virtual files are from the
same directory.  This is useful for injecting virtual files whose
directories don't exist in the real file system.

- minor clean-ups and adding comments for class
FileManager::UniqueDirContainer and FileManager::UniqueFileContainer.

- adding statistics on virtual files to FileManager::PrintStats().

- adding unit tests to verify the existing and new behavior of FileManager.

llvm-svn: 125384
This commit is contained in:
Zhanyong Wan 2011-02-11 18:44:49 +00:00
parent 8479622152
commit e1dd3e2c31
5 changed files with 418 additions and 106 deletions

View File

@ -35,7 +35,8 @@ namespace clang {
class FileManager;
class FileSystemStatCache;
/// DirectoryEntry - Cached information about one directory on the disk.
/// DirectoryEntry - Cached information about one directory (either on
/// the disk or in the virtual file system).
///
class DirectoryEntry {
const char *Name; // Name of the directory.
@ -45,9 +46,9 @@ public:
const char *getName() const { return Name; }
};
/// FileEntry - Cached information about one file on the disk. If the 'FD'
/// member is valid, then this FileEntry has an open file descriptor for the
/// file.
/// FileEntry - Cached information about one file (either on the disk
/// or in the virtual file system). If the 'FD' member is valid, then
/// this FileEntry has an open file descriptor for the file.
///
class FileEntry {
const char *Name; // Name of the file.
@ -106,28 +107,36 @@ public:
///
class FileManager {
FileSystemOptions FileSystemOpts;
class UniqueDirContainer;
class UniqueFileContainer;
/// UniqueDirs/UniqueFiles - Cache for existing directories/files.
/// UniqueRealDirs/UniqueRealFiles - Cache for existing real directories/files.
///
UniqueDirContainer &UniqueDirs;
UniqueFileContainer &UniqueFiles;
UniqueDirContainer &UniqueRealDirs;
UniqueFileContainer &UniqueRealFiles;
/// DirEntries/FileEntries - This is a cache of directory/file entries we have
/// looked up. The actual Entry is owned by UniqueFiles/UniqueDirs above.
/// \brief The virtual directories that we have allocated. For each
/// virtual file (e.g. foo/bar/baz.cpp), we add all of its parent
/// directories (foo/ and foo/bar/) here.
llvm::SmallVector<DirectoryEntry*, 4> VirtualDirectoryEntries;
/// \brief The virtual files that we have allocated.
llvm::SmallVector<FileEntry*, 4> VirtualFileEntries;
/// SeenDirEntries/SeenFileEntries - This is a cache that maps paths
/// to directory/file entries (either real or virtual) we have
/// looked up. The actual Entries for real directories/files are
/// owned by UniqueRealDirs/UniqueRealFiles above, while the Entries
/// for virtual directories/files are owned by
/// VirtualDirectoryEntries/VirtualFileEntries above.
///
llvm::StringMap<DirectoryEntry*, llvm::BumpPtrAllocator> DirEntries;
llvm::StringMap<FileEntry*, llvm::BumpPtrAllocator> FileEntries;
llvm::StringMap<DirectoryEntry*, llvm::BumpPtrAllocator> SeenDirEntries;
llvm::StringMap<FileEntry*, llvm::BumpPtrAllocator> SeenFileEntries;
/// NextFileUID - Each FileEntry we create is assigned a unique ID #.
///
unsigned NextFileUID;
/// \brief The virtual files that we have allocated.
llvm::SmallVector<FileEntry*, 4> VirtualFileEntries;
// Statistics.
unsigned NumDirLookups, NumFileLookups;
unsigned NumDirCacheMisses, NumFileCacheMisses;
@ -137,12 +146,17 @@ class FileManager {
bool getStatValue(const char *Path, struct stat &StatBuf,
int *FileDescriptor);
/// Add all ancestors of the given path (pointing to either a file
/// or a directory) as virtual directories.
void addAncestorsAsVirtualDirs(llvm::StringRef Path);
public:
FileManager(const FileSystemOptions &FileSystemOpts);
~FileManager();
/// \brief Installs the provided FileSystemStatCache object within
/// the FileManager.
/// the FileManager.
///
/// Ownership of this object is transferred to the FileManager.
///
@ -156,14 +170,14 @@ public:
/// \brief Removes the specified FileSystemStatCache object from the manager.
void removeStatCache(FileSystemStatCache *statCache);
/// getDirectory - Lookup, cache, and verify the specified directory. This
/// returns null if the directory doesn't exist.
///
const DirectoryEntry *getDirectory(llvm::StringRef Filename);
/// getFile - Lookup, cache, and verify the specified file. This returns null
/// if the file doesn't exist.
/// getDirectory - Lookup, cache, and verify the specified directory
/// (real or virtual). This returns NULL if the directory doesn't exist.
///
const DirectoryEntry *getDirectory(llvm::StringRef DirName);
/// getFile - Lookup, cache, and verify the specified file (real or
/// virtual). This returns NULL if the file doesn't exist.
///
const FileEntry *getFile(llvm::StringRef Filename);
@ -185,7 +199,7 @@ public:
/// working directory.
static void FixupRelativePath(llvm::sys::Path &path,
const FileSystemOptions &FSOpts);
/// \brief Produce an array mapping from the unique IDs assigned to each
/// file to the corresponding FileEntry pointer.

View File

@ -84,7 +84,11 @@ class FileManager::UniqueDirContainer {
llvm::StringMap<DirectoryEntry> UniqueDirs;
public:
DirectoryEntry &getDirectory(const char *Name, struct stat &StatBuf) {
/// getDirectory - Return an existing DirectoryEntry with the given
/// name if there is already one; otherwise create and return a
/// default-constructed DirectoryEntry.
DirectoryEntry &getDirectory(const char *Name,
const struct stat & /*StatBuf*/) {
std::string FullPath(GetFullPath(Name));
return UniqueDirs.GetOrCreateValue(FullPath).getValue();
}
@ -98,9 +102,12 @@ class FileManager::UniqueFileContainer {
llvm::StringMap<FileEntry, llvm::BumpPtrAllocator> UniqueFiles;
public:
FileEntry &getFile(const char *Name, struct stat &StatBuf) {
/// getFile - Return an existing FileEntry with the given name if
/// there is already one; otherwise create and return a
/// default-constructed FileEntry.
FileEntry &getFile(const char *Name, const struct stat & /*StatBuf*/) {
std::string FullPath(GetFullPath(Name));
// LowercaseString because Windows filesystem is case insensitive.
FullPath = llvm::LowercaseString(FullPath);
return UniqueFiles.GetOrCreateValue(FullPath).getValue();
@ -122,7 +129,11 @@ class FileManager::UniqueDirContainer {
std::map<std::pair<dev_t, ino_t>, DirectoryEntry> UniqueDirs;
public:
DirectoryEntry &getDirectory(const char *Name, struct stat &StatBuf) {
/// getDirectory - Return an existing DirectoryEntry with the given
/// ID's if there is already one; otherwise create and return a
/// default-constructed DirectoryEntry.
DirectoryEntry &getDirectory(const char * /*Name*/,
const struct stat &StatBuf) {
return UniqueDirs[std::make_pair(StatBuf.st_dev, StatBuf.st_ino)];
}
@ -134,7 +145,10 @@ class FileManager::UniqueFileContainer {
std::set<FileEntry> UniqueFiles;
public:
FileEntry &getFile(const char *Name, struct stat &StatBuf) {
/// getFile - Return an existing FileEntry with the given ID's if
/// there is already one; otherwise create and return a
/// default-constructed FileEntry.
FileEntry &getFile(const char * /*Name*/, const struct stat &StatBuf) {
return
const_cast<FileEntry&>(
*UniqueFiles.insert(FileEntry(StatBuf.st_dev,
@ -153,18 +167,20 @@ public:
FileManager::FileManager(const FileSystemOptions &FSO)
: FileSystemOpts(FSO),
UniqueDirs(*new UniqueDirContainer()),
UniqueFiles(*new UniqueFileContainer()),
DirEntries(64), FileEntries(64), NextFileUID(0) {
UniqueRealDirs(*new UniqueDirContainer()),
UniqueRealFiles(*new UniqueFileContainer()),
SeenDirEntries(64), SeenFileEntries(64), NextFileUID(0) {
NumDirLookups = NumFileLookups = 0;
NumDirCacheMisses = NumFileCacheMisses = 0;
}
FileManager::~FileManager() {
delete &UniqueDirs;
delete &UniqueFiles;
delete &UniqueRealDirs;
delete &UniqueRealFiles;
for (unsigned i = 0, e = VirtualFileEntries.size(); i != e; ++i)
delete VirtualFileEntries[i];
for (unsigned i = 0, e = VirtualDirectoryEntries.size(); i != e; ++i)
delete VirtualDirectoryEntries[i];
}
void FileManager::addStatCache(FileSystemStatCache *statCache,
@ -203,12 +219,16 @@ void FileManager::removeStatCache(FileSystemStatCache *statCache) {
}
/// \brief Retrieve the directory that the given file name resides in.
/// Filename can point to either a real file or a virtual file.
static const DirectoryEntry *getDirectoryFromFile(FileManager &FileMgr,
llvm::StringRef Filename) {
// Figure out what directory it is in. If the string contains a / in it,
// strip off everything after it.
// FIXME: this logic should be in sys::Path.
size_t SlashPos = Filename.size();
if (SlashPos == 0 || IS_DIR_SEPARATOR_CHAR(Filename[SlashPos-1]))
return NULL; // If Filename is empty or a directory.
while (SlashPos != 0 && !IS_DIR_SEPARATOR_CHAR(Filename[SlashPos-1]))
--SlashPos;
@ -216,9 +236,6 @@ static const DirectoryEntry *getDirectoryFromFile(FileManager &FileMgr,
if (SlashPos == 0)
return FileMgr.getDirectory(".");
if (SlashPos == Filename.size()-1)
return 0; // If filename ends with a /, it's a directory.
// Ignore repeated //'s.
while (SlashPos != 0 && IS_DIR_SEPARATOR_CHAR(Filename[SlashPos-1]))
--SlashPos;
@ -226,19 +243,58 @@ static const DirectoryEntry *getDirectoryFromFile(FileManager &FileMgr,
return FileMgr.getDirectory(Filename.substr(0, SlashPos));
}
/// getDirectory - Lookup, cache, and verify the specified directory. This
/// returns null if the directory doesn't exist.
/// Add all ancestors of the given path (pointing to either a file or
/// a directory) as virtual directories.
void FileManager::addAncestorsAsVirtualDirs(llvm::StringRef Path) {
size_t SlashPos = Path.size();
// Find the beginning of the last segment in Path.
while (SlashPos != 0 && !IS_DIR_SEPARATOR_CHAR(Path[SlashPos-1]))
--SlashPos;
// Ignore repeated //'s.
while (SlashPos != 0 && IS_DIR_SEPARATOR_CHAR(Path[SlashPos-1]))
--SlashPos;
if (SlashPos == 0)
return;
llvm::StringRef DirName = Path.substr(0, SlashPos);
llvm::StringMapEntry<DirectoryEntry *> &NamedDirEnt =
SeenDirEntries.GetOrCreateValue(DirName);
// When caching a virtual directory, we always cache its ancestors
// at the same time. Therefore, if DirName is already in the cache,
// we don't need to recurse as its ancestors must also already be in
// the cache.
if (NamedDirEnt.getValue())
return;
// Add the virtual directory to the cache.
DirectoryEntry *UDE = new DirectoryEntry;
UDE->Name = NamedDirEnt.getKeyData();
NamedDirEnt.setValue(UDE);
VirtualDirectoryEntries.push_back(UDE);
// Recursively add the other ancestors.
addAncestorsAsVirtualDirs(DirName);
}
/// getDirectory - Lookup, cache, and verify the specified directory
/// (real or virtual). This returns NULL if the directory doesn't
/// exist.
///
const DirectoryEntry *FileManager::getDirectory(llvm::StringRef Filename) {
const DirectoryEntry *FileManager::getDirectory(llvm::StringRef DirName) {
// stat doesn't like trailing separators (at least on Windows).
if (Filename.size() > 1 && IS_DIR_SEPARATOR_CHAR(Filename.back()))
Filename = Filename.substr(0, Filename.size()-1);
if (DirName.size() > 1 && IS_DIR_SEPARATOR_CHAR(DirName.back()))
DirName = DirName.substr(0, DirName.size()-1);
++NumDirLookups;
llvm::StringMapEntry<DirectoryEntry *> &NamedDirEnt =
DirEntries.GetOrCreateValue(Filename);
SeenDirEntries.GetOrCreateValue(DirName);
// See if there is already an entry in the map.
// See if there was already an entry in the map. Note that the map
// contains both virtual and real directories.
if (NamedDirEnt.getValue())
return NamedDirEnt.getValue() == NON_EXISTENT_DIR
? 0 : NamedDirEnt.getValue();
@ -249,37 +305,41 @@ const DirectoryEntry *FileManager::getDirectory(llvm::StringRef Filename) {
NamedDirEnt.setValue(NON_EXISTENT_DIR);
// Get the null-terminated directory name as stored as the key of the
// DirEntries map.
// SeenDirEntries map.
const char *InterndDirName = NamedDirEnt.getKeyData();
// Check to see if the directory exists.
struct stat StatBuf;
if (getStatValue(InterndDirName, StatBuf, 0/*directory lookup*/))
if (getStatValue(InterndDirName, StatBuf, 0/*directory lookup*/)) {
// There's no real directory at the given path.
return 0;
}
// It exists. See if we have already opened a directory with the same inode.
// This occurs when one dir is symlinked to another, for example.
DirectoryEntry &UDE = UniqueDirs.getDirectory(InterndDirName, StatBuf);
// It exists. See if we have already opened a directory with the
// same inode (this occurs on Unix-like systems when one dir is
// symlinked to another, for example) or the same path (on
// Windows).
DirectoryEntry &UDE = UniqueRealDirs.getDirectory(InterndDirName, StatBuf);
NamedDirEnt.setValue(&UDE);
if (UDE.getName()) // Already have an entry with this inode, return it.
return &UDE;
if (!UDE.getName()) {
// We don't have this directory yet, add it. We use the string
// key from the SeenDirEntries map as the string.
UDE.Name = InterndDirName;
}
// Otherwise, we don't have this directory yet, add it. We use the string
// key from the DirEntries map as the string.
UDE.Name = InterndDirName;
return &UDE;
}
/// getFile - Lookup, cache, and verify the specified file. This returns null
/// if the file doesn't exist.
/// getFile - Lookup, cache, and verify the specified file (real or
/// virtual). This returns NULL if the file doesn't exist.
///
const FileEntry *FileManager::getFile(llvm::StringRef Filename) {
++NumFileLookups;
// See if there is already an entry in the map.
llvm::StringMapEntry<FileEntry *> &NamedFileEnt =
FileEntries.GetOrCreateValue(Filename);
SeenFileEntries.GetOrCreateValue(Filename);
// See if there is already an entry in the map.
if (NamedFileEnt.getValue())
@ -291,12 +351,10 @@ const FileEntry *FileManager::getFile(llvm::StringRef Filename) {
// By default, initialize it to invalid.
NamedFileEnt.setValue(NON_EXISTENT_FILE);
// Get the null-terminated file name as stored as the key of the
// FileEntries map.
// SeenFileEntries map.
const char *InterndFileName = NamedFileEnt.getKeyData();
// Look up the directory for the file. When looking up something like
// sys/foo.h we'll discover all of the search directories that have a 'sys'
// subdirectory. This will let us avoid having to waste time on known-to-fail
@ -312,25 +370,27 @@ const FileEntry *FileManager::getFile(llvm::StringRef Filename) {
// Nope, there isn't. Check to see if the file exists.
int FileDescriptor = -1;
struct stat StatBuf;
if (getStatValue(InterndFileName, StatBuf, &FileDescriptor))
if (getStatValue(InterndFileName, StatBuf, &FileDescriptor)) {
// There's no real file at the given path.
return 0;
}
// It exists. See if we have already opened a file with the same inode.
// This occurs when one dir is symlinked to another, for example.
FileEntry &UFE = UniqueFiles.getFile(InterndFileName, StatBuf);
FileEntry &UFE = UniqueRealFiles.getFile(InterndFileName, StatBuf);
NamedFileEnt.setValue(&UFE);
if (UFE.getName()) { // Already have an entry with this inode, return it.
// If the stat process opened the file, close it to avoid a FD leak.
if (FileDescriptor != -1)
close(FileDescriptor);
return &UFE;
}
// Otherwise, we don't have this directory yet, add it.
// FIXME: Change the name to be a char* that points back to the 'FileEntries'
// key.
// FIXME: Change the name to be a char* that points back to the
// 'SeenFileEntries' key.
UFE.Name = InterndFileName;
UFE.Size = StatBuf.st_size;
UFE.ModTime = StatBuf.st_mtime;
@ -347,7 +407,7 @@ FileManager::getVirtualFile(llvm::StringRef Filename, off_t Size,
// See if there is already an entry in the map.
llvm::StringMapEntry<FileEntry *> &NamedFileEnt =
FileEntries.GetOrCreateValue(Filename);
SeenFileEntries.GetOrCreateValue(Filename);
// See if there is already an entry in the map.
if (NamedFileEnt.getValue() && NamedFileEnt.getValue() != NON_EXISTENT_FILE)
@ -358,37 +418,42 @@ FileManager::getVirtualFile(llvm::StringRef Filename, off_t Size,
// By default, initialize it to invalid.
NamedFileEnt.setValue(NON_EXISTENT_FILE);
// We allow the directory to not exist. If it does exist we store it.
addAncestorsAsVirtualDirs(Filename);
FileEntry *UFE = 0;
// Now that all ancestors of Filename are in the cache, the
// following call is guaranteed to find the DirectoryEntry from the
// cache.
const DirectoryEntry *DirInfo = getDirectoryFromFile(*this, Filename);
if (DirInfo) {
// Check to see if the file exists. If so, drop the virtual file
int FileDescriptor = -1;
struct stat StatBuf;
const char *InterndFileName = NamedFileEnt.getKeyData();
if (getStatValue(InterndFileName, StatBuf, &FileDescriptor) == 0) {
// If the stat process opened the file, close it to avoid a FD leak.
if (FileDescriptor != -1)
close(FileDescriptor);
assert(DirInfo &&
"The directory of a virtual file should already be in the cache.");
StatBuf.st_size = Size;
StatBuf.st_mtime = ModificationTime;
UFE = &UniqueFiles.getFile(InterndFileName, StatBuf);
// Check to see if the file exists. If so, drop the virtual file
int FileDescriptor = -1;
struct stat StatBuf;
const char *InterndFileName = NamedFileEnt.getKeyData();
if (getStatValue(InterndFileName, StatBuf, &FileDescriptor) == 0) {
// If the stat process opened the file, close it to avoid a FD leak.
if (FileDescriptor != -1)
close(FileDescriptor);
NamedFileEnt.setValue(UFE);
StatBuf.st_size = Size;
StatBuf.st_mtime = ModificationTime;
UFE = &UniqueRealFiles.getFile(InterndFileName, StatBuf);
// If we had already opened this file, close it now so we don't
// leak the descriptor. We're not going to use the file
// descriptor anyway, since this is a virtual file.
if (UFE->FD != -1) {
close(UFE->FD);
UFE->FD = -1;
}
NamedFileEnt.setValue(UFE);
// If we already have an entry with this inode, return it.
if (UFE->getName())
return UFE;
// If we had already opened this file, close it now so we don't
// leak the descriptor. We're not going to use the file
// descriptor anyway, since this is a virtual file.
if (UFE->FD != -1) {
close(UFE->FD);
UFE->FD = -1;
}
// If we already have an entry with this inode, return it.
if (UFE->getName())
return UFE;
}
if (!UFE) {
@ -397,10 +462,6 @@ FileManager::getVirtualFile(llvm::StringRef Filename, off_t Size,
NamedFileEnt.setValue(UFE);
}
// Get the null-terminated file name as stored as the key of the
// FileEntries map.
const char *InterndFileName = NamedFileEnt.getKeyData();
UFE->Name = InterndFileName;
UFE->Size = Size;
UFE->ModTime = ModificationTime;
@ -472,12 +533,11 @@ getBufferForFile(llvm::StringRef Filename, std::string *ErrorStr) {
return Result.take();
}
/// getStatValue - Get the 'stat' information for the specified path, using the
/// cache to accelerate it if possible. This returns true if the path does not
/// exist or false if it exists.
///
/// The isForDir member indicates whether this is a directory lookup or not.
/// This will return failure if the lookup isn't the expected kind.
/// getStatValue - Get the 'stat' information for the specified path,
/// using the cache to accelerate it if possible. This returns true
/// if the path points to a virtual file or does not exist, or returns
/// false if it's an existent real file. If FileDescriptor is NULL,
/// do directory look-up instead of file look-up.
bool FileManager::getStatValue(const char *Path, struct stat &StatBuf,
int *FileDescriptor) {
// FIXME: FileSystemOpts shouldn't be passed in here, all paths should be
@ -485,7 +545,7 @@ bool FileManager::getStatValue(const char *Path, struct stat &StatBuf,
if (FileSystemOpts.WorkingDir.empty())
return FileSystemStatCache::get(Path, StatBuf, FileDescriptor,
StatCache.get());
llvm::sys::Path FilePath(Path);
FixupRelativePath(FilePath, FileSystemOpts);
@ -500,7 +560,7 @@ void FileManager::GetUniqueIDMapping(
// Map file entries
for (llvm::StringMap<FileEntry*, llvm::BumpPtrAllocator>::const_iterator
FE = FileEntries.begin(), FEEnd = FileEntries.end();
FE = SeenFileEntries.begin(), FEEnd = SeenFileEntries.end();
FE != FEEnd; ++FE)
if (FE->getValue() && FE->getValue() != NON_EXISTENT_FILE)
UIDToFiles[FE->getValue()->getUID()] = FE->getValue();
@ -516,8 +576,10 @@ void FileManager::GetUniqueIDMapping(
void FileManager::PrintStats() const {
llvm::errs() << "\n*** File Manager Stats:\n";
llvm::errs() << UniqueFiles.size() << " files found, "
<< UniqueDirs.size() << " dirs found.\n";
llvm::errs() << UniqueRealFiles.size() << " real files found, "
<< UniqueRealDirs.size() << " real dirs found.\n";
llvm::errs() << VirtualFileEntries.size() << " virtual files found, "
<< VirtualDirectoryEntries.size() << " virtual dirs found.\n";
llvm::errs() << NumDirLookups << " dir lookups, "
<< NumDirCacheMisses << " dir cache misses.\n";
llvm::errs() << NumFileLookups << " file lookups, "
@ -525,4 +587,3 @@ void FileManager::PrintStats() const {
//llvm::errs() << PagesMapped << BytesOfPagesMapped << FSLookups;
}

View File

@ -0,0 +1,222 @@
//===- unittests/FileManager/FileMangerTest.cpp ------ FileManger tests ---===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#include "clang/Basic/FileSystemOptions.h"
#include "clang/Basic/FileSystemStatCache.h"
#include "clang/Basic/FileManager.h"
#include "gtest/gtest.h"
using namespace llvm;
using namespace clang;
namespace {
// Used to create a fake file system for running the tests with such
// that the tests are not affected by the structure/contents of the
// file system on the machine running the tests.
class FakeStatCache : public FileSystemStatCache {
private:
// Maps a file/directory path to its desired stat result. Anything
// not in this map is considered to not exist in the file system.
llvm::StringMap<struct stat, llvm::BumpPtrAllocator> StatCalls;
void InjectFileOrDirectory(const char *Path, ino_t INode, bool IsFile) {
struct stat statBuf = {};
statBuf.st_dev = 1;
#ifndef LLVM_ON_WIN32 // struct stat has no st_ino field on Windows.
statBuf.st_ino = INode;
#endif
statBuf.st_mode = IsFile ? (0777 | S_IFREG) // a regular file
: (0777 | S_IFDIR); // a directory
StatCalls[Path] = statBuf;
}
public:
// Inject a file with the given inode value to the fake file system.
void InjectFile(const char *Path, ino_t INode) {
InjectFileOrDirectory(Path, INode, /*IsFile=*/true);
}
// Inject a directory with the given inode value to the fake file system.
void InjectDirectory(const char *Path, ino_t INode) {
InjectFileOrDirectory(Path, INode, /*IsFile=*/false);
}
// Implement FileSystemStatCache::getStat().
virtual LookupResult getStat(const char *Path, struct stat &StatBuf,
int *FileDescriptor) {
if (StatCalls.count(Path) != 0) {
StatBuf = StatCalls[Path];
return CacheExists;
}
return CacheMissing; // This means the file/directory doesn't exist.
}
};
// The test fixture.
class FileManagerTest : public ::testing::Test {
protected:
FileManagerTest() : manager(options) {
}
FileSystemOptions options;
FileManager manager;
};
// When a virtual file is added, its getDir() field is set correctly
// (not NULL, correct name).
TEST_F(FileManagerTest, getVirtualFileSetsTheDirFieldCorrectly) {
const FileEntry *file = manager.getVirtualFile("foo.cpp", 42, 0);
ASSERT_TRUE(file != NULL);
const DirectoryEntry *dir = file->getDir();
ASSERT_TRUE(dir != NULL);
EXPECT_STREQ(".", dir->getName());
file = manager.getVirtualFile("x/y/z.cpp", 42, 0);
ASSERT_TRUE(file != NULL);
dir = file->getDir();
ASSERT_TRUE(dir != NULL);
EXPECT_STREQ("x/y", dir->getName());
}
// Before any virtual file is added, no virtual directory exists.
TEST_F(FileManagerTest, NoVirtualDirectoryExistsBeforeAVirtualFileIsAdded) {
// An empty FakeStatCache causes all stat calls made by the
// FileManager to report "file/directory doesn't exist". This
// avoids the possibility of the result of this test being affected
// by what's in the real file system.
manager.addStatCache(new FakeStatCache);
EXPECT_EQ(NULL, manager.getDirectory("virtual/dir/foo"));
EXPECT_EQ(NULL, manager.getDirectory("virtual/dir"));
EXPECT_EQ(NULL, manager.getDirectory("virtual"));
}
// When a virtual file is added, all of its ancestors should be created.
TEST_F(FileManagerTest, getVirtualFileCreatesDirectoryEntriesForAncestors) {
// Fake an empty real file system.
manager.addStatCache(new FakeStatCache);
manager.getVirtualFile("virtual/dir/bar.h", 100, 0);
EXPECT_EQ(NULL, manager.getDirectory("virtual/dir/foo"));
const DirectoryEntry *dir = manager.getDirectory("virtual/dir");
ASSERT_TRUE(dir != NULL);
EXPECT_STREQ("virtual/dir", dir->getName());
dir = manager.getDirectory("virtual");
ASSERT_TRUE(dir != NULL);
EXPECT_STREQ("virtual", dir->getName());
}
// getFile() returns non-NULL if a real file exists at the given path.
TEST_F(FileManagerTest, getFileReturnsValidFileEntryForExistingRealFile) {
// Inject fake files into the file system.
FakeStatCache *statCache = new FakeStatCache;
statCache->InjectDirectory("/tmp", 42);
statCache->InjectFile("/tmp/test", 43);
manager.addStatCache(statCache);
const FileEntry *file = manager.getFile("/tmp/test");
ASSERT_TRUE(file != NULL);
EXPECT_STREQ("/tmp/test", file->getName());
const DirectoryEntry *dir = file->getDir();
ASSERT_TRUE(dir != NULL);
EXPECT_STREQ("/tmp", dir->getName());
}
// getFile() returns non-NULL if a virtual file exists at the given path.
TEST_F(FileManagerTest, getFileReturnsValidFileEntryForExistingVirtualFile) {
// Fake an empty real file system.
manager.addStatCache(new FakeStatCache);
manager.getVirtualFile("virtual/dir/bar.h", 100, 0);
const FileEntry *file = manager.getFile("virtual/dir/bar.h");
ASSERT_TRUE(file != NULL);
EXPECT_STREQ("virtual/dir/bar.h", file->getName());
const DirectoryEntry *dir = file->getDir();
ASSERT_TRUE(dir != NULL);
EXPECT_STREQ("virtual/dir", dir->getName());
}
// getFile() returns different FileEntries for different paths when
// there's no aliasing.
TEST_F(FileManagerTest, getFileReturnsDifferentFileEntriesForDifferentFiles) {
// Inject two fake files into the file system. Different inodes
// mean the files are not symlinked together.
FakeStatCache *statCache = new FakeStatCache;
statCache->InjectDirectory(".", 41);
statCache->InjectFile("foo.cpp", 42);
statCache->InjectFile("bar.cpp", 43);
manager.addStatCache(statCache);
const FileEntry *fileFoo = manager.getFile("foo.cpp");
const FileEntry *fileBar = manager.getFile("bar.cpp");
ASSERT_TRUE(fileFoo != NULL);
ASSERT_TRUE(fileBar != NULL);
EXPECT_NE(fileFoo, fileBar);
}
// getFile() returns NULL if neither a real file nor a virtual file
// exists at the given path.
TEST_F(FileManagerTest, getFileReturnsNULLForNonexistentFile) {
// Inject a fake foo.cpp into the file system.
FakeStatCache *statCache = new FakeStatCache;
statCache->InjectDirectory(".", 41);
statCache->InjectFile("foo.cpp", 42);
manager.addStatCache(statCache);
// Create a virtual bar.cpp file.
manager.getVirtualFile("bar.cpp", 200, 0);
const FileEntry *file = manager.getFile("xyz.txt");
EXPECT_EQ(NULL, file);
}
// The following tests apply to Unix-like system only.
#ifndef LLVM_ON_WIN32
// getFile() returns the same FileEntry for real files that are aliases.
TEST_F(FileManagerTest, getFileReturnsSameFileEntryForAliasedRealFiles) {
// Inject two real files with the same inode.
FakeStatCache *statCache = new FakeStatCache;
statCache->InjectDirectory("abc", 41);
statCache->InjectFile("abc/foo.cpp", 42);
statCache->InjectFile("abc/bar.cpp", 42);
manager.addStatCache(statCache);
EXPECT_EQ(manager.getFile("abc/foo.cpp"), manager.getFile("abc/bar.cpp"));
}
// getFile() returns the same FileEntry for virtual files that have
// corresponding real files that are aliases.
TEST_F(FileManagerTest, getFileReturnsSameFileEntryForAliasedVirtualFiles) {
// Inject two real files with the same inode.
FakeStatCache *statCache = new FakeStatCache;
statCache->InjectDirectory("abc", 41);
statCache->InjectFile("abc/foo.cpp", 42);
statCache->InjectFile("abc/bar.cpp", 42);
manager.addStatCache(statCache);
manager.getVirtualFile("abc/foo.cpp", 100, 0);
manager.getVirtualFile("abc/bar.cpp", 200, 0);
EXPECT_EQ(manager.getFile("abc/foo.cpp"), manager.getFile("abc/bar.cpp"));
}
#endif // !LLVM_ON_WIN32
} // anonymous namespace

View File

@ -0,0 +1,15 @@
##===- unittests/FileManager/Makefile ----------------------*- Makefile -*-===##
#
# The LLVM Compiler Infrastructure
#
# This file is distributed under the University of Illinois Open Source
# License. See LICENSE.TXT for details.
#
##===----------------------------------------------------------------------===##
CLANG_LEVEL = ../..
TESTNAME = FileManager
LINK_COMPONENTS := core support mc
USEDLIBS = clangBasic.a
include $(CLANG_LEVEL)/unittests/Makefile

View File

@ -14,7 +14,7 @@ ifndef CLANG_LEVEL
IS_UNITTEST_LEVEL := 1
CLANG_LEVEL := ..
PARALLEL_DIRS = Frontend
PARALLEL_DIRS = FileManager Frontend
endif # CLANG_LEVEL