diff --git a/lib/path.cpp b/lib/path.cpp index 8b0b75c8ca0..0f9e34dd554 100644 --- a/lib/path.cpp +++ b/lib/path.cpp @@ -358,16 +358,26 @@ bool Path::isHeader(const std::string &path) std::string Path::getAbsoluteFilePath(const std::string& filePath) { + if (filePath.empty()) + return ""; + std::string absolute_path; #ifdef _WIN32 char absolute[_MAX_PATH]; if (_fullpath(absolute, filePath.c_str(), _MAX_PATH)) absolute_path = absolute; + if (!absolute_path.empty() && absolute_path.back() == '\\') + absolute_path.pop_back(); #elif defined(__linux__) || defined(__sun) || defined(__hpux) || defined(__GNUC__) || defined(__CPPCHECK__) - char * absolute = realpath(filePath.c_str(), nullptr); + // simplify the path since any non-existent part has to exist even if discarded by ".." + std::string spath = Path::simplifyPath(filePath); + char * absolute = realpath(spath.c_str(), nullptr); if (absolute) absolute_path = absolute; free(absolute); + // only throw on realpath() fialure to resolve a path when the given one was non-existent + if (!spath.empty() && absolute_path.empty() && !exists(spath)) + throw std::runtime_error("path '" + filePath + "' does not exist"); #else #error Platform absolute path function needed #endif @@ -411,6 +421,12 @@ bool Path::isDirectory(const std::string &path) return file_type(path) == S_IFDIR; } +bool Path::exists(const std::string &path) +{ + const auto type = file_type(path); + return type == S_IFREG || type == S_IFDIR; +} + std::string Path::join(const std::string& path1, const std::string& path2) { if (path1.empty() || path2.empty()) return path1 + path2; diff --git a/lib/path.h b/lib/path.h index f3dd870af70..fda36c01f85 100644 --- a/lib/path.h +++ b/lib/path.h @@ -190,6 +190,13 @@ class CPPCHECKLIB Path { */ static bool isDirectory(const std::string &path); + /** + * @brief Checks if a given path exists (i.e. is a file or directory) + * @param path Path to be checked + * @return true if given path exists + */ + static bool exists(const std::string &path); + /** * join 2 paths with '/' separators */ diff --git a/test/testpath.cpp b/test/testpath.cpp index 7be1f9a2436..a51037c3081 100644 --- a/test/testpath.cpp +++ b/test/testpath.cpp @@ -49,6 +49,8 @@ class TestPath : public TestFixture { TEST_CASE(identifyWithCppProbe); TEST_CASE(is_header); TEST_CASE(simplifyPath); + TEST_CASE(getAbsolutePath); + TEST_CASE(exists); } void removeQuotationMarks() const { @@ -437,6 +439,101 @@ class TestPath : public TestFixture { ASSERT_EQUALS("//home/file.cpp", Path::simplifyPath("\\\\home\\test\\..\\file.cpp")); ASSERT_EQUALS("//file.cpp", Path::simplifyPath("\\\\home\\..\\test\\..\\file.cpp")); } + + void getAbsolutePath() const { + const std::string cwd = Path::getCurrentPath(); + + ScopedFile file("testabspath.txt", ""); + std::string expected = Path::toNativeSeparators(Path::join(cwd, "testabspath.txt")); + + ASSERT_EQUALS(expected, Path::getAbsoluteFilePath("testabspath.txt")); + ASSERT_EQUALS(expected, Path::getAbsoluteFilePath("./testabspath.txt")); + ASSERT_EQUALS(expected, Path::getAbsoluteFilePath(".\\testabspath.txt")); + ASSERT_EQUALS(expected, Path::getAbsoluteFilePath("test/../testabspath.txt")); + ASSERT_EQUALS(expected, Path::getAbsoluteFilePath("test\\..\\testabspath.txt")); + ASSERT_EQUALS(expected, Path::getAbsoluteFilePath("./test/../testabspath.txt")); + ASSERT_EQUALS(expected, Path::getAbsoluteFilePath(".\\test\\../testabspath.txt")); + + ASSERT_EQUALS(expected, Path::getAbsoluteFilePath(Path::join(cwd, "testabspath.txt"))); + + std::string cwd_up = Path::getPathFromFilename(cwd); + cwd_up.pop_back(); // remove trailing slash + ASSERT_EQUALS(cwd_up, Path::getAbsoluteFilePath(Path::join(cwd, ".."))); + ASSERT_EQUALS(cwd_up, Path::getAbsoluteFilePath(Path::join(cwd, "../"))); + ASSERT_EQUALS(cwd_up, Path::getAbsoluteFilePath(Path::join(cwd, "..\\"))); + ASSERT_EQUALS(cwd_up, Path::getAbsoluteFilePath(Path::join(cwd, "./../"))); + ASSERT_EQUALS(cwd_up, Path::getAbsoluteFilePath(Path::join(cwd, ".\\..\\"))); + + ASSERT_EQUALS(cwd, Path::getAbsoluteFilePath(".")); +#ifndef _WIN32 + TODO_ASSERT_EQUALS(cwd, "", Path::getAbsoluteFilePath("./")); + TODO_ASSERT_EQUALS(cwd, "", Path::getAbsoluteFilePath(".\\")); +#else + ASSERT_EQUALS(cwd, Path::getAbsoluteFilePath("./")); + ASSERT_EQUALS(cwd, Path::getAbsoluteFilePath(".\\")); +#endif + + ASSERT_EQUALS("", Path::getAbsoluteFilePath("")); + +#ifndef _WIN32 + // the underlying realpath() call only returns something if the path actually exists + ASSERT_THROW_EQUALS_2(Path::getAbsoluteFilePath("testabspath2.txt"), std::runtime_error, "path 'testabspath2.txt' does not exist"); +#else + ASSERT_EQUALS(Path::toNativeSeparators(Path::join(cwd, "testabspath2.txt")), Path::getAbsoluteFilePath("testabspath2.txt")); +#endif + +#ifdef _WIN32 + // determine an existing drive letter + std::string drive = Path::getCurrentPath().substr(0, 2); + ASSERT_EQUALS(drive + "", Path::getAbsoluteFilePath(drive + "\\")); + ASSERT_EQUALS(drive + "\\path", Path::getAbsoluteFilePath(drive + "\\path")); + ASSERT_EQUALS(drive + "\\path", Path::getAbsoluteFilePath(drive + "\\path\\")); + ASSERT_EQUALS(drive + "\\path\\files.txt", Path::getAbsoluteFilePath(drive + "\\path\\files.txt")); + ASSERT_EQUALS(drive + "", Path::getAbsoluteFilePath(drive + "//")); + ASSERT_EQUALS(drive + "\\path", Path::getAbsoluteFilePath(drive + "//path")); + ASSERT_EQUALS(drive + "\\path", Path::getAbsoluteFilePath(drive + "//path/")); + ASSERT_EQUALS(drive + "\\path\\files.txt", Path::getAbsoluteFilePath(drive + "//path/files.txt")); + ASSERT_EQUALS(drive + "\\path", Path::getAbsoluteFilePath(drive + "\\\\path")); + ASSERT_EQUALS(drive + "", Path::getAbsoluteFilePath(drive + "/")); + ASSERT_EQUALS(drive + "\\path", Path::getAbsoluteFilePath(drive + "/path")); + + drive[0] = static_cast(toupper(drive[0])); + ASSERT_EQUALS(drive + "\\path", Path::getAbsoluteFilePath(drive + "\\path")); + ASSERT_EQUALS(drive + "\\path", Path::getAbsoluteFilePath(drive + "/path")); + + drive[0] = static_cast(tolower(drive[0])); + ASSERT_EQUALS(drive + "\\path", Path::getAbsoluteFilePath(drive + "\\path")); + ASSERT_EQUALS(drive + "\\path", Path::getAbsoluteFilePath(drive + "/path")); + + ASSERT_EQUALS("1:\\path\\files.txt", Path::getAbsoluteFilePath("1:\\path\\files.txt")); // treated as valid drive + ASSERT_EQUALS( + Path::toNativeSeparators(Path::join(Path::getCurrentPath(), "CC:\\path\\files.txt")), + Path::getAbsoluteFilePath("CC:\\path\\files.txt")); // treated as filename + ASSERT_EQUALS("1:\\path\\files.txt", Path::getAbsoluteFilePath("1:/path/files.txt")); // treated as valid drive + ASSERT_EQUALS( + Path::toNativeSeparators(Path::join(Path::getCurrentPath(), "CC:\\path\\files.txt")), + Path::getAbsoluteFilePath("CC:/path/files.txt")); // treated as filename +#endif + +#ifndef _WIN32 + ASSERT_THROW_EQUALS_2(Path::getAbsoluteFilePath("C:\\path\\files.txt"), std::runtime_error, "path 'C:\\path\\files.txt' does not exist"); +#endif + + // TODO: test UNC paths + // TODO: test with symlinks + } + + void exists() const { + ScopedFile file("testpath.txt", "", "testpath"); + ScopedFile file2("testpath2.txt", ""); + ASSERT_EQUALS(true, Path::exists("testpath")); + ASSERT_EQUALS(true, Path::exists("testpath/testpath.txt")); + ASSERT_EQUALS(true, Path::exists("testpath2.txt")); + + ASSERT_EQUALS(false, Path::exists("testpath2")); + ASSERT_EQUALS(false, Path::exists("testpath/testpath2.txt")); + ASSERT_EQUALS(false, Path::exists("testpath.txt")); + } }; REGISTER_TEST(TestPath)