// Copyright (C) 2011-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/Util.hpp" #include "../src/fmtmacros.hpp" #include "TestUtil.hpp" #include #include #include "third_party/doctest.h" #include "third_party/fmt/core.h" #include #include #include using doctest::Approx; using TestUtil::TestContext; TEST_SUITE_BEGIN("Config"); TEST_CASE("Config: default values") { Config config; CHECK(config.base_dir().empty()); CHECK(config.cache_dir().empty()); // Set later CHECK(config.compiler().empty()); CHECK(config.compiler_check() == "mtime"); CHECK(config.compiler_type() == CompilerType::auto_guess); CHECK(config.compression()); CHECK(config.compression_level() == 0); CHECK(config.cpp_extension().empty()); CHECK(!config.debug()); CHECK(config.debug_dir().empty()); CHECK(!config.depend_mode()); CHECK(config.direct_mode()); CHECK(!config.disable()); CHECK(config.extra_files_to_hash().empty()); CHECK(!config.file_clone()); CHECK(!config.hard_link()); CHECK(config.hash_dir()); CHECK(config.ignore_headers_in_manifest().empty()); CHECK(config.ignore_options().empty()); CHECK(config.inode_cache()); CHECK_FALSE(config.keep_comments_cpp()); CHECK(config.log_file().empty()); CHECK(config.max_files() == 0); CHECK(config.max_size() == static_cast(5) * 1024 * 1024 * 1024); CHECK(config.msvc_dep_prefix() == "Note: including file:"); CHECK(config.path().empty()); CHECK_FALSE(config.pch_external_checksum()); CHECK(config.prefix_command().empty()); CHECK(config.prefix_command_cpp().empty()); CHECK_FALSE(config.read_only()); CHECK_FALSE(config.read_only_direct()); CHECK_FALSE(config.recache()); CHECK_FALSE(config.remote_only()); CHECK(config.remote_storage().empty()); CHECK_FALSE(config.reshare()); CHECK(config.run_second_cpp()); CHECK(config.sloppiness().to_bitmask() == 0); CHECK(config.stats()); CHECK(config.temporary_dir().empty()); // Set later CHECK(config.umask() == std::nullopt); } TEST_CASE("Config::update_from_file") { TestContext test_context; const char user[] = "rabbit"; Util::setenv("USER", user); #ifndef _WIN32 std::string base_dir = FMT("/{0}/foo/{0}", user); #else std::string base_dir = FMT("C:/{0}/foo/{0}", user); #endif util::write_file( "ccache.conf", "base_dir = " + base_dir + "\n" "cache_dir=\n" "cache_dir = $USER$/${USER}/.ccache\n" "\n" "\n" " #A comment\n" "\t compiler = foo\n" "compiler_check = none\n" "compiler_type = nvcc\n" "compression=false\n" "compression_level= 2\n" "cpp_extension = .foo\n" "depend_mode = true\n" "direct_mode = false\n" "disable = true\n" "extra_files_to_hash = a:b c:$USER\n" "file_clone = true\n" "hard_link = true\n" "hash_dir = false\n" "ignore_headers_in_manifest = a:b/c\n" "ignore_options = -a=* -b\n" "keep_comments_cpp = true\n" "log_file = $USER${USER} \n" "max_files = 17\n" "max_size = 123M\n" "msvc_dep_prefix = Some other prefix:\n" "path = $USER.x\n" "pch_external_checksum = true\n" "prefix_command = x$USER\n" "prefix_command_cpp = y\n" "read_only = true\n" "read_only_direct = true\n" "recache = true\n" "reshare = true\n" "run_second_cpp = false\n" "sloppiness = time_macros ,include_file_mtime" " include_file_ctime,file_stat_matches,file_stat_matches_ctime,pch_defines" " , no_system_headers,system_headers,clang_index_store,ivfsoverlay," " gcno_cwd,\n" "stats = false\n" "temporary_dir = ${USER}_foo\n" "umask = 777"); // Note: no newline. Config config; REQUIRE(config.update_from_file("ccache.conf")); CHECK(config.base_dir() == base_dir); CHECK(config.cache_dir() == FMT("{0}$/{0}/.ccache", user)); CHECK(config.compiler() == "foo"); CHECK(config.compiler_check() == "none"); CHECK(config.compiler_type() == CompilerType::nvcc); CHECK_FALSE(config.compression()); CHECK(config.compression_level() == 2); CHECK(config.cpp_extension() == ".foo"); CHECK(config.depend_mode()); CHECK_FALSE(config.direct_mode()); CHECK(config.disable()); CHECK(config.extra_files_to_hash() == FMT("a:b c:{}", user)); CHECK(config.file_clone()); CHECK(config.hard_link()); CHECK_FALSE(config.hash_dir()); CHECK(config.ignore_headers_in_manifest() == "a:b/c"); CHECK(config.ignore_options() == "-a=* -b"); CHECK(config.keep_comments_cpp()); CHECK(config.log_file() == FMT("{0}{0}", user)); CHECK(config.max_files() == 17); CHECK(config.max_size() == 123 * 1000 * 1000); CHECK(config.msvc_dep_prefix() == "Some other prefix:"); CHECK(config.path() == FMT("{}.x", user)); CHECK(config.pch_external_checksum()); CHECK(config.prefix_command() == FMT("x{}", user)); CHECK(config.prefix_command_cpp() == "y"); CHECK(config.read_only()); CHECK(config.read_only_direct()); CHECK(config.recache()); CHECK(config.reshare()); CHECK_FALSE(config.run_second_cpp()); CHECK(config.sloppiness().to_bitmask() == (static_cast(core::Sloppy::clang_index_store) | static_cast(core::Sloppy::file_stat_matches) | static_cast(core::Sloppy::file_stat_matches_ctime) | static_cast(core::Sloppy::gcno_cwd) | static_cast(core::Sloppy::include_file_ctime) | static_cast(core::Sloppy::include_file_mtime) | static_cast(core::Sloppy::ivfsoverlay) | static_cast(core::Sloppy::pch_defines) | static_cast(core::Sloppy::system_headers) | static_cast(core::Sloppy::time_macros))); CHECK_FALSE(config.stats()); CHECK(config.temporary_dir() == FMT("{}_foo", user)); CHECK(config.umask() == 0777U); } TEST_CASE("Config::update_from_file, error handling") { TestContext test_context; Config config; SUBCASE("missing equal sign") { util::write_file("ccache.conf", "no equal sign"); REQUIRE_THROWS_WITH(config.update_from_file("ccache.conf"), "ccache.conf:1: missing equal sign"); } SUBCASE("unknown key") { util::write_file("ccache.conf", "# Comment\nfoo = bar"); CHECK(config.update_from_file("ccache.conf")); } SUBCASE("invalid bool") { util::write_file("ccache.conf", "disable="); REQUIRE_THROWS_WITH(config.update_from_file("ccache.conf"), "ccache.conf:1: not a boolean value: \"\""); util::write_file("ccache.conf", "disable=foo"); REQUIRE_THROWS_WITH(config.update_from_file("ccache.conf"), "ccache.conf:1: not a boolean value: \"foo\""); } SUBCASE("invalid variable reference") { util::write_file("ccache.conf", "base_dir = ${foo"); REQUIRE_THROWS_WITH( config.update_from_file("ccache.conf"), "ccache.conf:1: syntax error: missing '}' after \"foo\""); // Other cases tested in test_Util.c. } SUBCASE("empty umask") { util::write_file("ccache.conf", "umask = "); CHECK(config.update_from_file("ccache.conf")); CHECK(config.umask() == std::nullopt); } SUBCASE("invalid size") { util::write_file("ccache.conf", "max_size = foo"); REQUIRE_THROWS_WITH(config.update_from_file("ccache.conf"), "ccache.conf:1: invalid size: \"foo\""); // Other cases tested in test_Util.c. } SUBCASE("unknown sloppiness") { util::write_file("ccache.conf", "sloppiness = time_macros, foo"); CHECK(config.update_from_file("ccache.conf")); CHECK(config.sloppiness().to_bitmask() == static_cast(core::Sloppy::time_macros)); } SUBCASE("invalid unsigned") { util::write_file("ccache.conf", "max_files ="); REQUIRE_THROWS_WITH(config.update_from_file("ccache.conf"), "ccache.conf:1: invalid unsigned integer: \"\""); util::write_file("ccache.conf", "max_files = -42"); REQUIRE_THROWS_WITH(config.update_from_file("ccache.conf"), "ccache.conf:1: invalid unsigned integer: \"-42\""); util::write_file("ccache.conf", "max_files = foo"); REQUIRE_THROWS_WITH(config.update_from_file("ccache.conf"), "ccache.conf:1: invalid unsigned integer: \"foo\""); } SUBCASE("missing file") { CHECK(!config.update_from_file("ccache.conf")); } SUBCASE("relative base dir") { util::write_file("ccache.conf", "base_dir = relative/path"); REQUIRE_THROWS_WITH( config.update_from_file("ccache.conf"), "ccache.conf:1: not an absolute path: \"relative/path\""); util::write_file("ccache.conf", "base_dir ="); CHECK(config.update_from_file("ccache.conf")); } } TEST_CASE("Config::update_from_environment") { Config config; Util::setenv("CCACHE_COMPRESS", "1"); config.update_from_environment(); CHECK(config.compression()); Util::unsetenv("CCACHE_COMPRESS"); Util::setenv("CCACHE_NOCOMPRESS", "1"); config.update_from_environment(); CHECK(!config.compression()); } TEST_CASE("Config::set_value_in_file") { TestContext test_context; Config config; SUBCASE("set new value") { util::write_file("ccache.conf", "path = vanilla\n"); config.set_value_in_file("ccache.conf", "compiler", "chocolate"); std::string content = *util::read_file("ccache.conf"); CHECK(content == "path = vanilla\ncompiler = chocolate\n"); } SUBCASE("existing value") { util::write_file("ccache.conf", "path = chocolate\nstats = chocolate\n"); config.set_value_in_file("ccache.conf", "path", "vanilla"); std::string content = *util::read_file("ccache.conf"); CHECK(content == "path = vanilla\nstats = chocolate\n"); } SUBCASE("unknown option") { util::write_file("ccache.conf", "path = chocolate\nstats = chocolate\n"); try { config.set_value_in_file("ccache.conf", "foo", "bar"); CHECK(false); } catch (const core::Error& e) { CHECK(std::string(e.what()) == "unknown configuration option \"foo\""); } std::string content = *util::read_file("ccache.conf"); CHECK(content == "path = chocolate\nstats = chocolate\n"); } SUBCASE("unknown sloppiness") { util::write_file("ccache.conf", "path = vanilla\n"); config.set_value_in_file("ccache.conf", "sloppiness", "foo"); std::string content = *util::read_file("ccache.conf"); CHECK(content == "path = vanilla\nsloppiness = foo\n"); } SUBCASE("comments are kept") { util::write_file("ccache.conf", "# c1\npath = blueberry\n#c2\n"); config.set_value_in_file("ccache.conf", "path", "vanilla"); config.set_value_in_file("ccache.conf", "compiler", "chocolate"); std::string content = *util::read_file("ccache.conf"); CHECK(content == "# c1\npath = vanilla\n#c2\ncompiler = chocolate\n"); } } TEST_CASE("Config::get_string_value") { Config config; SUBCASE("base case") { config.set_max_files(42); CHECK(config.get_string_value("max_files") == "42"); } SUBCASE("unknown key") { try { config.get_string_value("foo"); CHECK(false); } catch (const core::Error& e) { CHECK(std::string(e.what()) == "unknown configuration option \"foo\""); } } } TEST_CASE("Config::visit_items") { TestContext test_context; util::write_file( "test.conf", "absolute_paths_in_stderr = true\n" #ifndef _WIN32 "base_dir = /bd\n" #else "base_dir = C:/bd\n" #endif "cache_dir = cd\n" "compiler = c\n" "compiler_check = cc\n" "compiler_type = clang\n" "compression = true\n" "compression_level = 8\n" "cpp_extension = ce\n" "debug = false\n" "debug_dir = /dd\n" "depend_mode = true\n" "direct_mode = false\n" "disable = true\n" "extra_files_to_hash = efth\n" "file_clone = true\n" "hard_link = true\n" "hash_dir = false\n" "ignore_headers_in_manifest = ihim\n" "ignore_options = -a=* -b\n" "inode_cache = false\n" "keep_comments_cpp = true\n" "log_file = lf\n" "max_files = 4711\n" "max_size = 98.7M\n" "msvc_dep_prefix = mdp\n" "namespace = ns\n" "path = p\n" "pch_external_checksum = true\n" "prefix_command = pc\n" "prefix_command_cpp = pcc\n" "read_only = true\n" "read_only_direct = true\n" "recache = true\n" "remote_only = true\n" "remote_storage = rs\n" "reshare = true\n" "run_second_cpp = false\n" "sloppiness = include_file_mtime, include_file_ctime, time_macros," " file_stat_matches, file_stat_matches_ctime, pch_defines, system_headers," " clang_index_store, ivfsoverlay, gcno_cwd \n" "stats = false\n" "stats_log = sl\n" "temporary_dir = td\n" "umask = 022\n"); Config config; config.update_from_file("test.conf"); std::vector received_items; config.visit_items( [&](const auto& key, const auto& value, const auto& origin) { received_items.push_back(FMT("({}) {} = {}", origin, key, value)); }); std::vector expected = { "(test.conf) absolute_paths_in_stderr = true", #ifndef _WIN32 "(test.conf) base_dir = /bd", #else "(test.conf) base_dir = C:/bd", #endif "(test.conf) cache_dir = cd", "(test.conf) compiler = c", "(test.conf) compiler_check = cc", "(test.conf) compiler_type = clang", "(test.conf) compression = true", "(test.conf) compression_level = 8", "(test.conf) cpp_extension = ce", "(test.conf) debug = false", "(test.conf) debug_dir = /dd", "(test.conf) depend_mode = true", "(test.conf) direct_mode = false", "(test.conf) disable = true", "(test.conf) extra_files_to_hash = efth", "(test.conf) file_clone = true", "(test.conf) hard_link = true", "(test.conf) hash_dir = false", "(test.conf) ignore_headers_in_manifest = ihim", "(test.conf) ignore_options = -a=* -b", "(test.conf) inode_cache = false", "(test.conf) keep_comments_cpp = true", "(test.conf) log_file = lf", "(test.conf) max_files = 4711", "(test.conf) max_size = 98.7 MB", "(test.conf) msvc_dep_prefix = mdp", "(test.conf) namespace = ns", "(test.conf) path = p", "(test.conf) pch_external_checksum = true", "(test.conf) prefix_command = pc", "(test.conf) prefix_command_cpp = pcc", "(test.conf) read_only = true", "(test.conf) read_only_direct = true", "(test.conf) recache = true", "(test.conf) remote_only = true", "(test.conf) remote_storage = rs", "(test.conf) reshare = true", "(test.conf) run_second_cpp = false", "(test.conf) sloppiness = clang_index_store, file_stat_matches," " file_stat_matches_ctime, gcno_cwd, include_file_ctime," " include_file_mtime, ivfsoverlay, pch_defines, system_headers," " time_macros", "(test.conf) stats = false", "(test.conf) stats_log = sl", "(test.conf) temporary_dir = td", "(test.conf) umask = 022", }; REQUIRE(received_items.size() == expected.size()); for (size_t i = 0; i < expected.size(); ++i) { CHECK(received_items[i] == expected[i]); } } TEST_CASE("Check key tables consistency") { CHECK_NOTHROW(Config::check_key_tables_consistency()); }