From 17c7c8e1bbb076fe37192dfb9eab7baa80f401fd Mon Sep 17 00:00:00 2001 From: Matthias Koefferlein Date: Tue, 3 Jul 2018 23:43:57 +0200 Subject: [PATCH] Some file utils tests added (file name decomposition, fake Windows/Linux tests) --- src/tl/tl/tlFileUtils.cc | 308 +++++++++++++++++-------------- src/tl/tl/tlFileUtils.h | 8 +- src/tl/unit_tests/tlFileUtils.cc | 133 +++++++++++++ 3 files changed, 313 insertions(+), 136 deletions(-) diff --git a/src/tl/tl/tlFileUtils.cc b/src/tl/tl/tlFileUtils.cc index a9f309e6d..b896ce8c6 100644 --- a/src/tl/tl/tlFileUtils.cc +++ b/src/tl/tl/tlFileUtils.cc @@ -34,31 +34,64 @@ namespace tl { +enum { OS_Auto, OS_Windows, OS_Linux } s_mode = OS_Auto; + +static bool is_win () +{ + if (s_mode == OS_Windows) { + return true; + } else if (s_mode == OS_Linux) { + return false; + } else { #if defined(_WIN32) + return true; +#else + return false; +#endif + } +} + +// Secret mode switchers for testing +TL_PUBLIC void file_utils_force_windows () { s_mode = OS_Windows; } +TL_PUBLIC void file_utils_force_linux () { s_mode = OS_Linux; } +TL_PUBLIC void file_utils_force_reset () { s_mode = OS_Auto; } + + static bool is_drive (const std::string &part) { - return (part.size () == 2 && isletter (part[0]) && part[1] == ':'); + return is_win () && (part.size () == 2 && isalpha (part[0]) && part[1] == ':'); } -#else -static bool is_drive (const std::string &) + +static std::string normalized_part (const std::string &part) { - return false; + if (! is_win ()) { + return part; + } + + std::string p; + p.reserve (part.size ()); + const char *cp = part.c_str (); + while (*cp == '\\' || *cp == '/') { + p += '\\'; + ++cp; + } + p += cp; + return p; } -#endif static std::string trimmed_part (const std::string &part) { const char *cp = part.c_str (); -#if defined(_WIN32) - while (*cp == '\\' || *cp == '/') { - ++cp; + if (is_win ()) { + while (*cp == '\\' || *cp == '/') { + ++cp; + } + } else { + while (*cp == '/') { + ++cp; + } } -#else - while (*cp == '/') { - ++cp; - } -#endif return std::string (cp); } @@ -66,88 +99,92 @@ static std::string trimmed_part (const std::string &part) static bool is_part_with_separator (const std::string &part) { const char *cp = part.c_str (); -#if defined(_WIN32) - return (*cp == '\\' || *cp == '/'); -#else - return (*cp == '/'); -#endif + if (is_win ()) { + return (*cp == '\\' || *cp == '/'); + } else { + return (*cp == '/'); + } } -std::vector split_path (const std::string &p) +std::vector split_path (const std::string &p, bool keep_last) { std::vector parts; -#if defined(_WIN32) + bool first = true; - const char *cp = p.c_str (); - if (*cp && isletter (*cp) && cp[1] == ':') { + if (is_win ()) { - // drive name - parts.push_back (std::string ()); - parts.back () += toupper (*cp); - parts.back () += ":"; + const char *cp = p.c_str (); + if (*cp && isalpha (*cp) && cp[1] == ':') { - cp += 2; + // drive name + parts.push_back (std::string ()); + parts.back () += toupper (*cp); + parts.back () += ":"; - } else if ((*cp == '\\' && cp[1] == '\\') || (*cp == '/' && cp[1] == '/')) { + cp += 2; - // UNC server name - const char *cp0 = cp; - cp += 2; - while (*cp && *cp != '\\' && *cp != '/') { - ++cp; - } - parts.push_back (std::string (cp0, 0, cp - cp0)); + } else if ((*cp == '\\' && cp[1] == '\\') || (*cp == '/' && cp[1] == '/')) { - } - - while (*cp) { - - const char *cp0 = cp; - bool any = false; - while (*cp && (!any || (*cp != '\\' && *cp != '/'))) { - if (*cp != '\\' && *cp != '/') { - any = true; - } else { - cp0 = cp; - } - ++cp; - } - - if (any) { - parts.push_back (std::string (cp0, 0, cp - cp0)); - } - - } - -#else - - const char *cp = p.c_str (); - while (*cp) { - - const char *cp0 = cp; - bool any = false; - while (*cp && (!any || *cp != '/')) { - if (*cp != '/') { - any = true; - } else { - cp0 = cp; - } - // backslash escape - if (*cp == '\\' && cp[1]) { + // UNC server name + const char *cp0 = cp; + cp += 2; + while (*cp && *cp != '\\' && *cp != '/') { ++cp; } - ++cp; + parts.push_back (tl::normalized_part (std::string (cp0, 0, cp - cp0))); + } - if (any) { - parts.push_back (std::string (cp0, 0, cp - cp0)); + while (*cp) { + + const char *cp0 = cp; + bool any = false; + while (*cp && (!any || (*cp != '\\' && *cp != '/'))) { + if (*cp != '\\' && *cp != '/') { + any = true; + } else { + cp0 = cp; + } + ++cp; + } + + if (any || first || keep_last) { + first = false; + parts.push_back (tl::normalized_part (std::string (cp0, 0, cp - cp0))); + } + + } + + } else { + + const char *cp = p.c_str (); + while (*cp) { + + const char *cp0 = cp; + bool any = false; + while (*cp && (!any || *cp != '/')) { + if (*cp != '/') { + any = true; + } else { + cp0 = cp; + } + // backslash escape + if (*cp == '\\' && cp[1]) { + ++cp; + } + ++cp; + } + + if (any || first || keep_last) { + first = false; + parts.push_back (std::string (cp0, 0, cp - cp0)); + } + } } -#endif - return parts; } @@ -179,6 +216,61 @@ static std::vector split_filename (const std::string &fn) return parts; } +std::string normalize_path (const std::string &s) +{ + return tl::join (tl::split_path (s), ""); +} + +std::string combine_path (const std::string &p1, const std::string &p2, bool always_join) +{ + if (! always_join && p2.empty ()) { + return p1; + } else if (is_win ()) { + return p1 + "\\" + p2; + } else { + return p1 + "/" + p2; + } +} + +std::string dirname (const std::string &s) +{ + std::vector parts = split_path (s, true /*keep last part*/); + if (parts.size () > 0) { + parts.pop_back (); + } + + return tl::join (parts, ""); +} + +std::string filename (const std::string &s) +{ + std::vector parts = split_path (s, true /*keep last part*/); + if (parts.size () > 0) { + return trimmed_part (parts.back ()); + } else { + return std::string (); + } +} + +std::string basename (const std::string &s) +{ + std::vector fnp = split_filename (filename (s)); + if (fnp.size () > 0) { + return fnp.front (); + } else { + return std::string (); + } +} + +std::string extension (const std::string &s) +{ + std::vector fnp = split_filename (filename (s)); + if (fnp.size () > 0) { + fnp.erase (fnp.begin ()); + } + return tl::join (fnp, "."); +} + bool is_parent_path (const std::string &parent, const std::string &path) { @@ -198,7 +290,7 @@ is_parent_path (const std::string &parent, const std::string &path) } // We did not find a match - now maybe the parent is root - return (is_same_file (parent, tl::combine_path (tl::join (parts, ""), ""))); + return (is_same_file (parent, tl::combine_path (tl::join (parts, ""), "", true /*always add slash*/))); } std::vector dir_entries (const std::string &s, bool with_files, bool with_dirs, bool without_dotfiles) @@ -315,6 +407,11 @@ bool rm_dir_recursive (const std::string &p) std::vector entries; std::string path = tl::absolute_file_path (p); + if (! tl::file_exists (path)) { + // already gone. + return true; + } + entries = dir_entries (path, false /*without_files*/, true /*with_dirs*/); for (std::vector::const_iterator e = entries.begin (); e != entries.end (); ++e) { if (! rm_dir_recursive (tl::combine_path (path, *e))) { @@ -518,45 +615,6 @@ std::string absolute_file_path (const std::string &s) } } -std::string dirname (const std::string &s) -{ - std::vector parts = split_path (s); - if (parts.size () > 0) { - parts.pop_back (); - } - - return tl::join (parts, ""); -} - -std::string filename (const std::string &s) -{ - std::vector parts = split_path (s); - if (parts.size () > 0) { - return trimmed_part (parts.back ()); - } else { - return std::string (); - } -} - -std::string basename (const std::string &s) -{ - std::vector fnp = split_filename (filename (s)); - if (fnp.size () > 0) { - return fnp.front (); - } else { - return std::string (); - } -} - -std::string extension (const std::string &s) -{ - std::vector fnp = split_filename (filename (s)); - if (fnp.size () > 0) { - fnp.erase (fnp.begin ()); - } - return tl::join (fnp, "."); -} - static int stat_func (const std::string &s, struct stat &st) { #if defined(_WIN32) @@ -608,24 +666,6 @@ std::string relative_path (const std::string &base, const std::string &p) return p; } -std::string normalize_path (const std::string &s) -{ - return tl::join (tl::split_path (s), ""); -} - -std::string combine_path (const std::string &p1, const std::string &p2) -{ - if (p2.empty ()) { - return p1; - } else { -#if defined(_WIN32) - return p1 + "\\" + p2; -#else - return p1 + "/" + p2; -#endif - } -} - bool is_same_file (const std::string &a, const std::string &b) { diff --git a/src/tl/tl/tlFileUtils.h b/src/tl/tl/tlFileUtils.h index cb8e0db80..648354132 100644 --- a/src/tl/tl/tlFileUtils.h +++ b/src/tl/tl/tlFileUtils.h @@ -140,8 +140,10 @@ std::string TL_PUBLIC normalize_path (const std::string &s); /** * @brief Combines the two path components into one path + * If "always_join" is true, the path is also built if p2 is empty. This will + * essentially add a slash or backslash to p1. */ -std::string TL_PUBLIC combine_path (const std::string &p1, const std::string &p2); +std::string TL_PUBLIC combine_path (const std::string &p1, const std::string &p2, bool always_join = false); /** * @brief Gets the current directory @@ -156,8 +158,10 @@ std::string TL_PUBLIC current_dir (); * parts will render the original path. A trailing empty element is * added if the path terminates with a separator (like "C:\" or "/home/user/"). * The idea is that the last element is the file name part. + * If "keep_last" is true, the last part will be kept even if it's empty. + * With this, a path like "/hello/" becomes "/hello"+"/". */ -std::vector split_path (const std::string &p); +std::vector TL_PUBLIC split_path (const std::string &p, bool keep_last = false); } diff --git a/src/tl/unit_tests/tlFileUtils.cc b/src/tl/unit_tests/tlFileUtils.cc index 1b6e385ed..1d21a2d23 100644 --- a/src/tl/unit_tests/tlFileUtils.cc +++ b/src/tl/unit_tests/tlFileUtils.cc @@ -166,4 +166,137 @@ TEST (3) } } +// Secret mode switchers for testing +namespace tl +{ +TL_PUBLIC void file_utils_force_windows (); +TL_PUBLIC void file_utils_force_linux (); +TL_PUBLIC void file_utils_force_reset (); +} + +// Fake Windows-tests +TEST (10) +{ + tl::file_utils_force_windows (); + try { + + EXPECT_EQ (tl::join (tl::split_path ("\\hello\\world"), "+"), "\\hello+\\world"); + EXPECT_EQ (tl::join (tl::split_path ("\\hello\\\\world\\"), "+"), "\\hello+\\world"); + EXPECT_EQ (tl::join (tl::split_path ("hello\\\\world\\"), "+"), "hello+\\world"); + EXPECT_EQ (tl::join (tl::split_path ("\\\\SERVER\\hello\\world"), "+"), "\\\\SERVER+\\hello+\\world"); + EXPECT_EQ (tl::join (tl::split_path ("c:\\hello\\\\world\\"), "+"), "C:+\\hello+\\world"); + + // slashes are good too: + EXPECT_EQ (tl::join (tl::split_path ("/hello/world"), "+"), "\\hello+\\world"); + EXPECT_EQ (tl::join (tl::split_path ("/hello//world/"), "+"), "\\hello+\\world"); + EXPECT_EQ (tl::join (tl::split_path ("hello//world/"), "+"), "hello+\\world"); + EXPECT_EQ (tl::join (tl::split_path ("//SERVER/hello/world"), "+"), "\\\\SERVER+\\hello+\\world"); + EXPECT_EQ (tl::join (tl::split_path ("c:/hello//world/"), "+"), "C:+\\hello+\\world"); + + // boundary cases + EXPECT_EQ (tl::join (tl::split_path (""), "+"), ""); + EXPECT_EQ (tl::join (tl::split_path ("\\"), "+"), "\\"); + EXPECT_EQ (tl::join (tl::split_path ("/"), "+"), "\\"); + EXPECT_EQ (tl::join (tl::split_path ("d:"), "+"), "D:"); + EXPECT_EQ (tl::join (tl::split_path ("\\\\"), "+"), "\\\\"); + EXPECT_EQ (tl::join (tl::split_path ("//"), "+"), "\\\\"); + EXPECT_EQ (tl::join (tl::split_path ("d:\\"), "+"), "D:+\\"); + EXPECT_EQ (tl::join (tl::split_path ("d:\\\\"), "+"), "D:+\\"); + EXPECT_EQ (tl::join (tl::split_path ("d:/"), "+"), "D:+\\"); + EXPECT_EQ (tl::join (tl::split_path ("d://"), "+"), "D:+\\"); + + EXPECT_EQ (tl::dirname ("/hello/world"), "\\hello"); + EXPECT_EQ (tl::dirname ("\\hello\\world"), "\\hello"); + EXPECT_EQ (tl::dirname ("/hello//world/"), "\\hello\\world"); + EXPECT_EQ (tl::dirname ("\\hello\\\\world\\"), "\\hello\\world"); + EXPECT_EQ (tl::dirname ("hello//world/"), "hello\\world"); + EXPECT_EQ (tl::dirname ("hello\\\\world\\"), "hello\\world"); + EXPECT_EQ (tl::dirname ("\\\\SERVER\\hello\\world"), "\\\\SERVER\\hello"); + EXPECT_EQ (tl::dirname ("//SERVER/hello/world"), "\\\\SERVER\\hello"); + EXPECT_EQ (tl::dirname ("c:\\hello\\world"), "C:\\hello"); + EXPECT_EQ (tl::dirname ("c:\\hello\\\\world"), "C:\\hello"); + EXPECT_EQ (tl::dirname ("c:/hello//world"), "C:\\hello"); + EXPECT_EQ (tl::dirname ("c:/hello//world/"), "C:\\hello\\world"); + + EXPECT_EQ (tl::filename ("/hello/world"), "world"); + EXPECT_EQ (tl::filename ("\\hello\\world"), "world"); + EXPECT_EQ (tl::filename ("/hello//world/"), ""); + EXPECT_EQ (tl::filename ("\\hello\\\\world\\"), ""); + EXPECT_EQ (tl::filename ("hello//world/"), ""); + EXPECT_EQ (tl::filename ("hello\\\\world\\"), ""); + EXPECT_EQ (tl::filename ("\\\\SERVER\\hello\\world"), "world"); + EXPECT_EQ (tl::filename ("//SERVER/hello/world"), "world"); + EXPECT_EQ (tl::filename ("c:\\hello\\world"), "world"); + EXPECT_EQ (tl::filename ("c:\\hello\\\\world"), "world"); + EXPECT_EQ (tl::filename ("c:/hello//world"), "world"); + EXPECT_EQ (tl::filename ("c:/hello//world/"), ""); + + EXPECT_EQ (tl::basename ("/hello/world"), "world"); + EXPECT_EQ (tl::basename ("/hello/world.tar"), "world"); + EXPECT_EQ (tl::basename ("/hello/world.tar.gz"), "world"); + EXPECT_EQ (tl::basename ("\\hello\\.world"), ".world"); + EXPECT_EQ (tl::basename ("\\hello\\.world.gz"), ".world"); + EXPECT_EQ (tl::basename ("/hello//world/"), ""); + + EXPECT_EQ (tl::extension ("/hello/world"), ""); + EXPECT_EQ (tl::extension ("/hello/world.tar"), "tar"); + EXPECT_EQ (tl::extension ("/hello/world.tar.gz"), "tar.gz"); + EXPECT_EQ (tl::extension ("\\hello\\.world"), ""); + EXPECT_EQ (tl::extension ("\\hello\\.world.gz"), "gz"); + EXPECT_EQ (tl::extension ("/hello//world/"), ""); + + tl::file_utils_force_reset (); + + } catch (...) { + tl::file_utils_force_reset (); + throw; + } +} + +// Fake Linux-tests +TEST (11) +{ + tl::file_utils_force_linux (); + try { + + EXPECT_EQ (tl::join (tl::split_path ("/hello/world"), "+"), "/hello+/world"); + EXPECT_EQ (tl::join (tl::split_path ("/hel\\/\\\\lo/world"), "+"), "/hel\\/\\\\lo+/world"); + EXPECT_EQ (tl::join (tl::split_path ("/hello//world/"), "+"), "/hello+/world"); + EXPECT_EQ (tl::join (tl::split_path ("hello//world/"), "+"), "hello+/world"); + + // boundary cases + EXPECT_EQ (tl::join (tl::split_path (""), "+"), ""); + EXPECT_EQ (tl::join (tl::split_path ("/"), "+"), "/"); + EXPECT_EQ (tl::join (tl::split_path ("//"), "+"), "/"); + + EXPECT_EQ (tl::dirname ("/hello/world"), "/hello"); + EXPECT_EQ (tl::dirname ("/hello//world/"), "/hello/world"); + EXPECT_EQ (tl::dirname ("hello//world/"), "hello/world"); + + EXPECT_EQ (tl::filename ("/hello/world"), "world"); + EXPECT_EQ (tl::filename ("/hello//world/"), ""); + EXPECT_EQ (tl::filename ("hello//world/"), ""); + + EXPECT_EQ (tl::basename ("/hello/world"), "world"); + EXPECT_EQ (tl::basename ("/hello/world.tar"), "world"); + EXPECT_EQ (tl::basename ("/hello/world.tar.gz"), "world"); + EXPECT_EQ (tl::basename ("/hello/.world"), ".world"); + EXPECT_EQ (tl::basename ("/hello/.world.gz"), ".world"); + EXPECT_EQ (tl::basename ("/hello//world/"), ""); + + EXPECT_EQ (tl::extension ("/hello/world"), ""); + EXPECT_EQ (tl::extension ("/hello///world.tar"), "tar"); + EXPECT_EQ (tl::extension ("/hello/world.tar.gz"), "tar.gz"); + EXPECT_EQ (tl::extension ("/hello//.world"), ""); + EXPECT_EQ (tl::extension ("/hello/.world.gz"), "gz"); + EXPECT_EQ (tl::extension ("/hello//world/"), ""); + + tl::file_utils_force_reset (); + + } catch (...) { + tl::file_utils_force_reset (); + throw; + } +} + #endif