diff --git a/include/clang/Basic/FileManager.h b/include/clang/Basic/FileManager.h index 7e3b8650fe41213fea5c47b5b9dfc7429560ef1e..563157fa8fbebcc597030ae30da9eb2d31f4e3e9 100644 --- a/include/clang/Basic/FileManager.h +++ b/include/clang/Basic/FileManager.h @@ -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. + + /// 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 Filename); + const DirectoryEntry *getDirectory(llvm::StringRef DirName); - /// 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 *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. diff --git a/lib/Basic/FileManager.cpp b/lib/Basic/FileManager.cpp index cfb24a2fa253094aa197fef222d076f9cf7b64e9..df3ed2830229e2324aceda4e1ea97139daabbd4e 100644 --- a/lib/Basic/FileManager.cpp +++ b/lib/Basic/FileManager.cpp @@ -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); - - StatBuf.st_size = Size; - StatBuf.st_mtime = ModificationTime; - UFE = &UniqueFiles.getFile(InterndFileName, StatBuf); - - NamedFileEnt.setValue(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; + assert(DirInfo && + "The directory of a virtual file should already be in the cache."); + + // 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); + + StatBuf.st_size = Size; + StatBuf.st_mtime = ModificationTime; + UFE = &UniqueRealFiles.getFile(InterndFileName, StatBuf); + + NamedFileEnt.setValue(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; } - diff --git a/unittests/FileManager/FileManagerTest.cpp b/unittests/FileManager/FileManagerTest.cpp new file mode 100644 index 0000000000000000000000000000000000000000..a7d3df3125d7d5bd9a155046611442c6be86c715 --- /dev/null +++ b/unittests/FileManager/FileManagerTest.cpp @@ -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 diff --git a/unittests/FileManager/Makefile b/unittests/FileManager/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..3851aa4f59cdf3422fa9c7b8a3bed1ee06adf8a8 --- /dev/null +++ b/unittests/FileManager/Makefile @@ -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 diff --git a/unittests/Makefile b/unittests/Makefile index 685e397864a4ac54743006597b6ef19a4b863b5a..e8b4d55f3de4dcd3069f44e6e490a313bcac4288 100644 --- a/unittests/Makefile +++ b/unittests/Makefile @@ -14,7 +14,7 @@ ifndef CLANG_LEVEL IS_UNITTEST_LEVEL := 1 CLANG_LEVEL := .. -PARALLEL_DIRS = Frontend +PARALLEL_DIRS = FileManager Frontend endif # CLANG_LEVEL