// Copyright 2016 The Bazel Authors. All rights reserved. // // Licensed 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. #include #include #include #include "src/main/cpp/util/file.h" #include "src/main/cpp/util/file_platform.h" #include "gtest/gtest.h" namespace blaze_util { using std::pair; using std::string; using std::vector; static bool Symlink(const string& old_path, const string& new_path) { return symlink(old_path.c_str(), new_path.c_str()) == 0; } static bool CreateEmptyFile(const string& path) { // From the man page of open (man 2 open): // int open(const char *pathname, int flags, mode_t mode); // // mode specifies the permissions to use in case a new file is created. // This argument must be supplied when O_CREAT is specified in flags; // if O_CREAT is not specified, then mode is ignored. int fd = open(path.c_str(), O_CREAT | O_WRONLY, 0700); if (fd == -1) { return false; } return close(fd) == 0; } TEST(FileTest, TestDirname) { // The Posix version of SplitPath (thus Dirname too, which is implemented on // top of it) is not aware of Windows paths. ASSERT_EQ("", Dirname("")); ASSERT_EQ("/", Dirname("/")); ASSERT_EQ("", Dirname("foo")); ASSERT_EQ("/", Dirname("/foo")); ASSERT_EQ("/foo", Dirname("/foo/")); ASSERT_EQ("foo", Dirname("foo/bar")); ASSERT_EQ("foo/bar", Dirname("foo/bar/baz")); ASSERT_EQ("", Dirname("\\foo")); ASSERT_EQ("", Dirname("\\foo\\")); ASSERT_EQ("", Dirname("foo\\bar")); ASSERT_EQ("", Dirname("foo\\bar\\baz")); ASSERT_EQ("foo\\bar", Dirname("foo\\bar/baz\\qux")); ASSERT_EQ("c:", Dirname("c:/")); ASSERT_EQ("", Dirname("c:\\")); ASSERT_EQ("c:", Dirname("c:/foo")); ASSERT_EQ("", Dirname("c:\\foo")); ASSERT_EQ("", Dirname("\\\\?\\c:\\")); ASSERT_EQ("", Dirname("\\\\?\\c:\\foo")); } TEST(FileTest, TestBasename) { // The Posix version of SplitPath (thus Basename too, which is implemented on // top of it) is not aware of Windows paths. ASSERT_EQ("", Basename("")); ASSERT_EQ("", Basename("/")); ASSERT_EQ("foo", Basename("foo")); ASSERT_EQ("foo", Basename("/foo")); ASSERT_EQ("", Basename("/foo/")); ASSERT_EQ("bar", Basename("foo/bar")); ASSERT_EQ("baz", Basename("foo/bar/baz")); ASSERT_EQ("\\foo", Basename("\\foo")); ASSERT_EQ("\\foo\\", Basename("\\foo\\")); ASSERT_EQ("foo\\bar", Basename("foo\\bar")); ASSERT_EQ("foo\\bar\\baz", Basename("foo\\bar\\baz")); ASSERT_EQ("baz\\qux", Basename("foo\\bar/baz\\qux")); ASSERT_EQ("qux", Basename("qux")); ASSERT_EQ("", Basename("c:/")); ASSERT_EQ("c:\\", Basename("c:\\")); ASSERT_EQ("foo", Basename("c:/foo")); ASSERT_EQ("c:\\foo", Basename("c:\\foo")); ASSERT_EQ("\\\\?\\c:\\", Basename("\\\\?\\c:\\")); ASSERT_EQ("\\\\?\\c:\\foo", Basename("\\\\?\\c:\\foo")); } TEST(FileTest, JoinPath) { std::string path = JoinPath("", ""); ASSERT_EQ("", path); path = JoinPath("a", "b"); ASSERT_EQ("a/b", path); path = JoinPath("a/", "b"); ASSERT_EQ("a/b", path); path = JoinPath("a", "/b"); ASSERT_EQ("a/b", path); path = JoinPath("a/", "/b"); ASSERT_EQ("a/b", path); path = JoinPath("/", "/"); ASSERT_EQ("/", path); } void MockDirectoryListingFunction(const string& path, DirectoryEntryConsumer* consume) { if (path == "root") { consume->Consume("root/file1", false); consume->Consume("root/dir2", true); consume->Consume("root/dir1", true); } else if (path == "root/dir1") { consume->Consume("root/dir1/dir3", true); consume->Consume("root/dir1/file2", false); } else if (path == "root/dir2") { consume->Consume("root/dir2/file3", false); } else if (path == "root/dir1/dir3") { consume->Consume("root/dir1/dir3/file4", false); consume->Consume("root/dir1/dir3/file5", false); } else { // Unexpected path GTEST_FAIL(); } } TEST(FileTest, GetAllFilesUnder) { vector result; _GetAllFilesUnder("root", &result, &MockDirectoryListingFunction); std::sort(result.begin(), result.end()); vector expected({"root/dir1/dir3/file4", "root/dir1/dir3/file5", "root/dir1/file2", "root/dir2/file3", "root/file1"}); ASSERT_EQ(expected, result); } TEST(FileTest, MakeDirectories) { const char* tmp_dir = getenv("TEST_TMPDIR"); ASSERT_STRNE(tmp_dir, NULL); const char* test_src_dir = getenv("TEST_SRCDIR"); ASSERT_STRNE(NULL, test_src_dir); string dir = JoinPath(tmp_dir, "x/y/z"); bool ok = MakeDirectories(dir, 0755); ASSERT_TRUE(ok); // Changing permissions on an existing dir should work. ok = MakeDirectories(dir, 0750); ASSERT_TRUE(ok); struct stat filestat = {}; ASSERT_EQ(0, stat(dir.c_str(), &filestat)); ASSERT_EQ(0750, filestat.st_mode & 0777); // srcdir shouldn't be writable. // TODO(ulfjack): Fix this! // string srcdir = JoinPath(test_src_dir, "x/y/z"); // ok = MakeDirectories(srcdir, 0755); // ASSERT_FALSE(ok); // ASSERT_EQ(EACCES, errno); // Can't make a dir out of a file. string non_dir = JoinPath(dir, "w"); ASSERT_TRUE(CreateEmptyFile(non_dir)); ok = MakeDirectories(non_dir, 0755); ASSERT_FALSE(ok); ASSERT_EQ(ENOTDIR, errno); // Valid symlink should work. string symlink = JoinPath(tmp_dir, "z"); ASSERT_TRUE(Symlink(dir, symlink)); ok = MakeDirectories(symlink, 0755); ASSERT_TRUE(ok); // Error: Symlink to a file. symlink = JoinPath(tmp_dir, "w"); ASSERT_TRUE(Symlink(non_dir, symlink)); ok = MakeDirectories(symlink, 0755); ASSERT_FALSE(ok); ASSERT_EQ(ENOTDIR, errno); // Error: Symlink to a dir with wrong perms. symlink = JoinPath(tmp_dir, "s"); ASSERT_TRUE(Symlink("/", symlink)); // These perms will force a chmod() // TODO(ulfjack): Fix this! // ok = MakeDirectories(symlink, 0000); // ASSERTFALSE(ok); // ASSERT_EQ(EPERM, errno); // Edge cases. ASSERT_FALSE(MakeDirectories("", 0755)); ASSERT_EQ(EACCES, errno); ASSERT_FALSE(MakeDirectories("/", 0755)); ASSERT_EQ(EACCES, errno); } TEST(FileTest, HammerMakeDirectories) { const char* tmp_dir = getenv("TEST_TMPDIR"); ASSERT_STRNE(tmp_dir, NULL); string path = JoinPath(tmp_dir, "x/y/z"); // TODO(ulfjack): Fix this! // ASSERT_LE(0, fork()); // ASSERT_TRUE(MakeDirectories(path, 0755)); } TEST(FilePosixTest, Which) { ASSERT_EQ("", Which("")); ASSERT_EQ("", Which("foo")); ASSERT_EQ("", Which("/")); // /usr/bin/yes exists on Linux, Darwin, and MSYS, but "which yes" does not // always return that (if $PATH is different). string actual = Which("yes"); // Assert that it's an absolute path ASSERT_EQ(0, actual.find("/")); // Assert that it ends with /yes, we cannot assume more than that. ASSERT_EQ(actual.size() - string("/yes").size(), actual.rfind("/yes")); } TEST(FilePosixTest, PathExists) { ASSERT_FALSE(PathExists("/this/should/not/exist/mkay")); ASSERT_FALSE(PathExists("non.existent")); ASSERT_FALSE(PathExists("")); // /usr/bin/yes exists on Linux, Darwin, and MSYS ASSERT_TRUE(PathExists("/")); ASSERT_TRUE(PathExists("/usr")); ASSERT_TRUE(PathExists("/usr/")); ASSERT_TRUE(PathExists("/usr/bin/yes")); } TEST(FilePosixTest, CanAccess) { for (int i = 0; i < 8; ++i) { ASSERT_FALSE(CanAccess("/this/should/not/exist/mkay", i & 1, i & 2, i & 4)); ASSERT_FALSE(CanAccess("non.existent", i & 1, i & 2, i & 4)); } for (int i = 0; i < 4; ++i) { // /usr/bin/yes exists on Linux, Darwin, and MSYS ASSERT_TRUE(CanAccess("/", i & 1, false, i & 2)); ASSERT_TRUE(CanAccess("/usr", i & 1, false, i & 2)); ASSERT_TRUE(CanAccess("/usr/", i & 1, false, i & 2)); ASSERT_TRUE(CanAccess("/usr/bin/yes", i & 1, false, i & 2)); } char* tmpdir_cstr = getenv("TEST_TMPDIR"); ASSERT_FALSE(tmpdir_cstr == NULL); string tmpdir(tmpdir_cstr); ASSERT_NE("", tmpdir); string mock_file = tmpdir + (tmpdir.back() == '/' ? "" : "/") + "FilePosixTest.CanAccess.mock_file"; int fd = open(mock_file.c_str(), O_CREAT, 0500); ASSERT_GT(fd, 0); close(fd); // Sanity check: assert that we successfully created the file with the given // permissions. ASSERT_EQ(0, access(mock_file.c_str(), R_OK | X_OK)); ASSERT_NE(0, access(mock_file.c_str(), R_OK | W_OK | X_OK)); // Actual assertion for (int i = 0; i < 4; ++i) { ASSERT_TRUE(CanAccess(mock_file, i & 1, false, i & 2)); ASSERT_FALSE(CanAccess(mock_file, i & 1, true, i & 2)); } } TEST(FilePosixTest, GetCwd) { char cwdbuf[PATH_MAX]; ASSERT_EQ(cwdbuf, getcwd(cwdbuf, PATH_MAX)); // Assert that GetCwd() and getcwd() return the same value. string cwd(cwdbuf); ASSERT_EQ(cwd, blaze_util::GetCwd()); // Change to a different directory. ASSERT_EQ(0, chdir("/usr")); // Assert that GetCwd() returns the new CWD. ASSERT_EQ(string("/usr"), blaze_util::GetCwd()); ASSERT_EQ(0, chdir(cwd.c_str())); ASSERT_EQ(cwd, blaze_util::GetCwd()); } TEST(FilePosixTest, ChangeDirectory) { // Retrieve the current working directory. char old_wd[PATH_MAX]; ASSERT_EQ(old_wd, getcwd(old_wd, PATH_MAX)); // Change to a different directory and assert it was successful. ASSERT_FALSE(blaze_util::ChangeDirectory("/non/existent/path")); ASSERT_TRUE(blaze_util::ChangeDirectory("/usr")); char new_wd[PATH_MAX]; ASSERT_EQ(new_wd, getcwd(new_wd, PATH_MAX)); ASSERT_EQ(string("/usr"), string(new_wd)); // Change back to the original CWD. ASSERT_TRUE(blaze_util::ChangeDirectory(old_wd)); ASSERT_EQ(new_wd, getcwd(new_wd, PATH_MAX)); ASSERT_EQ(string(old_wd), string(new_wd)); } class MockDirectoryEntryConsumer : public DirectoryEntryConsumer { public: void Consume(const std::string &name, bool is_directory) override { entries.push_back(pair(name, is_directory)); } vector > entries; }; TEST(FilePosixTest, ForEachDirectoryEntry) { // Get the test's temp dir. char* tmpdir_cstr = getenv("TEST_TMPDIR"); ASSERT_FALSE(tmpdir_cstr == NULL); string tempdir(tmpdir_cstr); ASSERT_FALSE(tempdir.empty()); if (tempdir.back() == '/') { tempdir = tempdir.substr(0, tempdir.size() - 1); } // Create the root directory for the mock directory tree. string root = tempdir + "/FilePosixTest.ForEachDirectoryEntry.root"; ASSERT_EQ(0, mkdir(root.c_str(), 0700)); // Names of mock files and directories. string dir = root + "/dir"; string file = root + "/file"; string dir_sym = root + "/dir_sym"; string file_sym = root + "/file_sym"; string subfile = dir + "/subfile"; string subfile_through_sym = dir_sym + "/subfile"; // Create mock directory, file, and symlinks. int fd = open(file.c_str(), O_CREAT, 0700); ASSERT_GT(fd, 0); close(fd); ASSERT_EQ(0, mkdir(dir.c_str(), 0700)); ASSERT_EQ(0, symlink("dir", dir_sym.c_str())); ASSERT_EQ(0, symlink("file", file_sym.c_str())); fd = open(subfile.c_str(), O_CREAT, 0700); ASSERT_GT(fd, 0); close(fd); // Assert that stat'ing the symlinks (with following them) point to the right // filesystem entry types. struct stat stat_buf; ASSERT_EQ(0, stat(dir_sym.c_str(), &stat_buf)); ASSERT_TRUE(S_ISDIR(stat_buf.st_mode)); ASSERT_EQ(0, stat(file_sym.c_str(), &stat_buf)); ASSERT_FALSE(S_ISDIR(stat_buf.st_mode)); // Actual test: list the directory. MockDirectoryEntryConsumer consumer; ForEachDirectoryEntry(root, &consumer); ASSERT_EQ(4, consumer.entries.size()); // Sort the collected directory entries. struct { bool operator()(const pair &a, const pair &b) { return a.first < b.first; } } sort_pairs; std::sort(consumer.entries.begin(), consumer.entries.end(), sort_pairs); // Assert that the directory entries have the right name and type. pair expected; expected = pair(dir, true); ASSERT_EQ(expected, consumer.entries[0]); expected = pair(dir_sym, false); ASSERT_EQ(expected, consumer.entries[1]); expected = pair(file, false); ASSERT_EQ(expected, consumer.entries[2]); expected = pair(file_sym, false); ASSERT_EQ(expected, consumer.entries[3]); // Actual test: list a directory symlink. consumer.entries.clear(); ForEachDirectoryEntry(dir_sym, &consumer); ASSERT_EQ(1, consumer.entries.size()); expected = pair(subfile_through_sym, false); ASSERT_EQ(expected, consumer.entries[0]); // Actual test: list a path that's actually a file, not a directory. consumer.entries.clear(); ForEachDirectoryEntry(file, &consumer); ASSERT_TRUE(consumer.entries.empty()); // Cleanup: delete mock directory tree. rmdir(subfile.c_str()); rmdir(dir.c_str()); unlink(dir_sym.c_str()); unlink(file.c_str()); unlink(file_sym.c_str()); rmdir(root.c_str()); } } // namespace blaze_util