/* * Copyright (c) 2016-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. * */ #include "eden/fs/utils/PathFuncs.h" #include #include #include #include #include #include #include #include #include #include #include "eden/fs/testharness/TempFile.h" using facebook::eden::basename; using facebook::eden::dirname; using folly::StringPiece; using std::string; using std::vector; using namespace facebook::eden; using testing::ElementsAre; TEST(PathFuncs, StringCompare) { PathComponentPiece piece("foo"); EXPECT_EQ("foo", piece); EXPECT_EQ(piece, "foo"); } TEST(PathFuncs, Iterate) { RelativePath rel("foo/bar/baz"); std::vector parents( rel.paths().begin(), rel.paths().end()); EXPECT_EQ(3, parents.size()); EXPECT_EQ("foo"_relpath, parents.at(0)); EXPECT_EQ("foo/bar"_relpath, parents.at(1)); EXPECT_EQ("foo/bar/baz"_relpath, parents.at(2)); std::vector allPaths( rel.allPaths().begin(), rel.allPaths().end()); EXPECT_EQ(4, allPaths.size()); EXPECT_EQ(""_relpath, allPaths.at(0)); EXPECT_EQ("foo"_relpath, allPaths.at(1)); EXPECT_EQ("foo/bar"_relpath, allPaths.at(2)); EXPECT_EQ("foo/bar/baz"_relpath, allPaths.at(3)); // And in reverse. std::vector rparents( rel.rpaths().begin(), rel.rpaths().end()); EXPECT_EQ(3, rparents.size()); EXPECT_EQ("foo/bar/baz"_relpath, rparents.at(0)); EXPECT_EQ("foo/bar"_relpath, rparents.at(1)); EXPECT_EQ("foo"_relpath, rparents.at(2)); std::vector rallPaths( rel.rallPaths().begin(), rel.rallPaths().end()); EXPECT_EQ(4, rallPaths.size()); EXPECT_EQ("foo/bar/baz"_relpath, rallPaths.at(0)); EXPECT_EQ("foo/bar"_relpath, rallPaths.at(1)); EXPECT_EQ("foo"_relpath, rallPaths.at(2)); EXPECT_EQ(""_relpath, rallPaths.at(3)); // An empty relative path yields no elements. RelativePath emptyRel; std::vector emptyPaths( emptyRel.paths().begin(), emptyRel.paths().end()); EXPECT_EQ(0, emptyPaths.size()); std::vector allEmptyPaths( emptyRel.allPaths().begin(), emptyRel.allPaths().end()); EXPECT_EQ(1, allEmptyPaths.size()); EXPECT_EQ(""_relpath, allEmptyPaths.at(0)); // An empty relative path yields no elements in reverse either. std::vector remptyPaths( emptyRel.rpaths().begin(), emptyRel.rpaths().end()); EXPECT_EQ(0, remptyPaths.size()); std::vector rallEmptyPaths( emptyRel.rallPaths().begin(), emptyRel.rallPaths().end()); EXPECT_EQ(1, rallEmptyPaths.size()); EXPECT_EQ(""_relpath, rallEmptyPaths.at(0)); AbsolutePath absPath("/foo/bar/baz"); std::vector acomps( absPath.paths().begin(), absPath.paths().end()); EXPECT_EQ(4, acomps.size()); EXPECT_EQ("/"_abspath, acomps.at(0)); EXPECT_EQ("/foo"_abspath, acomps.at(1)); EXPECT_EQ("/foo/bar"_abspath, acomps.at(2)); EXPECT_EQ("/foo/bar/baz"_abspath, acomps.at(3)); std::vector racomps( absPath.rpaths().begin(), absPath.rpaths().end()); EXPECT_EQ(4, racomps.size()); EXPECT_EQ("/foo/bar/baz"_abspath, racomps.at(0)); EXPECT_EQ("/foo/bar"_abspath, racomps.at(1)); EXPECT_EQ("/foo"_abspath, racomps.at(2)); EXPECT_EQ("/"_abspath, racomps.at(3)); AbsolutePath slashAbs("/"); std::vector slashPieces( slashAbs.paths().begin(), slashAbs.paths().end()); EXPECT_EQ(1, slashPieces.size()); EXPECT_EQ("/"_abspath, slashPieces.at(0)); std::vector rslashPieces( slashAbs.rpaths().begin(), slashAbs.rpaths().end()); EXPECT_EQ(1, rslashPieces.size()); EXPECT_EQ("/"_abspath, rslashPieces.at(0)); } TEST(PathFuncs, IteratorDecrement) { auto checkDecrement = [](const auto& path, StringPiece function, const auto& range, const vector& expectedList) { SCOPED_TRACE(folly::to(path, ".", function, "()")); auto iter = range.end(); for (const auto& expectedPath : expectedList) { ASSERT_FALSE(iter == range.begin()); --iter; EXPECT_EQ(expectedPath, (*iter).stringPiece()); } EXPECT_TRUE(iter == range.begin()); }; RelativePath rel("foo/bar/baz"); vector expected = {"foo/bar/baz", "foo/bar", "foo"}; checkDecrement(rel, "paths", rel.paths(), expected); expected = vector{"foo/bar/baz", "foo/bar", "foo", ""}; checkDecrement(rel, "allPaths", rel.allPaths(), expected); expected = vector{"foo", "foo/bar", "foo/bar/baz"}; checkDecrement(rel, "rpaths", rel.rpaths(), expected); expected = vector{"", "foo", "foo/bar", "foo/bar/baz"}; checkDecrement(rel, "rallPaths", rel.rallPaths(), expected); AbsolutePath abs("/foo/bar/baz"); expected = vector{"/foo/bar/baz", "/foo/bar", "/foo", "/"}; checkDecrement(abs, "paths", abs.paths(), expected); expected = vector{"/", "/foo", "/foo/bar", "/foo/bar/baz"}; checkDecrement(abs, "rpaths", abs.rpaths(), expected); } TEST(PathFuncs, IterateComponents) { RelativePath rel("foo/bar/baz"); std::vector relParts( rel.components().begin(), rel.components().end()); EXPECT_THAT(relParts, ElementsAre("foo"_pc, "bar"_pc, "baz"_pc)); std::vector relRParts( rel.rcomponents().begin(), rel.rcomponents().end()); EXPECT_THAT(relRParts, ElementsAre("baz"_pc, "bar"_pc, "foo"_pc)); AbsolutePath abs("/foo/bar/baz"); std::vector absParts( abs.components().begin(), abs.components().end()); EXPECT_THAT(absParts, ElementsAre("foo"_pc, "bar"_pc, "baz"_pc)); std::vector absRParts( abs.rcomponents().begin(), abs.rcomponents().end()); EXPECT_THAT(absRParts, ElementsAre("baz"_pc, "bar"_pc, "foo"_pc)); RelativePath rel2("r/s/t/u"); std::vector rel2Parts( rel2.components().begin(), rel2.components().end()); EXPECT_THAT(rel2Parts, ElementsAre("r"_pc, "s"_pc, "t"_pc, "u"_pc)); std::vector rel2RParts( rel2.rcomponents().begin(), rel2.rcomponents().end()); EXPECT_THAT(rel2RParts, ElementsAre("u"_pc, "t"_pc, "s"_pc, "r"_pc)); AbsolutePath abs2("/a/b/c/d"); std::vector abs2Parts( abs2.components().begin(), abs2.components().end()); EXPECT_THAT(abs2Parts, ElementsAre("a"_pc, "b"_pc, "c"_pc, "d"_pc)); std::vector abs2RParts( abs2.rcomponents().begin(), abs2.rcomponents().end()); EXPECT_THAT(abs2RParts, ElementsAre("d"_pc, "c"_pc, "b"_pc, "a"_pc)); RelativePath empty; std::vector emptyParts( empty.components().begin(), empty.components().end()); EXPECT_THAT(emptyParts, ElementsAre()); std::vector emptyRParts( empty.rcomponents().begin(), empty.rcomponents().end()); EXPECT_THAT(emptyRParts, ElementsAre()); } TEST(PathFuncs, IterateSuffixes) { RelativePath rel("foo/bar/baz"); std::vector relParts( rel.suffixes().begin(), rel.suffixes().end()); EXPECT_THAT( relParts, ElementsAre("foo/bar/baz"_relpath, "bar/baz"_relpath, "baz"_relpath)); std::vector relRParts( rel.rsuffixes().begin(), rel.rsuffixes().end()); EXPECT_THAT( relRParts, ElementsAre("baz"_relpath, "bar/baz"_relpath, "foo/bar/baz"_relpath)); AbsolutePath abs("/foo/bar/baz"); std::vector absParts( abs.suffixes().begin(), abs.suffixes().end()); EXPECT_THAT( absParts, ElementsAre("foo/bar/baz"_relpath, "bar/baz"_relpath, "baz"_relpath)); std::vector absRParts( abs.rsuffixes().begin(), abs.rsuffixes().end()); EXPECT_THAT( absRParts, ElementsAre("baz"_relpath, "bar/baz"_relpath, "foo/bar/baz"_relpath)); RelativePath rel2("r/s/t/u"); std::vector rel2Parts( rel2.suffixes().begin(), rel2.suffixes().end()); EXPECT_THAT( rel2Parts, ElementsAre( "r/s/t/u"_relpath, "s/t/u"_relpath, "t/u"_relpath, "u"_relpath)); std::vector rel2RParts( rel2.rsuffixes().begin(), rel2.rsuffixes().end()); EXPECT_THAT( rel2RParts, ElementsAre( "u"_relpath, "t/u"_relpath, "s/t/u"_relpath, "r/s/t/u"_relpath)); AbsolutePath abs2("/a/b/c/d"); std::vector abs2Parts( abs2.suffixes().begin(), abs2.suffixes().end()); EXPECT_THAT( abs2Parts, ElementsAre( "a/b/c/d"_relpath, "b/c/d"_relpath, "c/d"_relpath, "d"_relpath)); std::vector abs2RParts( abs2.rsuffixes().begin(), abs2.rsuffixes().end()); EXPECT_THAT( abs2RParts, ElementsAre( "d"_relpath, "c/d"_relpath, "b/c/d"_relpath, "a/b/c/d"_relpath)); RelativePath empty; std::vector emptyParts( empty.suffixes().begin(), empty.suffixes().end()); EXPECT_THAT(emptyParts, ElementsAre()); std::vector emptyRParts( empty.rsuffixes().begin(), empty.rsuffixes().end()); EXPECT_THAT(emptyRParts, ElementsAre()); } TEST(PathFuncs, InitializeFromIter) { // Assert that we can build a vector of path components and convert // it to a RelativePath std::vector components = { PathComponent("a"), PathComponent("b"), PathComponent("c")}; // This form uses iterators explicitly RelativePath rel(components.begin(), components.end()); EXPECT_EQ("a/b/c", rel.stringPiece()); // This form constructs from the container directly (which uses the // iterator form under the covers) RelativePath rel2(components); EXPECT_EQ(rel, rel2); // And this form uses an initializer_list (which also uses the iterator // form under the covers). // Note that we're mixing both the Stored and Piece flavors of the // PathComponent in the initializer. RelativePath rel3{PathComponent("stored"), "notstored"_pc}; EXPECT_EQ("stored/notstored", rel3.stringPiece()); } TEST(PathFuncs, Hash) { // Assert that we can find the hash_value function in the correct // namespace for boost::hash. boost::hash hasher; EXPECT_EQ(9188533406165618471, hasher("foo"_pc)); // Similarly for std::hash std::set pset; std::set rset; std::set aset; std::unordered_set upset; std::unordered_set urset; std::unordered_set uaset; } TEST(PathFuncs, Stream) { // Assert that our stream operator functions. std::stringstream str; str << PathComponent("file"); EXPECT_EQ("file", str.str()); } TEST(PathFuncs, ImplicitPiece) { // Assert that we can implicitly convert from Stored -> Piece, // which is a pattern we desire for passing either Stored or Piece // to a method that accepts a Piece. PathComponent comp("stored"); [](PathComponentPiece piece) { EXPECT_EQ("stored", piece.stringPiece()); }(comp); } TEST(PathFuncs, PathComponent) { PathComponent comp("hello"); EXPECT_EQ("hello", comp.stringPiece()); PathComponentPiece compPiece("helloPiece"); EXPECT_EQ("helloPiece", compPiece.stringPiece()); PathComponent storedFromStored(comp); EXPECT_EQ(comp, storedFromStored); PathComponent storedFromPiece(compPiece); EXPECT_EQ(compPiece, storedFromPiece); PathComponentPiece pieceFromStored(comp); EXPECT_EQ(comp, pieceFromStored); PathComponentPiece pieceFromPiece(compPiece); EXPECT_EQ(compPiece, pieceFromPiece); EXPECT_NE(comp, compPiece); EXPECT_THROW_RE( PathComponent("foo/bar"), std::domain_error, "containing a directory separator"); EXPECT_THROW_RE( PathComponent(""), std::domain_error, "cannot have an empty PathComponent"); EXPECT_THROW_RE( PathComponent("."), std::domain_error, "must not be \\. or \\.\\."); EXPECT_THROW_RE( PathComponent(".."), std::domain_error, "must not be \\. or \\.\\."); } TEST(PathFuncs, RelativePath) { RelativePath emptyRel; EXPECT_EQ("", emptyRel.stringPiece()); EXPECT_EQ("", (emptyRel + RelativePath()).value()); EXPECT_THROW_RE(RelativePath("/foo/bar"), std::domain_error, "absolute path"); EXPECT_THROW_RE( RelativePath("foo/"), std::domain_error, "must not end with a slash"); RelativePathPiece relPiece("foo/bar"); EXPECT_EQ("foo/bar", relPiece.stringPiece()); EXPECT_NE(emptyRel, relPiece); EXPECT_EQ("a", (emptyRel + "a"_relpath).value()); EXPECT_EQ("a", ("a"_relpath + emptyRel).value()); auto comp = "top"_pc + "sub"_pc; EXPECT_EQ("top/sub", comp.stringPiece()); auto comp2 = comp + "third"_pc; EXPECT_EQ("top/sub/third", comp2.stringPiece()); auto comp3 = comp + emptyRel; EXPECT_EQ("top/sub", comp3.stringPiece()); auto comp4 = emptyRel + comp; EXPECT_EQ("top/sub", comp4.stringPiece()); EXPECT_EQ("third", comp2.basename().stringPiece()); EXPECT_EQ("top/sub", comp2.dirname().stringPiece()); EXPECT_EQ("top", comp2.dirname().dirname().stringPiece()); EXPECT_EQ("", comp2.dirname().dirname().dirname().stringPiece()); EXPECT_EQ("", comp2.dirname().dirname().dirname().dirname().stringPiece()); } TEST(PathFuncs, AbsolutePath) { EXPECT_THROW_RE( AbsolutePath("invalid"), std::domain_error, "non-absolute string"); EXPECT_THROW_RE(AbsolutePath(""), std::domain_error, "non-absolute string"); EXPECT_THROW_RE( AbsolutePath("/trailing/slash/"), std::domain_error, "must not end with a slash"); AbsolutePath abs("/some/dir"); EXPECT_EQ("dir", abs.basename().stringPiece()); EXPECT_EQ("/some", abs.dirname().stringPiece()); EXPECT_EQ("/some/dir", (abs + ""_relpath).value()); auto rel = "one"_pc + "two"_pc; auto comp = abs + rel; EXPECT_EQ("/some/dir/one/two", comp.stringPiece()); auto comp2 = abs + RelativePathPiece(); EXPECT_EQ("/some/dir", comp2.stringPiece()); auto comp3 = abs + PathComponent("comp"); EXPECT_EQ("/some/dir/comp", comp3.stringPiece()); EXPECT_EQ("/", AbsolutePathPiece().stringPiece()); EXPECT_EQ("/", "/"_abspath.stringPiece()); auto comp4 = AbsolutePathPiece() + "foo"_relpath; EXPECT_EQ("/foo", comp4.stringPiece()); AbsolutePath root("/"); EXPECT_EQ(RelativePathPiece(), root.relativize(root)); EXPECT_EQ(RelativePathPiece(), abs.relativize(abs)); EXPECT_EQ("foo"_relpath, abs.relativize(abs + "foo"_relpath)); EXPECT_EQ("foo/bar"_relpath, abs.relativize(abs + "foo/bar"_relpath)); // auto bad = rel + abs; doesn't compile; invalid for ABS to be on RHS } TEST(PathFuncs, relativize_memory_safety) { AbsolutePath abs{"/some/dir/this/has/to/be/long/enough/to/exceed/sso"}; // This test validates that the result is accessible as long as the // argument's memory is alive. const auto& child = abs + "foo"_relpath; auto piece = abs.relativize(child); EXPECT_EQ("foo"_relpath, piece); } TEST(PathFuncs, dirname) { EXPECT_EQ("foo/bar", dirname("foo/bar/baz")); EXPECT_EQ("foo", dirname("foo/bar")); EXPECT_EQ("", dirname("foo")); } TEST(PathFuncs, basename) { // Note: need explicit StringPiece type here to avoid the compiler picking // basename(3) based on type deduction from the const char * literal. // Should resolve our our idea of a Path type, but don't want to bikeshed // that right here EXPECT_EQ("baz", basename(StringPiece("foo/bar/baz"))); EXPECT_EQ("bar", basename(StringPiece("foo/bar"))); EXPECT_EQ("foo", basename(StringPiece("foo"))); } TEST(PathFuncs, isSubDir) { // Helper functions that convert string arguments to RelativePathPiece auto isSubdir = [](StringPiece a, StringPiece b) { return RelativePathPiece(a).isSubDirOf(RelativePathPiece(b)); }; auto isParent = [](StringPiece a, StringPiece b) { return RelativePathPiece(a).isParentDirOf(RelativePathPiece(b)); }; EXPECT_TRUE(isSubdir("foo/bar/baz", "")); EXPECT_TRUE(isSubdir("foo/bar/baz", "foo")); EXPECT_TRUE(isSubdir("foo/bar/baz", "foo/bar")); EXPECT_FALSE(isSubdir("foo/bar/baz", "foo/bar/baz")); EXPECT_FALSE(isSubdir("foo/bar", "foo/bar/baz")); EXPECT_FALSE(isSubdir("foo/barbaz", "foo/bar")); EXPECT_TRUE(isParent("", "foo/bar/baz")); EXPECT_TRUE(isParent("foo", "foo/bar/baz")); EXPECT_TRUE(isParent("foo/bar", "foo/bar/baz")); EXPECT_FALSE(isParent("foo/bar/baz", "foo/bar/baz")); EXPECT_FALSE(isParent("foo/bar", "foo/barbaz")); EXPECT_FALSE(isParent("foo/bar/baz", "foo/bar")); } TEST(PathFuncs, findParent) { RelativePath path("foo/bar/baz"); auto it = path.findParent("foo"_relpath); vector parents(it, path.allPaths().end()); EXPECT_EQ(3, parents.size()); EXPECT_EQ("foo"_relpath, parents.at(0)); EXPECT_EQ("foo/bar"_relpath, parents.at(1)); EXPECT_EQ("foo/bar/baz"_relpath, parents.at(2)); it = path.findParent(""_relpath); parents = vector(it, path.allPaths().end()); EXPECT_EQ(4, parents.size()); EXPECT_EQ(""_relpath, parents.at(0)); EXPECT_EQ("foo"_relpath, parents.at(1)); EXPECT_EQ("foo/bar"_relpath, parents.at(2)); EXPECT_EQ("foo/bar/baz"_relpath, parents.at(3)); it = path.findParent("foo/bar/baz"_relpath); EXPECT_TRUE(it == path.allPaths().end()); } TEST(PathFuncs, format) { // Test using folly::format with all of the various path types PathComponentPiece comp("foo"); EXPECT_EQ("x(foo)", folly::sformat("x({})", comp)); PathComponentPiece compPiece("bar"); EXPECT_EQ("x(bar)", folly::sformat("x({})", compPiece)); AbsolutePath abs("/home/johndoe"); EXPECT_EQ("x(/home/johndoe)", folly::sformat("x({})", abs)); AbsolutePathPiece absPiece("/var/log/clowntown"); EXPECT_EQ("x(/var/log/clowntown)", folly::sformat("x({})", absPiece)); RelativePath rel("src/ping.c"); EXPECT_EQ("x(src/ping.c)", folly::sformat("x({})", rel)); RelativePathPiece relPiece("src/abc.def"); EXPECT_EQ("x(src/abc.def)", folly::sformat("x({})", relPiece)); } TEST(PathFuncs, splitFirst) { using SplitResult = decltype(splitFirst(std::declval())); RelativePath rp1{""}; EXPECT_THROW(splitFirst(rp1), std::domain_error); RelativePath rp2{"foo"}; EXPECT_EQ((SplitResult{"foo", ""}), splitFirst(rp2)); RelativePath rp3{"foo/bar"}; EXPECT_EQ((SplitResult{"foo", "bar"}), splitFirst(rp3)); RelativePath rp4{"foo/bar/baz"}; EXPECT_EQ((SplitResult{"foo", "bar/baz"}), splitFirst(rp4)); } namespace { /* * Helper class to create a temporary directory and cd into it while this * object exists. */ class TmpWorkingDir { public: TmpWorkingDir() { chdir(pathStr.c_str()); } ~TmpWorkingDir() { chdir(oldDir.value().c_str()); } AbsolutePath oldDir{getcwd()}; folly::test::TemporaryDirectory dir = makeTempDir(); std::string pathStr{dir.path().string()}; AbsolutePathPiece path{pathStr}; }; } // namespace TEST(PathFuncs, canonicalPath) { EXPECT_EQ("/foo/bar/abc.txt", canonicalPath("/foo/bar/abc.txt").value()); EXPECT_EQ("/foo/bar/abc.txt", canonicalPath("///foo/bar//abc.txt").value()); EXPECT_EQ("/foo/bar/abc.txt", canonicalPath("///foo/bar/./abc.txt").value()); EXPECT_EQ("/foo/bar/abc.txt", canonicalPath("/..//foo/bar//abc.txt").value()); EXPECT_EQ("/abc.txt", canonicalPath("/..//foo/bar/../../abc.txt").value()); EXPECT_EQ("/", canonicalPath("/").value()); EXPECT_EQ("/", canonicalPath("////").value()); EXPECT_EQ("/", canonicalPath("/../../..").value()); EXPECT_EQ("/", canonicalPath("/././.").value()); EXPECT_EQ("/", canonicalPath("/./././").value()); EXPECT_EQ("/", canonicalPath("/./.././").value()); EXPECT_EQ("/abc.foo", canonicalPath("/abc.foo/../abc.foo///").value()); EXPECT_EQ("/foo", canonicalPath("//foo").value()); auto base = AbsolutePath{"/base/dir/path"}; EXPECT_EQ("/base/dir/path", canonicalPath("", base).value()); EXPECT_EQ("/base/dir/path/abc", canonicalPath("abc", base).value()); EXPECT_EQ("/base/dir/path/abc/def", canonicalPath("abc/def/", base).value()); EXPECT_EQ("/base/dir/path", canonicalPath(".", base).value()); EXPECT_EQ("/base/dir/path", canonicalPath("././/.", base).value()); EXPECT_EQ("/base/dir", canonicalPath("..", base).value()); EXPECT_EQ("/base/dir", canonicalPath("../", base).value()); EXPECT_EQ("/base/dir", canonicalPath("../.", base).value()); EXPECT_EQ("/base/dir", canonicalPath(".././", base).value()); EXPECT_EQ( "/base/dir/xy/s.txt", canonicalPath(".././xy//z/../s.txt", base).value()); EXPECT_EQ( "/base/dir/xy/s.txt", canonicalPath("z//.././../xy//s.txt", base).value()); EXPECT_EQ( "/base/dir/path/ foo bar ", canonicalPath(" foo bar ", base).value()); EXPECT_EQ("/base/dir/path/.../test", canonicalPath(".../test", base).value()); TmpWorkingDir tmpDir; EXPECT_EQ(tmpDir.pathStr, canonicalPath(".").value()); EXPECT_EQ(tmpDir.pathStr + "/foo", canonicalPath("foo").value()); EXPECT_EQ( tmpDir.pathStr + "/a/b/c.txt", canonicalPath("foo/../a//d/../b/./c.txt").value()); } TEST(PathFuncs, joinAndNormalize) { const auto good = [](const char* base, const char* path) { return joinAndNormalize(RelativePath{base}, path).value(); }; const auto bad = [](const char* base, const char* path) { return joinAndNormalize(RelativePath{base}, path).error(); }; EXPECT_EQ(good("a/b/c", "d"), RelativePath{"a/b/c/d"}); EXPECT_EQ(good("a/b/c/d", "../../e"), RelativePath{"a/b/e"}); EXPECT_EQ(good("a/b/c", ""), RelativePath{"a/b/c"}); EXPECT_EQ(good("", ""), RelativePath{""}); EXPECT_EQ(good("", "a/b"), RelativePath{"a/b"}); EXPECT_EQ(good("a/b", "../.."), RelativePath{""}); EXPECT_EQ(good("a/b/c", "../.."), RelativePath{"a"}); EXPECT_EQ(bad("a", "/b/c"), EPERM); EXPECT_EQ(bad("a/b/c", "/"), EPERM); EXPECT_EQ(bad("", ".."), EXDEV); EXPECT_EQ(bad("a/b", "../../.."), EXDEV); EXPECT_EQ(bad("a", "b/../../.."), EXDEV); } TEST(PathFuncs, realpath) { TmpWorkingDir tmpDir; // Change directories to the tmp dir for the duration of this test auto oldDir = getcwd(); SCOPE_EXIT { chdir(oldDir.value().c_str()); }; chdir(tmpDir.pathStr.c_str()); // Set up some files to test with folly::checkUnixError( open("simple.txt", O_WRONLY | O_CREAT, 0644), "failed to create simple.txt"); folly::checkUnixError(mkdir("parent", 0755), "failed to mkdir parent"); folly::checkUnixError(mkdir("parent/child", 0755), "failed to mkdir child"); folly::checkUnixError( open("parent/child/file.txt", O_WRONLY | O_CREAT, 0644), "failed to create file.txt"); folly::checkUnixError( symlink("parent//child/../child/file.txt", "wonky_link"), "failed to create wonky_link"); folly::checkUnixError( symlink("child/nowhere", "parent/broken_link"), "failed to create broken_link"); folly::checkUnixError( symlink("../loop_b", "parent/loop_a"), "failed to create loop_a"); folly::checkUnixError( symlink("parent/loop_a", "loop_b"), "failed to create loop_b"); // Now actually test realpath() EXPECT_EQ(tmpDir.pathStr + "/simple.txt", realpath("simple.txt").value()); EXPECT_EQ( tmpDir.pathStr + "/simple.txt", realpath("parent/../simple.txt").value()); EXPECT_EQ( tmpDir.pathStr + "/simple.txt", realpath("parent/..//parent/.//child/../../simple.txt").value()); EXPECT_THROW_ERRNO(realpath("nosuchdir/../simple.txt"), ENOENT); EXPECT_EQ( tmpDir.pathStr + "/simple.txt", realpath(tmpDir.pathStr + "//simple.txt").value()); EXPECT_EQ( tmpDir.pathStr + "/simple.txt", realpath(tmpDir.pathStr + "//parent/../simple.txt").value()); EXPECT_EQ( tmpDir.pathStr + "/parent/child/file.txt", realpath("parent///child//file.txt").value()); EXPECT_EQ( tmpDir.pathStr + "/parent/child/file.txt", realpath("wonky_link").value()); EXPECT_EQ( tmpDir.pathStr + "/parent/child/file.txt", realpathExpected("wonky_link").value().value()); EXPECT_EQ( tmpDir.pathStr + "/parent/child", realpath("parent///child//").value()); EXPECT_EQ(tmpDir.pathStr + "/parent", realpath("parent/.").value()); EXPECT_EQ(tmpDir.pathStr, realpath("parent/..").value()); EXPECT_THROW_ERRNO(realpath("parent/broken_link"), ENOENT); EXPECT_THROW_ERRNO(realpath("parent/loop_a"), ELOOP); EXPECT_THROW_ERRNO(realpath("loop_b"), ELOOP); EXPECT_THROW_ERRNO(realpath("parent/nosuchfile"), ENOENT); EXPECT_EQ(ELOOP, realpathExpected("parent/loop_a").error()); EXPECT_EQ(ENOENT, realpathExpected("parent/nosuchfile").error()); // Perform some tests for normalizeBestEffort() as well EXPECT_EQ( tmpDir.pathStr + "/simple.txt", normalizeBestEffort(tmpDir.pathStr + "//simple.txt").value()); EXPECT_EQ( tmpDir.pathStr + "/parent/nosuchfile", normalizeBestEffort("parent/nosuchfile")); EXPECT_EQ( tmpDir.pathStr + "/nosuchfile", normalizeBestEffort("parent/..//nosuchfile")); EXPECT_EQ( tmpDir.pathStr + "/parent/loop_a", normalizeBestEffort("parent/loop_a")); EXPECT_EQ( "/foo/bar/abc.txt", normalizeBestEffort("/..//foo/bar//abc.txt").value()); EXPECT_EQ(tmpDir.pathStr, normalizeBestEffort(tmpDir.pathStr)); } TEST(PathFuncs, expandUser) { EXPECT_EQ("/foo/bar"_abspath, expandUser("/foo/bar")); EXPECT_THROW(expandUser("~user/foo/bar"), std::runtime_error); EXPECT_THROW(expandUser("~user/foo/bar", ""), std::runtime_error); EXPECT_EQ("/home/bob/foo/bar"_abspath, expandUser("~/foo/bar", "/home/bob")); EXPECT_EQ("/home/bob"_abspath, expandUser("~", "/home/bob")); EXPECT_EQ( "/home/bob/foo"_abspath, expandUser("~//foo/./bar/../", "/home/./bob/")); } template void compareHelper(StringPiece str1, StringPiece str2) { EXPECT_TRUE(StoredType{str1} < StoredType{str2}); EXPECT_TRUE(PieceType{str1} < PieceType{str2}); EXPECT_TRUE(StoredType{str1} < PieceType{str2}); EXPECT_TRUE(PieceType{str1} < StoredType{str2}); EXPECT_TRUE(StoredType{str1} <= StoredType{str2}); EXPECT_TRUE(PieceType{str1} <= PieceType{str2}); EXPECT_TRUE(StoredType{str1} <= PieceType{str2}); EXPECT_TRUE(PieceType{str1} <= StoredType{str2}); EXPECT_FALSE(StoredType{str1} > StoredType{str2}); EXPECT_FALSE(PieceType{str1} > PieceType{str2}); EXPECT_FALSE(StoredType{str1} > PieceType{str2}); EXPECT_FALSE(PieceType{str1} > StoredType{str2}); EXPECT_FALSE(StoredType{str1} >= StoredType{str2}); EXPECT_FALSE(PieceType{str1} >= PieceType{str2}); EXPECT_FALSE(StoredType{str1} >= PieceType{str2}); EXPECT_FALSE(PieceType{str1} >= StoredType{str2}); EXPECT_FALSE(StoredType{str1} == StoredType{str2}); EXPECT_FALSE(PieceType{str1} == PieceType{str2}); EXPECT_FALSE(StoredType{str1} == PieceType{str2}); EXPECT_FALSE(PieceType{str1} == StoredType{str2}); EXPECT_TRUE(StoredType{str1} != StoredType{str2}); EXPECT_TRUE(PieceType{str1} != PieceType{str2}); EXPECT_TRUE(StoredType{str1} != PieceType{str2}); EXPECT_TRUE(PieceType{str1} != StoredType{str2}); } TEST(PathFuncs, comparison) { // Test various combinations of path comparison operators, // mostly to make sure that the template instantiations all compile // correctly and unambiguously. compareHelper("abc", "def"); compareHelper("abc/def", "abc/xyz"); compareHelper("/abc/def", "/abc/xyz"); // We should always perform byte-by-byte comparisons (and ignore locale) EXPECT_LT(PathComponent{"ABC"}, PathComponent{"abc"}); EXPECT_LT(PathComponent{"XYZ"}, PathComponent{"abc"}); } // std::string is nothrow move constructible and assignable, so the // path types should be as well. static_assert( std::is_nothrow_move_constructible::value, "AbsolutePath should be nothrow move constructible"); static_assert( std::is_nothrow_move_constructible::value, "AbsolutePathPiece should be nothrow move constructible"); static_assert( std::is_nothrow_move_constructible::value, "RelativePath should be nothrow move constructible"); static_assert( std::is_nothrow_move_constructible::value, "RelativePathPiece should be nothrow move constructible"); static_assert( std::is_nothrow_move_constructible::value, "PathComponent should be nothrow move constructible"); static_assert( std::is_nothrow_move_constructible::value, "PathComponentPiece should be nothrow move constructible"); static_assert( std::is_nothrow_move_assignable::value, "AbsolutePath should be nothrow move assignable"); static_assert( std::is_nothrow_move_assignable::value, "AbsolutePathPiece should be nothrow move assignable"); static_assert( std::is_nothrow_move_assignable::value, "RelativePath should be nothrow move assignable"); static_assert( std::is_nothrow_move_assignable::value, "RelativePathPiece should be nothrow move assignable"); static_assert( std::is_nothrow_move_assignable::value, "PathComponent should be nothrow move assignable"); static_assert( std::is_nothrow_move_assignable::value, "PathComponentPiece should be nothrow move assignable");