/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying file LICENSE.rst or https://cmake.org/licensing for details. */ #include <cmConfigure.h> // IWYU pragma: keep #include <cerrno> #include <map> #include <string> #include <utility> #ifdef _WIN32 # include <cctype> #endif #include <cmsys/Status.hxx> #include "cmPathResolver.h" #if defined(_WIN32) || defined(__APPLE__) # include "cmSystemTools.h" #endif #include "testCommon.h" // IWYU pragma: no_forward_declare cm::PathResolver::Policies::LogicalPath // IWYU pragma: no_forward_declare cm::PathResolver::Policies::NaivePath // IWYU pragma: no_forward_declare cm::PathResolver::Policies::CasePath // IWYU pragma: no_forward_declare cm::PathResolver::Policies::RealPath namespace { class MockSystem : public cm::PathResolver::System { public: ~MockSystem() override = default; struct Path { std::string Name; std::string Link; }; std::map<std::string, Path> Paths; void SetPaths(std::map<std::string, Path> paths) { this->Paths = std::move(paths); } static std::string AdjustCase(std::string const& path) { #if defined(_WIN32) || defined(__APPLE__) return cmSystemTools::LowerCase(path); #else return path; #endif } cmsys::Status ReadSymlink(std::string const& path, std::string& link) override { auto i = this->Paths.find(AdjustCase(path)); if (i == this->Paths.end()) { return cmsys::Status::POSIX(ENOENT); } if (i->second.Link.empty()) { return cmsys::Status::POSIX(EINVAL); } link = i->second.Link; return cmsys::Status::Success(); } bool PathExists(std::string const& path) override { return this->Paths.find(AdjustCase(path)) != this->Paths.end(); } std::string WorkDir; void SetWorkDir(std::string wd) { this->WorkDir = std::move(wd); } std::string GetWorkingDirectory() override { return this->WorkDir; } #ifdef _WIN32 std::map<char, std::string> WorkDirOnDrive; void SetWorkDirOnDrive(std::map<char, std::string> wd) { this->WorkDirOnDrive = std::move(wd); } std::string GetWorkingDirectoryOnDrive(char letter) override { std::string result; auto i = this->WorkDirOnDrive.find(std::tolower(letter)); if (i != this->WorkDirOnDrive.end()) { result = i->second; } return result; } #endif #if defined(_WIN32) || defined(__APPLE__) cmsys::Status ReadName(std::string const& path, std::string& name) override { auto i = this->Paths.find(AdjustCase(path)); if (i == this->Paths.end()) { return cmsys::Status::POSIX(ENOENT); } name = i->second.Name; return cmsys::Status::Success(); } #endif }; #define EXPECT_RESOLVE(_in, _expect) \ do { \ std::string out; \ ASSERT_TRUE(r.Resolve(_in, out)); \ ASSERT_EQUAL(out, _expect); \ } while (false) #define EXPECT_ENOENT(_in, _expect) \ do { \ std::string out; \ ASSERT_EQUAL(r.Resolve(_in, out).GetPOSIX(), ENOENT); \ ASSERT_EQUAL(out, _expect); \ } while (false) using namespace cm::PathResolver; bool posixRoot() { std::cout << "posixRoot()\n"; MockSystem os; os.SetPaths({ { "/", { {}, {} } }, }); Resolver<Policies::RealPath> const r(os); EXPECT_RESOLVE("/", "/"); EXPECT_RESOLVE("//", "/"); EXPECT_RESOLVE("/.", "/"); EXPECT_RESOLVE("/./", "/"); EXPECT_RESOLVE("/..", "/"); EXPECT_RESOLVE("/../", "/"); return true; } bool posixAbsolutePath() { std::cout << "posixAbsolutePath()\n"; MockSystem os; os.SetPaths({ { "/", { {}, {} } }, { "/a", { {}, {} } }, }); Resolver<Policies::RealPath> const r(os); EXPECT_RESOLVE("/a", "/a"); EXPECT_RESOLVE("/a/", "/a"); EXPECT_RESOLVE("/a//", "/a"); EXPECT_RESOLVE("/a/.", "/a"); EXPECT_RESOLVE("/a/./", "/a"); EXPECT_RESOLVE("/a/..", "/"); EXPECT_RESOLVE("/a/../", "/"); EXPECT_RESOLVE("/a/../..", "/"); #ifndef _WIN32 EXPECT_RESOLVE("//a", "/a"); #endif return true; } bool posixWorkingDirectory() { std::cout << "posixWorkingDirectory()\n"; MockSystem os; os.SetPaths({ { "/", { {}, {} } }, { "/a", { {}, {} } }, { "/cwd", { {}, {} } }, { "/cwd/a", { {}, {} } }, }); Resolver<Policies::RealPath> const r(os); EXPECT_RESOLVE("", "/"); EXPECT_RESOLVE(".", "/"); EXPECT_RESOLVE("..", "/"); EXPECT_RESOLVE("a", "/a"); os.SetWorkDir("/cwd"); EXPECT_RESOLVE("", "/cwd"); EXPECT_RESOLVE(".", "/cwd"); EXPECT_RESOLVE("..", "/"); EXPECT_RESOLVE("a", "/cwd/a"); return true; } bool posixSymlink() { std::cout << "posixSymlink()\n"; MockSystem os; os.SetPaths({ { "/", { {}, {} } }, { "/link-a", { {}, "a" } }, { "/link-a-excess", { {}, "a//." } }, { "/link-broken", { {}, "link-broken-dest" } }, { "/a", { {}, {} } }, { "/a/b", { {}, {} } }, { "/a/link-b", { {}, "b" } }, { "/a/b/link-c", { {}, "c" } }, { "/a/b/c", { {}, {} } }, { "/a/b/c/link-..|..", { {}, "../.." } }, { "/a/link-|1|2", { {}, "/1/2" } }, { "/1", { {}, {} } }, { "/1/2", { {}, {} } }, { "/1/2/3", { {}, {} } }, }); { Resolver<Policies::CasePath> const r(os); EXPECT_RESOLVE("/link-a", "/link-a"); EXPECT_RESOLVE("/link-a-excess", "/link-a-excess"); EXPECT_RESOLVE("/link-a-excess/b", "/link-a-excess/b"); EXPECT_RESOLVE("/link-broken", "/link-broken"); EXPECT_RESOLVE("/link-a/../missing", "/missing"); EXPECT_RESOLVE("/a/b/link-c", "/a/b/link-c"); EXPECT_RESOLVE("/a/link-b/c", "/a/link-b/c"); EXPECT_RESOLVE("/a/link-b/link-c/..", "/a/link-b"); EXPECT_RESOLVE("/a/b/c/link-..|..", "/a/b/c/link-..|.."); EXPECT_RESOLVE("/a/b/c/link-..|../link-b", "/a/b/c/link-..|../link-b"); EXPECT_RESOLVE("/a/link-|1|2/3", "/a/link-|1|2/3"); EXPECT_RESOLVE("/a/link-|1|2/../2/3", "/a/2/3"); } { Resolver<Policies::LogicalPath> const r(os); EXPECT_RESOLVE("/link-a", "/link-a"); EXPECT_RESOLVE("/link-a-excess", "/link-a-excess"); EXPECT_RESOLVE("/link-a-excess/b", "/link-a-excess/b"); EXPECT_RESOLVE("/link-broken", "/link-broken"); EXPECT_RESOLVE("/link-a/../missing", "/missing"); EXPECT_RESOLVE("/a/b/link-c", "/a/b/link-c"); EXPECT_RESOLVE("/a/link-b/c", "/a/link-b/c"); EXPECT_RESOLVE("/a/link-b/link-c/..", "/a/link-b"); EXPECT_RESOLVE("/a/b/c/link-..|..", "/a/b/c/link-..|.."); EXPECT_RESOLVE("/a/b/c/link-..|../link-b", "/a/b/c/link-..|../link-b"); EXPECT_RESOLVE("/a/link-|1|2/3", "/a/link-|1|2/3"); EXPECT_RESOLVE("/a/link-|1|2/../2/3", "/1/2/3"); } { Resolver<Policies::RealPath> const r(os); EXPECT_RESOLVE("/link-a", "/a"); EXPECT_RESOLVE("/link-a-excess", "/a"); EXPECT_RESOLVE("/link-a-excess/b", "/a/b"); EXPECT_ENOENT("/link-broken", "/link-broken-dest"); EXPECT_ENOENT("/link-a/../missing", "/missing"); EXPECT_RESOLVE("/a/b/link-c", "/a/b/c"); EXPECT_RESOLVE("/a/link-b/c", "/a/b/c"); EXPECT_RESOLVE("/a/link-b/link-c/..", "/a/b"); EXPECT_RESOLVE("/a/b/c/link-..|..", "/a"); EXPECT_RESOLVE("/a/b/c/link-..|../link-b", "/a/b"); EXPECT_RESOLVE("/a/link-|1|2/3", "/1/2/3"); } return true; } #ifdef __APPLE__ bool macosActualCase() { std::cout << "macosActualCase()\n"; MockSystem os; os.SetPaths({ { "/", { {}, {} } }, { "/mixed", { "MiXeD", {} } }, { "/mixed/link-mixed", { "LiNk-MiXeD", "mixed" } }, { "/mixed/mixed", { "MiXeD", {} } }, { "/mixed/link-c-mixed", { "LiNk-C-MiXeD", "/mIxEd" } }, { "/upper", { "UPPER", {} } }, { "/upper/link-upper", { "LINK-UPPER", "upper" } }, { "/upper/upper", { "UPPER", {} } }, { "/upper/link-c-upper", { "LINK-C-UPPER", "/upper" } }, }); { Resolver<Policies::CasePath> const r(os); EXPECT_RESOLVE("/mIxEd/MiSsInG", "/MiXeD/MiSsInG"); EXPECT_RESOLVE("/mIxEd/link-MiXeD", "/MiXeD/LiNk-MiXeD"); EXPECT_RESOLVE("/mIxEd/link-c-MiXeD", "/MiXeD/LiNk-C-MiXeD"); EXPECT_RESOLVE("/upper/mIsSiNg", "/UPPER/mIsSiNg"); EXPECT_RESOLVE("/upper/link-upper", "/UPPER/LINK-UPPER"); EXPECT_RESOLVE("/upper/link-c-upper", "/UPPER/LINK-C-UPPER"); } { Resolver<Policies::LogicalPath> const r(os); EXPECT_RESOLVE("/mIxEd/MiSsInG", "/MiXeD/MiSsInG"); EXPECT_RESOLVE("/mIxEd/link-MiXeD", "/MiXeD/LiNk-MiXeD"); EXPECT_RESOLVE("/mIxEd/link-c-MiXeD", "/MiXeD/LiNk-C-MiXeD"); EXPECT_RESOLVE("/upper/mIsSiNg", "/UPPER/mIsSiNg"); EXPECT_RESOLVE("/upper/link-upper", "/UPPER/LINK-UPPER"); EXPECT_RESOLVE("/upper/link-c-upper", "/UPPER/LINK-C-UPPER"); } { Resolver<Policies::RealPath> const r(os); EXPECT_ENOENT("/mIxEd/MiSsInG", "/MiXeD/MiSsInG"); EXPECT_RESOLVE("/mIxEd/link-MiXeD", "/MiXeD/MiXeD"); EXPECT_RESOLVE("/mIxEd/link-c-MiXeD", "/MiXeD"); EXPECT_ENOENT("/upper/mIsSiNg", "/UPPER/mIsSiNg"); EXPECT_RESOLVE("/upper/link-upper", "/UPPER/UPPER"); EXPECT_RESOLVE("/upper/link-c-upper", "/UPPER"); } return true; } #endif #ifdef _WIN32 bool windowsRoot() { std::cout << "windowsRoot()\n"; MockSystem os; { Resolver<Policies::NaivePath> const r(os); EXPECT_RESOLVE("c:/", "c:/"); EXPECT_RESOLVE("C:/", "C:/"); EXPECT_RESOLVE("c://", "c:/"); EXPECT_RESOLVE("C:/.", "C:/"); EXPECT_RESOLVE("c:/./", "c:/"); EXPECT_RESOLVE("C:/..", "C:/"); EXPECT_RESOLVE("c:/../", "c:/"); } { Resolver<Policies::CasePath> const r(os); EXPECT_RESOLVE("c:/", "C:/"); EXPECT_RESOLVE("C:/", "C:/"); EXPECT_RESOLVE("c://", "C:/"); EXPECT_RESOLVE("C:/.", "C:/"); EXPECT_RESOLVE("c:/./", "C:/"); EXPECT_RESOLVE("C:/..", "C:/"); EXPECT_RESOLVE("c:/../", "C:/"); } os.SetPaths({ { "c:/", { {}, {} } }, { "//host/", { {}, {} } }, }); { Resolver<Policies::RealPath> const r(os); EXPECT_RESOLVE("c:/", "C:/"); EXPECT_RESOLVE("C:/", "C:/"); EXPECT_RESOLVE("c://", "C:/"); EXPECT_RESOLVE("C:/.", "C:/"); EXPECT_RESOLVE("c:/./", "C:/"); EXPECT_RESOLVE("C:/..", "C:/"); EXPECT_RESOLVE("c:/../", "C:/"); EXPECT_RESOLVE("//host", "//host/"); EXPECT_RESOLVE("//host/.", "//host/"); EXPECT_RESOLVE("//host/./", "//host/"); EXPECT_RESOLVE("//host/..", "//host/"); EXPECT_RESOLVE("//host/../", "//host/"); } return true; } bool windowsAbsolutePath() { std::cout << "windowsAbsolutePath()\n"; MockSystem os; os.SetPaths({ { "c:/", { {}, {} } }, { "c:/a", { {}, {} } }, }); Resolver<Policies::RealPath> const r(os); EXPECT_RESOLVE("c:/a", "C:/a"); EXPECT_RESOLVE("c:/a/", "C:/a"); EXPECT_RESOLVE("c:/a//", "C:/a"); EXPECT_RESOLVE("c:/a/.", "C:/a"); EXPECT_RESOLVE("c:/a/./", "C:/a"); EXPECT_RESOLVE("c:/a/..", "C:/"); EXPECT_RESOLVE("c:/a/../", "C:/"); EXPECT_RESOLVE("c:/a/../..", "C:/"); return true; } bool windowsActualCase() { std::cout << "windowsActualCase()\n"; MockSystem os; os.SetPaths({ { "c:/", { {}, {} } }, { "c:/mixed", { "MiXeD", {} } }, { "c:/mixed/link-mixed", { "LiNk-MiXeD", "mixed" } }, { "c:/mixed/mixed", { "MiXeD", {} } }, { "c:/mixed/link-c-mixed", { "LiNk-C-MiXeD", "C:/mIxEd" } }, { "c:/upper", { "UPPER", {} } }, { "c:/upper/link-upper", { "LINK-UPPER", "upper" } }, { "c:/upper/upper", { "UPPER", {} } }, { "c:/upper/link-c-upper", { "LINK-C-UPPER", "c:/upper" } }, }); { Resolver<Policies::CasePath> const r(os); EXPECT_RESOLVE("c:/mIxEd/MiSsInG", "C:/MiXeD/MiSsInG"); EXPECT_RESOLVE("c:/mIxEd/link-MiXeD", "C:/MiXeD/LiNk-MiXeD"); EXPECT_RESOLVE("c:/mIxEd/link-c-MiXeD", "C:/MiXeD/LiNk-C-MiXeD"); EXPECT_RESOLVE("c:/upper/mIsSiNg", "C:/UPPER/mIsSiNg"); EXPECT_RESOLVE("c:/upper/link-upper", "C:/UPPER/LINK-UPPER"); EXPECT_RESOLVE("c:/upper/link-c-upper", "C:/UPPER/LINK-C-UPPER"); } { Resolver<Policies::LogicalPath> const r(os); EXPECT_RESOLVE("c:/mIxEd/MiSsInG", "C:/MiXeD/MiSsInG"); EXPECT_RESOLVE("c:/mIxEd/link-MiXeD", "C:/MiXeD/LiNk-MiXeD"); EXPECT_RESOLVE("c:/mIxEd/link-c-MiXeD", "C:/MiXeD/LiNk-C-MiXeD"); EXPECT_RESOLVE("c:/upper/mIsSiNg", "C:/UPPER/mIsSiNg"); EXPECT_RESOLVE("c:/upper/link-upper", "C:/UPPER/LINK-UPPER"); EXPECT_RESOLVE("c:/upper/link-c-upper", "C:/UPPER/LINK-C-UPPER"); } { Resolver<Policies::RealPath> const r(os); EXPECT_ENOENT("c:/mIxEd/MiSsInG", "C:/MiXeD/MiSsInG"); EXPECT_RESOLVE("c:/mIxEd/link-MiXeD", "C:/MiXeD/MiXeD"); EXPECT_RESOLVE("c:/mIxEd/link-c-MiXeD", "C:/MiXeD"); EXPECT_ENOENT("c:/upper/mIsSiNg", "C:/UPPER/mIsSiNg"); EXPECT_RESOLVE("c:/upper/link-upper", "C:/UPPER/UPPER"); EXPECT_RESOLVE("c:/upper/link-c-upper", "C:/UPPER"); } return true; } bool windowsWorkingDirectory() { std::cout << "windowsWorkingDirectory()\n"; MockSystem os; os.SetPaths({ { "c:/", { {}, {} } }, { "c:/a", { {}, {} } }, { "c:/cwd", { {}, {} } }, { "c:/cwd/a", { {}, {} } }, }); { Resolver<Policies::LogicalPath> const r(os); EXPECT_RESOLVE("", "/"); EXPECT_RESOLVE(".", "/"); EXPECT_RESOLVE("..", "/"); EXPECT_RESOLVE("a", "/a"); } { Resolver<Policies::RealPath> const r(os); os.SetWorkDir("c:/cwd"); EXPECT_RESOLVE("", "C:/cwd"); EXPECT_RESOLVE(".", "C:/cwd"); EXPECT_RESOLVE("..", "C:/"); EXPECT_RESOLVE("a", "C:/cwd/a"); EXPECT_ENOENT("missing", "C:/cwd/missing"); } return true; } bool windowsWorkingDirectoryOnDrive() { std::cout << "windowsWorkingDirectoryOnDrive()\n"; MockSystem os; os.SetWorkDir("c:/cwd"); os.SetWorkDirOnDrive({ { 'd', "d:/cwd-d" }, }); { Resolver<Policies::NaivePath> const r(os); EXPECT_RESOLVE("c:", "c:/cwd"); EXPECT_RESOLVE("c:.", "c:/cwd"); EXPECT_RESOLVE("c:..", "c:/"); EXPECT_RESOLVE("C:", "C:/cwd"); EXPECT_RESOLVE("C:.", "C:/cwd"); EXPECT_RESOLVE("C:..", "C:/"); EXPECT_RESOLVE("d:", "d:/cwd-d"); EXPECT_RESOLVE("d:.", "d:/cwd-d"); EXPECT_RESOLVE("d:..", "d:/"); EXPECT_RESOLVE("D:", "D:/cwd-d"); EXPECT_RESOLVE("D:.", "D:/cwd-d"); EXPECT_RESOLVE("D:..", "D:/"); EXPECT_RESOLVE("e:", "e:/"); EXPECT_RESOLVE("e:.", "e:/"); EXPECT_RESOLVE("e:..", "e:/"); EXPECT_RESOLVE("E:", "E:/"); EXPECT_RESOLVE("E:.", "E:/"); EXPECT_RESOLVE("E:..", "E:/"); } { Resolver<Policies::CasePath> const r(os); EXPECT_RESOLVE("c:", "C:/cwd"); EXPECT_RESOLVE("c:.", "C:/cwd"); EXPECT_RESOLVE("c:..", "C:/"); EXPECT_RESOLVE("C:", "C:/cwd"); EXPECT_RESOLVE("C:.", "C:/cwd"); EXPECT_RESOLVE("C:..", "C:/"); EXPECT_RESOLVE("d:", "D:/cwd-d"); EXPECT_RESOLVE("d:.", "D:/cwd-d"); EXPECT_RESOLVE("d:..", "D:/"); EXPECT_RESOLVE("D:", "D:/cwd-d"); EXPECT_RESOLVE("D:.", "D:/cwd-d"); EXPECT_RESOLVE("D:..", "D:/"); EXPECT_RESOLVE("e:", "E:/"); EXPECT_RESOLVE("e:.", "E:/"); EXPECT_RESOLVE("e:..", "E:/"); EXPECT_RESOLVE("E:", "E:/"); EXPECT_RESOLVE("E:.", "E:/"); EXPECT_RESOLVE("E:..", "E:/"); } os.SetPaths({ { "c:/", { {}, {} } }, { "c:/cwd", { {}, {} } }, { "c:/cwd/existing", { {}, {} } }, { "d:/", { {}, {} } }, { "d:/cwd-d", { {}, {} } }, { "d:/cwd-d/existing", { {}, {} } }, { "e:/", { {}, {} } }, }); { Resolver<Policies::RealPath> const r(os); EXPECT_RESOLVE("c:existing", "C:/cwd/existing"); EXPECT_ENOENT("c:missing", "C:/cwd/missing"); EXPECT_RESOLVE("C:existing", "C:/cwd/existing"); EXPECT_ENOENT("C:missing", "C:/cwd/missing"); EXPECT_RESOLVE("d:existing", "D:/cwd-d/existing"); EXPECT_ENOENT("d:missing", "D:/cwd-d/missing"); EXPECT_ENOENT("e:missing", "E:/missing"); EXPECT_ENOENT("f:", "F:/"); } return true; } bool windowsNetworkShare() { std::cout << "windowsNetworkShare()\n"; MockSystem os; os.SetPaths({ { "c:/", { {}, {} } }, { "c:/cwd", { {}, {} } }, { "c:/cwd/link-to-host-share", { {}, "//host/share" } }, { "//host/", { {}, {} } }, { "//host/share", { {}, {} } }, }); os.SetWorkDir("c:/cwd"); { Resolver<Policies::RealPath> const r(os); EXPECT_RESOLVE("//host/share", "//host/share"); EXPECT_RESOLVE("//host/share/", "//host/share"); EXPECT_RESOLVE("//host/share/.", "//host/share"); EXPECT_RESOLVE("//host/share/./", "//host/share"); EXPECT_RESOLVE("//host/share/..", "//host/"); EXPECT_RESOLVE("//host/share/../", "//host/"); EXPECT_RESOLVE("//host/share/../..", "//host/"); EXPECT_RESOLVE("link-to-host-share", "//host/share"); EXPECT_RESOLVE("link-to-host-share/..", "//host/"); EXPECT_ENOENT("link-to-host-share/../missing", "//host/missing"); } { Resolver<Policies::LogicalPath> const r(os); EXPECT_RESOLVE("link-to-host-share", "C:/cwd/link-to-host-share"); EXPECT_RESOLVE("link-to-host-share/..", "//host/"); EXPECT_RESOLVE("link-to-host-share/../missing", "//host/missing"); } { Resolver<Policies::CasePath> const r(os); EXPECT_RESOLVE("link-to-host-share", "C:/cwd/link-to-host-share"); EXPECT_RESOLVE("link-to-host-share/..", "C:/cwd"); EXPECT_RESOLVE("link-to-host-share/../missing", "C:/cwd/missing"); } return true; } #endif } int testPathResolver(int /*unused*/, char* /*unused*/[]) { return runTests({ posixRoot, posixAbsolutePath, posixWorkingDirectory, posixSymlink, #ifdef __APPLE__ macosActualCase, #endif #ifdef _WIN32 windowsRoot, windowsAbsolutePath, windowsActualCase, windowsWorkingDirectory, windowsWorkingDirectoryOnDrive, windowsNetworkShare, #endif }); }