// Copyright (C) 2019-2023 Joel Rosdahl and other contributors // // See doc/AUTHORS.adoc for a complete list of contributors. // // This program is free software; you can redistribute it and/or modify it // under the terms of the GNU General Public License as published by the Free // Software Foundation; either version 3 of the License, or (at your option) // any later version. // // This program is distributed in the hope that it will be useful, but WITHOUT // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or // FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for // more details. // // You should have received a copy of the GNU General Public License along with // this program; if not, write to the Free Software Foundation, Inc., 51 // Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA #include "../src/Config.hpp" #include "../src/Fd.hpp" #include "../src/Util.hpp" #include "../src/fmtmacros.hpp" #include "TestUtil.hpp" #include #include #include #include "third_party/doctest.h" #include #include #ifdef HAVE_UNISTD_H # include #endif #include using TestUtil::TestContext; TEST_SUITE_BEGIN("Util"); TEST_CASE("Util::base_name") { CHECK(Util::base_name("") == ""); CHECK(Util::base_name(".") == "."); CHECK(Util::base_name("foo") == "foo"); CHECK(Util::base_name("/") == ""); CHECK(Util::base_name("/foo") == "foo"); CHECK(Util::base_name("/foo/bar/f.txt") == "f.txt"); } TEST_CASE("Util::big_endian_to_int") { uint8_t bytes[8] = {0x70, 0x9e, 0x9a, 0xbc, 0xd6, 0x54, 0x4b, 0xca}; uint8_t uint8; Util::big_endian_to_int(bytes, uint8); CHECK(uint8 == 0x70); int8_t int8; Util::big_endian_to_int(bytes, int8); CHECK(int8 == 0x70); uint16_t uint16; Util::big_endian_to_int(bytes, uint16); CHECK(uint16 == 0x709e); int16_t int16; Util::big_endian_to_int(bytes, int16); CHECK(int16 == 0x709e); uint32_t uint32; Util::big_endian_to_int(bytes, uint32); CHECK(uint32 == 0x709e9abc); int32_t int32; Util::big_endian_to_int(bytes, int32); CHECK(int32 == 0x709e9abc); uint64_t uint64; Util::big_endian_to_int(bytes, uint64); CHECK(uint64 == 0x709e9abcd6544bca); int64_t int64; Util::big_endian_to_int(bytes, int64); CHECK(int64 == 0x709e9abcd6544bca); } TEST_CASE("Util::change_extension") { CHECK(Util::change_extension("", "") == ""); CHECK(Util::change_extension("x", "") == "x"); CHECK(Util::change_extension("", "x") == "x"); CHECK(Util::change_extension("", ".") == "."); CHECK(Util::change_extension(".", "") == ""); CHECK(Util::change_extension("...", "x") == "..x"); CHECK(Util::change_extension("abc", "def") == "abcdef"); CHECK(Util::change_extension("dot.", ".dot") == "dot.dot"); CHECK(Util::change_extension("foo.ext", "e2") == "fooe2"); CHECK(Util::change_extension("bar.txt", ".o") == "bar.o"); CHECK(Util::change_extension("foo.bar.txt", ".o") == "foo.bar.o"); } TEST_CASE("Util::common_dir_prefix_length") { CHECK(Util::common_dir_prefix_length("", "") == 0); CHECK(Util::common_dir_prefix_length("/", "") == 0); CHECK(Util::common_dir_prefix_length("", "/") == 0); CHECK(Util::common_dir_prefix_length("/", "/") == 0); CHECK(Util::common_dir_prefix_length("/", "/b") == 0); CHECK(Util::common_dir_prefix_length("/a", "/") == 0); CHECK(Util::common_dir_prefix_length("/a", "/b") == 0); CHECK(Util::common_dir_prefix_length("/a", "/a") == 2); CHECK(Util::common_dir_prefix_length("/a", "/a/b") == 2); CHECK(Util::common_dir_prefix_length("/a/b", "/a") == 2); CHECK(Util::common_dir_prefix_length("/a/b", "/a/c") == 2); CHECK(Util::common_dir_prefix_length("/a/b", "/a/b") == 4); CHECK(Util::common_dir_prefix_length("/a/bc", "/a/b") == 2); CHECK(Util::common_dir_prefix_length("/a/b", "/a/bc") == 2); } TEST_CASE("Util::create_dir") { TestContext test_context; CHECK(Util::create_dir("/")); CHECK(Util::create_dir("create/dir")); CHECK(Stat::stat("create/dir").is_directory()); util::write_file("create/dir/file", ""); CHECK(!Util::create_dir("create/dir/file")); } TEST_CASE("Util::dir_name") { CHECK(Util::dir_name("") == "."); CHECK(Util::dir_name(".") == "."); CHECK(Util::dir_name("foo") == "."); CHECK(Util::dir_name("/") == "/"); CHECK(Util::dir_name("/foo") == "/"); CHECK(Util::dir_name("/foo/bar/f.txt") == "/foo/bar"); #ifdef _WIN32 CHECK(Util::dir_name("C:/x/y") == "C:/x"); CHECK(Util::dir_name("X:/x/y") == "X:/x"); CHECK(Util::dir_name("C:\\x\\y") == "C:\\x"); CHECK(Util::dir_name("C:/x") == "C:/"); CHECK(Util::dir_name("C:\\x") == "C:\\"); CHECK(Util::dir_name("C:/") == "C:/"); CHECK(Util::dir_name("C:\\") == "C:\\"); #endif } TEST_CASE("Util::strip_ansi_csi_seqs") { const char input[] = "Normal," " \x1B[K\x1B[1mbold\x1B[m," " \x1B[31mred\x1B[m," " \x1B[1;32mbold green\x1B[m.\n"; CHECK(Util::strip_ansi_csi_seqs(input) == "Normal, bold, red, bold green.\n"); } TEST_CASE("Util::ensure_dir_exists") { TestContext test_context; CHECK_NOTHROW(Util::ensure_dir_exists("/")); CHECK_NOTHROW(Util::ensure_dir_exists("create/dir")); CHECK(Stat::stat("create/dir").is_directory()); util::write_file("create/dir/file", ""); CHECK_THROWS_WITH( Util::ensure_dir_exists("create/dir/file"), "Failed to create directory create/dir/file: Not a directory"); } TEST_CASE("Util::expand_environment_variables") { Util::setenv("FOO", "bar"); CHECK(Util::expand_environment_variables("") == ""); CHECK(Util::expand_environment_variables("$FOO") == "bar"); CHECK(Util::expand_environment_variables("$") == "$"); CHECK(Util::expand_environment_variables("$FOO $FOO:$FOO") == "bar bar:bar"); CHECK(Util::expand_environment_variables("x$FOO") == "xbar"); CHECK(Util::expand_environment_variables("${FOO}x") == "barx"); DOCTEST_GCC_SUPPRESS_WARNING_PUSH DOCTEST_GCC_SUPPRESS_WARNING("-Wunused-result") CHECK_THROWS_WITH( (void)Util::expand_environment_variables("$surelydoesntexist"), "environment variable \"surelydoesntexist\" not set"); CHECK_THROWS_WITH((void)Util::expand_environment_variables("${FOO"), "syntax error: missing '}' after \"FOO\""); DOCTEST_GCC_SUPPRESS_WARNING_POP } TEST_CASE("Util::fallocate") { TestContext test_context; const char filename[] = "test-file"; CHECK(Util::fallocate(Fd(creat(filename, S_IRUSR | S_IWUSR)).get(), 10000) == 0); CHECK(Stat::stat(filename).size() == 10000); CHECK( Util::fallocate(Fd(open(filename, O_RDWR, S_IRUSR | S_IWUSR)).get(), 5000) == 0); CHECK(Stat::stat(filename).size() == 10000); CHECK( Util::fallocate(Fd(open(filename, O_RDWR, S_IRUSR | S_IWUSR)).get(), 20000) == 0); CHECK(Stat::stat(filename).size() == 20000); } TEST_CASE("Util::format_argv_for_logging") { const char* argv_0[] = {nullptr}; CHECK(Util::format_argv_for_logging(argv_0) == ""); const char* argv_2[] = {"foo", "bar", nullptr}; CHECK(Util::format_argv_for_logging(argv_2) == "foo bar"); } TEST_CASE("Util::format_base16") { uint8_t none[] = ""; uint8_t text[4] = "foo"; // incl. NUL uint8_t data[4] = {0, 1, 2, 3}; CHECK(Util::format_base16(none, 0) == ""); CHECK(Util::format_base16(text, sizeof(text)) == "666f6f00"); CHECK(Util::format_base16(data, sizeof(data)) == "00010203"); } TEST_CASE("Util::format_base32hex") { // Test vectors (without padding) from RFC 4648. const uint8_t input[] = {'f', 'o', 'o', 'b', 'a', 'r'}; CHECK(Util::format_base32hex(input, 0) == ""); CHECK(Util::format_base32hex(input, 1) == "co"); CHECK(Util::format_base32hex(input, 2) == "cpng"); CHECK(Util::format_base32hex(input, 3) == "cpnmu"); CHECK(Util::format_base32hex(input, 4) == "cpnmuog"); CHECK(Util::format_base32hex(input, 5) == "cpnmuoj1"); CHECK(Util::format_base32hex(input, 6) == "cpnmuoj1e8"); } TEST_CASE("Util::get_extension") { CHECK(Util::get_extension("") == ""); CHECK(Util::get_extension(".") == "."); CHECK(Util::get_extension("...") == "."); CHECK(Util::get_extension("foo") == ""); CHECK(Util::get_extension("/") == ""); CHECK(Util::get_extension("/foo") == ""); CHECK(Util::get_extension("/foo/bar/f") == ""); CHECK(Util::get_extension("f.txt") == ".txt"); CHECK(Util::get_extension("f.abc.txt") == ".txt"); CHECK(Util::get_extension("/foo/bar/f.txt") == ".txt"); CHECK(Util::get_extension("/foo/bar/f.abc.txt") == ".txt"); } static inline std::string os_path(std::string path) { #if defined(_WIN32) && !defined(HAVE_DIRENT_H) std::replace(path.begin(), path.end(), '/', '\\'); #endif return path; } TEST_CASE("Util::get_relative_path") { #ifdef _WIN32 CHECK(Util::get_relative_path("C:/a", "C:/a") == "."); CHECK(Util::get_relative_path("C:/a", "Z:/a") == "Z:/a"); CHECK(Util::get_relative_path("C:/a/b", "C:/a") == ".."); CHECK(Util::get_relative_path("C:/a", "C:/a/b") == "b"); CHECK(Util::get_relative_path("C:/a", "C:/a/b/c") == "b/c"); CHECK(Util::get_relative_path("C:/a/b", "C:/a/c") == "../c"); CHECK(Util::get_relative_path("C:/a/b", "C:/a/c/d") == "../c/d"); CHECK(Util::get_relative_path("C:/a/b/c", "C:/a/c/d") == "../../c/d"); CHECK(Util::get_relative_path("C:/a/b", "C:/") == "../.."); CHECK(Util::get_relative_path("C:/a/b", "C:/c") == "../../c"); CHECK(Util::get_relative_path("C:/", "C:/a/b") == "a/b"); CHECK(Util::get_relative_path("C:/a", "D:/a/b") == "D:/a/b"); #else CHECK(Util::get_relative_path("/a", "/a") == "."); CHECK(Util::get_relative_path("/a/b", "/a") == ".."); CHECK(Util::get_relative_path("/a", "/a/b") == "b"); CHECK(Util::get_relative_path("/a", "/a/b/c") == "b/c"); CHECK(Util::get_relative_path("/a/b", "/a/c") == "../c"); CHECK(Util::get_relative_path("/a/b", "/a/c/d") == "../c/d"); CHECK(Util::get_relative_path("/a/b/c", "/a/c/d") == "../../c/d"); CHECK(Util::get_relative_path("/a/b", "/") == "../.."); CHECK(Util::get_relative_path("/a/b", "/c") == "../../c"); CHECK(Util::get_relative_path("/", "/a/b") == "a/b"); #endif } TEST_CASE("Util::hard_link") { TestContext test_context; SUBCASE("Link file to nonexistent destination") { util::write_file("old", "content"); CHECK_NOTHROW(Util::hard_link("old", "new")); CHECK(*util::read_file("new") == "content"); } SUBCASE("Link file to existing destination") { util::write_file("old", "content"); util::write_file("new", "other content"); CHECK_NOTHROW(Util::hard_link("old", "new")); CHECK(*util::read_file("new") == "content"); } SUBCASE("Link nonexistent file") { CHECK_THROWS_AS(Util::hard_link("old", "new"), core::Error); } } TEST_CASE("Util::int_to_big_endian") { uint8_t bytes[8]; uint8_t uint8 = 0x70; Util::int_to_big_endian(uint8, bytes); CHECK(bytes[0] == 0x70); int8_t int8 = 0x70; Util::int_to_big_endian(int8, bytes); CHECK(bytes[0] == 0x70); uint16_t uint16 = 0x709e; Util::int_to_big_endian(uint16, bytes); CHECK(bytes[0] == 0x70); CHECK(bytes[1] == 0x9e); int16_t int16 = 0x709e; Util::int_to_big_endian(int16, bytes); CHECK(bytes[0] == 0x70); CHECK(bytes[1] == 0x9e); uint32_t uint32 = 0x709e9abc; Util::int_to_big_endian(uint32, bytes); CHECK(bytes[0] == 0x70); CHECK(bytes[1] == 0x9e); CHECK(bytes[2] == 0x9a); CHECK(bytes[3] == 0xbc); int32_t int32 = 0x709e9abc; Util::int_to_big_endian(int32, bytes); CHECK(bytes[0] == 0x70); CHECK(bytes[1] == 0x9e); CHECK(bytes[2] == 0x9a); CHECK(bytes[3] == 0xbc); uint64_t uint64 = 0x709e9abcd6544bca; Util::int_to_big_endian(uint64, bytes); CHECK(bytes[0] == 0x70); CHECK(bytes[1] == 0x9e); CHECK(bytes[2] == 0x9a); CHECK(bytes[3] == 0xbc); CHECK(bytes[4] == 0xd6); CHECK(bytes[5] == 0x54); CHECK(bytes[6] == 0x4b); CHECK(bytes[7] == 0xca); int64_t int64 = 0x709e9abcd6544bca; Util::int_to_big_endian(int64, bytes); CHECK(bytes[0] == 0x70); CHECK(bytes[1] == 0x9e); CHECK(bytes[2] == 0x9a); CHECK(bytes[3] == 0xbc); CHECK(bytes[4] == 0xd6); CHECK(bytes[5] == 0x54); CHECK(bytes[6] == 0x4b); CHECK(bytes[7] == 0xca); } TEST_CASE("Util::is_absolute_path_with_prefix") { CHECK(*Util::is_absolute_path_with_prefix("-I/c/foo") == 2); CHECK(*Util::is_absolute_path_with_prefix("-W,path/c/foo") == 7); CHECK(!Util::is_absolute_path_with_prefix("-DMACRO")); #ifdef _WIN32 CHECK(*Util::is_absolute_path_with_prefix("-I/C:/foo") == 2); CHECK(*Util::is_absolute_path_with_prefix("-IC:/foo") == 2); CHECK(*Util::is_absolute_path_with_prefix("-W,path/c:/foo") == 7); CHECK(*Util::is_absolute_path_with_prefix("-W,pathc:/foo") == 7); CHECK(!Util::is_absolute_path_with_prefix("-opt:value")); #endif } TEST_CASE("Util::is_ccache_executable") { CHECK(Util::is_ccache_executable("ccache")); CHECK(Util::is_ccache_executable("ccache-1.2.3")); CHECK(!Util::is_ccache_executable("fooccache")); CHECK(!Util::is_ccache_executable("gcc")); #ifdef _WIN32 CHECK(Util::is_ccache_executable("CCACHE")); CHECK(Util::is_ccache_executable("CCACHE.exe")); CHECK(Util::is_ccache_executable("CCACHE-1.2.3")); CHECK(Util::is_ccache_executable("CCACHE.EXE")); CHECK(Util::is_ccache_executable("CCACHE-1.2.3.EXE")); #endif } TEST_CASE("Util::is_dir_separator") { CHECK(!Util::is_dir_separator('x')); CHECK(Util::is_dir_separator('/')); #ifdef _WIN32 CHECK(Util::is_dir_separator('\\')); #else CHECK(!Util::is_dir_separator('\\')); #endif } TEST_CASE("Util::make_relative_path") { using Util::make_relative_path; const TestContext test_context; const std::string cwd = Util::get_actual_cwd(); const std::string actual_cwd = FMT("{}/d", cwd); #ifdef _WIN32 const std::string apparent_cwd = actual_cwd; #else const std::string apparent_cwd = FMT("{}/s", cwd); #endif REQUIRE(Util::create_dir("d")); #ifndef _WIN32 REQUIRE(symlink("d", "s") == 0); #endif REQUIRE(chdir("d") == 0); Util::setenv("PWD", apparent_cwd); SUBCASE("No base directory") { CHECK(make_relative_path("", "/a", "/a", "/a/x") == "/a/x"); } SUBCASE("Path matches neither actual nor apparent CWD") { #ifdef _WIN32 CHECK(make_relative_path("C:/", "C:/a", "C:/b", "C:/x") == "C:/x"); #else CHECK(make_relative_path("/", "/a", "/b", "/x") == "/x"); #endif } SUBCASE("Match of actual CWD") { #ifdef _WIN32 CHECK( make_relative_path( actual_cwd.substr(0, 3), actual_cwd, apparent_cwd, actual_cwd + "/x") == "./x"); CHECK( make_relative_path( actual_cwd.substr(0, 3), actual_cwd, apparent_cwd, actual_cwd + "\\x") == ".\\x"); CHECK( make_relative_path( actual_cwd.substr(0, 3), actual_cwd, apparent_cwd, actual_cwd + "\\\\x") == ".\\x"); #else CHECK(make_relative_path("/", actual_cwd, apparent_cwd, actual_cwd + "/x") == "./x"); #endif } #ifndef _WIN32 SUBCASE("Match of apparent CWD") { CHECK(make_relative_path("/", actual_cwd, apparent_cwd, apparent_cwd + "/x") == "./x"); } SUBCASE("Match if using resolved (using realpath(3)) path") { CHECK(make_relative_path("/", actual_cwd, actual_cwd, apparent_cwd + "/x") == "./x"); } #endif } TEST_CASE("Util::matches_dir_prefix_or_file") { CHECK(!Util::matches_dir_prefix_or_file("", "")); CHECK(!Util::matches_dir_prefix_or_file("/", "")); CHECK(!Util::matches_dir_prefix_or_file("", "/")); CHECK(Util::matches_dir_prefix_or_file("aa", "aa")); CHECK(!Util::matches_dir_prefix_or_file("aaa", "aa")); CHECK(!Util::matches_dir_prefix_or_file("aa", "aaa")); CHECK(!Util::matches_dir_prefix_or_file("aa/", "aa")); CHECK(Util::matches_dir_prefix_or_file("/aa/bb", "/aa/bb")); CHECK(!Util::matches_dir_prefix_or_file("/aa/b", "/aa/bb")); CHECK(!Util::matches_dir_prefix_or_file("/aa/bbb", "/aa/bb")); CHECK(Util::matches_dir_prefix_or_file("/aa", "/aa/bb")); CHECK(Util::matches_dir_prefix_or_file("/aa/", "/aa/bb")); CHECK(!Util::matches_dir_prefix_or_file("/aa/bb", "/aa")); CHECK(!Util::matches_dir_prefix_or_file("/aa/bb", "/aa/")); #ifdef _WIN32 CHECK(Util::matches_dir_prefix_or_file("\\aa", "\\aa\\bb")); CHECK(Util::matches_dir_prefix_or_file("\\aa\\", "\\aa\\bb")); #else CHECK(!Util::matches_dir_prefix_or_file("\\aa", "\\aa\\bb")); CHECK(!Util::matches_dir_prefix_or_file("\\aa\\", "\\aa\\bb")); #endif } TEST_CASE("Util::normalize_abstract_absolute_path") { CHECK(Util::normalize_abstract_absolute_path("") == ""); CHECK(Util::normalize_abstract_absolute_path(".") == "."); CHECK(Util::normalize_abstract_absolute_path("..") == ".."); CHECK(Util::normalize_abstract_absolute_path("...") == "..."); CHECK(Util::normalize_abstract_absolute_path("x/./") == "x/./"); #ifdef _WIN32 CHECK(Util::normalize_abstract_absolute_path("c:/") == "c:/"); CHECK(Util::normalize_abstract_absolute_path("c:\\") == "c:/"); CHECK(Util::normalize_abstract_absolute_path("c:/.") == "c:/"); CHECK(Util::normalize_abstract_absolute_path("c:\\..") == "c:/"); CHECK(Util::normalize_abstract_absolute_path("c:\\x/..") == "c:/"); CHECK(Util::normalize_abstract_absolute_path("c:\\x/./y\\..\\\\z") == "c:/x/z"); #else CHECK(Util::normalize_abstract_absolute_path("/") == "/"); CHECK(Util::normalize_abstract_absolute_path("/.") == "/"); CHECK(Util::normalize_abstract_absolute_path("/..") == "/"); CHECK(Util::normalize_abstract_absolute_path("/./") == "/"); CHECK(Util::normalize_abstract_absolute_path("//") == "/"); CHECK(Util::normalize_abstract_absolute_path("/../x") == "/x"); CHECK(Util::normalize_abstract_absolute_path("/x/./y/z") == "/x/y/z"); CHECK(Util::normalize_abstract_absolute_path("/x/../y/z/") == "/y/z"); CHECK(Util::normalize_abstract_absolute_path("/x/.../y/z") == "/x/.../y/z"); CHECK(Util::normalize_abstract_absolute_path("/x/yyy/../zz") == "/x/zz"); CHECK(Util::normalize_abstract_absolute_path("//x/yyy///.././zz") == "/x/zz"); #endif } TEST_CASE("Util::normalize_concrete_absolute_path") { #ifndef _WIN32 TestContext test_context; util::write_file("file", ""); REQUIRE(Util::create_dir("dir1/dir2")); REQUIRE(symlink("dir1/dir2", "symlink") == 0); const auto cwd = Util::get_actual_cwd(); CHECK(Util::normalize_concrete_absolute_path(FMT("{}/file", cwd)) == FMT("{}/file", cwd)); CHECK(Util::normalize_concrete_absolute_path(FMT("{}/dir1/../file", cwd)) == FMT("{}/file", cwd)); CHECK(Util::normalize_concrete_absolute_path(FMT("{}/symlink/../file", cwd)) == FMT("{}/symlink/../file", cwd)); #endif } TEST_CASE("Util::parse_duration") { CHECK(Util::parse_duration("0s") == 0); CHECK(Util::parse_duration("2s") == 2); CHECK(Util::parse_duration("1d") == 3600 * 24); CHECK(Util::parse_duration("2d") == 2 * 3600 * 24); CHECK_THROWS_WITH( Util::parse_duration("-2"), "invalid suffix (supported: d (day) and s (second)): \"-2\""); CHECK_THROWS_WITH( Util::parse_duration("2x"), "invalid suffix (supported: d (day) and s (second)): \"2x\""); CHECK_THROWS_WITH( Util::parse_duration("2"), "invalid suffix (supported: d (day) and s (second)): \"2\""); } TEST_CASE("Util::remove_extension") { CHECK(Util::remove_extension("") == ""); CHECK(Util::remove_extension(".") == ""); CHECK(Util::remove_extension("...") == ".."); CHECK(Util::remove_extension("foo") == "foo"); CHECK(Util::remove_extension("/") == "/"); CHECK(Util::remove_extension("/foo") == "/foo"); CHECK(Util::remove_extension("/foo/bar/f") == "/foo/bar/f"); CHECK(Util::remove_extension("f.txt") == "f"); CHECK(Util::remove_extension("f.abc.txt") == "f.abc"); CHECK(Util::remove_extension("/foo/bar/f.txt") == "/foo/bar/f"); CHECK(Util::remove_extension("/foo/bar/f.abc.txt") == "/foo/bar/f.abc"); } // Util::split_into_strings and Util::split_into_views are tested implicitly in // test_util_Tokenizer.cpp. TEST_CASE("Util::to_lowercase") { CHECK(Util::to_lowercase("") == ""); CHECK(Util::to_lowercase("x") == "x"); CHECK(Util::to_lowercase("X") == "x"); CHECK(Util::to_lowercase(" x_X@") == " x_x@"); } TEST_CASE("Util::traverse") { TestContext test_context; REQUIRE(Util::create_dir("dir-with-subdir-and-file/subdir")); util::write_file("dir-with-subdir-and-file/subdir/f", ""); REQUIRE(Util::create_dir("dir-with-files")); util::write_file("dir-with-files/f1", ""); util::write_file("dir-with-files/f2", ""); REQUIRE(Util::create_dir("empty-dir")); std::vector visited; auto visitor = [&visited](const std::string& path, bool is_dir) { visited.push_back(FMT("[{}] {}", is_dir ? 'd' : 'f', path)); }; SUBCASE("traverse nonexistent path") { CHECK_THROWS_WITH( Util::traverse("nonexistent", visitor), "failed to open directory nonexistent: No such file or directory"); } SUBCASE("traverse file") { CHECK_NOTHROW(Util::traverse("dir-with-subdir-and-file/subdir/f", visitor)); REQUIRE(visited.size() == 1); CHECK(visited[0] == "[f] dir-with-subdir-and-file/subdir/f"); } SUBCASE("traverse empty directory") { CHECK_NOTHROW(Util::traverse("empty-dir", visitor)); REQUIRE(visited.size() == 1); CHECK(visited[0] == "[d] empty-dir"); } SUBCASE("traverse directory with files") { CHECK_NOTHROW(Util::traverse("dir-with-files", visitor)); REQUIRE(visited.size() == 3); std::string f1 = os_path("[f] dir-with-files/f1"); std::string f2 = os_path("[f] dir-with-files/f2"); CHECK(((visited[0] == f1 && visited[1] == f2) || (visited[0] == f2 && visited[1] == f1))); CHECK(visited[2] == "[d] dir-with-files"); } SUBCASE("traverse directory hierarchy") { CHECK_NOTHROW(Util::traverse("dir-with-subdir-and-file", visitor)); REQUIRE(visited.size() == 3); CHECK(visited[0] == os_path("[f] dir-with-subdir-and-file/subdir/f")); CHECK(visited[1] == os_path("[d] dir-with-subdir-and-file/subdir")); CHECK(visited[2] == "[d] dir-with-subdir-and-file"); } } TEST_CASE("Util::wipe_path") { TestContext test_context; SUBCASE("Wipe nonexistent path") { CHECK_NOTHROW(Util::wipe_path("a")); } SUBCASE("Wipe file") { util::write_file("a", ""); CHECK_NOTHROW(Util::wipe_path("a")); CHECK(!Stat::stat("a")); } SUBCASE("Wipe directory") { REQUIRE(Util::create_dir("a/b")); util::write_file("a/1", ""); util::write_file("a/b/1", ""); CHECK_NOTHROW(Util::wipe_path("a")); CHECK(!Stat::stat("a")); } SUBCASE("Wipe bad path") { #ifdef _WIN32 const char error[] = "failed to rmdir .: Permission denied"; #elif defined(_AIX) const char error[] = "failed to rmdir .: Device busy"; #else const char error[] = "failed to rmdir .: Invalid argument"; #endif CHECK_THROWS_WITH(Util::wipe_path("."), error); } } TEST_SUITE_END();