From: Alexander Lanin Date: Mon, 28 Sep 2020 13:39:55 +0000 (+0200) Subject: Add unittest for find_compiler (#670) X-Git-Tag: v4.0~59 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=41fa24ab58d0b72e73e6eaf420833e700c9cbea8;p=thirdparty%2Fccache.git Add unittest for find_compiler (#670) Co-authored-by: Joel Rosdahl --- diff --git a/src/Config.hpp b/src/Config.hpp index 3397db9c2..bc8bb4fc9 100644 --- a/src/Config.hpp +++ b/src/Config.hpp @@ -79,6 +79,7 @@ public: void set_base_dir(const std::string& value); void set_cache_dir(const std::string& value); void set_cpp_extension(const std::string& value); + void set_compiler(const std::string& value); void set_depend_mode(bool value); void set_debug(bool value); void set_direct_mode(bool value); @@ -415,6 +416,12 @@ Config::set_cpp_extension(const std::string& value) m_cpp_extension = value; } +inline void +Config::set_compiler(const std::string& value) +{ + m_compiler = value; +} + inline void Config::set_depend_mode(bool value) { diff --git a/src/Util.cpp b/src/Util.cpp index 793507290..0a579952f 100644 --- a/src/Util.cpp +++ b/src/Util.cpp @@ -1239,13 +1239,14 @@ rename(const std::string& oldpath, const std::string& newpath) } bool -same_program_name(const std::string& program_name, - const std::string& canonical_program_name) +same_program_name(nonstd::string_view program_name, + nonstd::string_view canonical_program_name) { #ifdef _WIN32 std::string lowercase_program_name = Util::to_lowercase(program_name); return lowercase_program_name == canonical_program_name - || lowercase_program_name == (canonical_program_name + ".exe"); + || lowercase_program_name + == fmt::format("{}.exe", canonical_program_name); #else return program_name == canonical_program_name; #endif diff --git a/src/Util.hpp b/src/Util.hpp index d2c7bff8f..b3ab9e9bc 100644 --- a/src/Util.hpp +++ b/src/Util.hpp @@ -388,8 +388,8 @@ void rename(const std::string& oldpath, const std::string& newpath); // Detmine if `program_name` is equal to `canonical_program_name`. On Windows, // this means performing a case insensitive equality check with and without a // ".exe" suffix. On non-Windows, it is a simple equality check. -bool same_program_name(const std::string& program_name, - const std::string& canonical_program_name); +bool same_program_name(nonstd::string_view program_name, + nonstd::string_view canonical_program_name); // Send `text` to STDERR_FILENO, optionally stripping ANSI color sequences if // `ctx.args_info.strip_diagnostics_colors` is true and rewriting paths to diff --git a/src/ccache.cpp b/src/ccache.cpp index 1c6ea5c4a..d620ce9c0 100644 --- a/src/ccache.cpp +++ b/src/ccache.cpp @@ -1749,13 +1749,17 @@ from_cache(Context& ctx, FromCacheCallMode mode) : Statistic::preprocessed_cache_hit; } -// Find the real compiler. We just search the PATH to find an executable of the -// same name that isn't a link to ourselves. -static void -find_compiler(Context& ctx, const char* const* argv) +// Find the real compiler and put it into ctx.orig_args[0]. We just search the +// PATH to find an executable of the same name that isn't a link to ourselves. +// Pass find_executable function as second parameter. +void +find_compiler(Context& ctx, + const FindExecutableFunction& find_executable_function) { + const std::string orig_first_arg = ctx.orig_args[0]; + // We might be being invoked like "ccache gcc -c foo.c". - std::string base(Util::base_name(argv[0])); + std::string base(Util::base_name(ctx.orig_args[0])); if (Util::same_program_name(base, CCACHE_NAME)) { ctx.orig_args.pop_front(); if (Util::is_full_path(ctx.orig_args[0])) { @@ -1769,11 +1773,11 @@ find_compiler(Context& ctx, const char* const* argv) base = ctx.config.compiler(); } - std::string compiler = find_executable(ctx, base, CCACHE_NAME); + std::string compiler = find_executable_function(ctx, base, CCACHE_NAME); if (compiler.empty()) { throw Fatal("Could not find compiler \"{}\" in PATH", base); } - if (compiler == argv[0]) { + if (compiler == orig_first_arg) { throw Fatal( "Recursive invocation (the name of the ccache binary must be \"{}\")", CCACHE_NAME); @@ -2133,7 +2137,7 @@ cache_compilation(int argc, const char* const* argv) initialize(ctx, argc, argv); MTR_BEGIN("main", "find_compiler"); - find_compiler(ctx, argv); + find_compiler(ctx, &find_executable); MTR_END("main", "find_compiler"); try { diff --git a/src/ccache.hpp b/src/ccache.hpp index 70d397b52..627f623b6 100644 --- a/src/ccache.hpp +++ b/src/ccache.hpp @@ -21,6 +21,11 @@ #include "system.hpp" +#include +#include + +class Context; + extern const char CCACHE_VERSION[]; enum class GuessedCompiler { clang, gcc, nvcc, pump, unknown }; @@ -44,3 +49,12 @@ const uint32_t SLOPPY_CLANG_INDEX_STORE = 1 << 7; const uint32_t SLOPPY_LOCALE = 1 << 8; // Allow caching even if -fmodules is used. const uint32_t SLOPPY_MODULES = 1 << 9; + +using FindExecutableFunction = + std::function; + +// Tested by unit tests. +void find_compiler(Context& ctx, + const FindExecutableFunction& find_executable_function); diff --git a/unittest/CMakeLists.txt b/unittest/CMakeLists.txt index aa1fa8f52..9be4d8563 100644 --- a/unittest/CMakeLists.txt +++ b/unittest/CMakeLists.txt @@ -17,6 +17,7 @@ set( test_Util.cpp test_ZstdCompression.cpp test_argprocessing.cpp + test_ccache.cpp test_compopt.cpp test_hashutil.cpp) diff --git a/unittest/test_ccache.cpp b/unittest/test_ccache.cpp new file mode 100644 index 000000000..18b361f37 --- /dev/null +++ b/unittest/test_ccache.cpp @@ -0,0 +1,147 @@ +// Copyright (C) 2020 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 "Context.hpp" +#include "TestUtil.hpp" +#include "ccache.hpp" + +#include "third_party/doctest.h" +#include "third_party/nonstd/optional.hpp" + +#ifdef MYNAME +# define CCACHE_NAME MYNAME +#else +# define CCACHE_NAME "ccache" +#endif + +TEST_SUITE_BEGIN("ccache"); + +// Wraps find_compiler in a test friendly interface. +static std::string +helper(const char* args, + const char* config_compiler, + const char* find_executable_return_string = nullptr) +{ + const auto find_executable_stub = + [&find_executable_return_string]( + const Context&, const std::string& s, const std::string&) -> std::string { + return find_executable_return_string ? find_executable_return_string + : "resolved_" + s; + }; + + Context ctx; + ctx.config.set_compiler(config_compiler); + ctx.orig_args = Args::from_string(args); + find_compiler(ctx, find_executable_stub); + return ctx.orig_args.to_string(); +} + +TEST_CASE("find_compiler") +{ + SUBCASE("no config") + { + // In case the first parameter is gcc it must be a link to ccache, so + // find_compiler should call find_executable to locate the next best "gcc" + // and return that value. + CHECK(helper("gcc", "") == "resolved_gcc"); + CHECK(helper("relative/gcc", "") == "resolved_gcc"); + CHECK(helper("/absolute/gcc", "") == "resolved_gcc"); + + // In case the first parameter is ccache, resolve the second parameter to + // the real compiler unless it's a relative or absolute path. + CHECK(helper(CCACHE_NAME " gcc", "") == "resolved_gcc"); + CHECK(helper(CCACHE_NAME " rel/gcc", "") == "rel/gcc"); + CHECK(helper(CCACHE_NAME " /abs/gcc", "") == "/abs/gcc"); + + CHECK(helper("rel/" CCACHE_NAME " gcc", "") == "resolved_gcc"); + CHECK(helper("rel/" CCACHE_NAME " rel/gcc", "") == "rel/gcc"); + CHECK(helper("rel/" CCACHE_NAME " /abs/gcc", "") == "/abs/gcc"); + + CHECK(helper("/abs/" CCACHE_NAME " gcc", "") == "resolved_gcc"); + CHECK(helper("/abs/" CCACHE_NAME " rel/gcc", "") == "rel/gcc"); + CHECK(helper("/abs/" CCACHE_NAME " /abs/gcc", "") == "/abs/gcc"); + + // If gcc points back to ccache throw, unless either ccache or gcc is a + // relative or absolute path. If ccache *and* compiler have a relative or + // absolute path, call ccache from PATH. + CHECK_THROWS(helper(CCACHE_NAME " gcc", "", CCACHE_NAME)); + CHECK(helper(CCACHE_NAME " rel/gcc", "", CCACHE_NAME) == "rel/gcc"); + CHECK(helper(CCACHE_NAME " /abs/gcc", "", CCACHE_NAME) == "/abs/gcc"); + + CHECK(helper("rel/" CCACHE_NAME " gcc", "", CCACHE_NAME) == "ccache"); + CHECK(helper("rel/" CCACHE_NAME " rel/gcc", "", CCACHE_NAME) == "rel/gcc"); + CHECK(helper("rel/" CCACHE_NAME " /a/gcc", "", CCACHE_NAME) == "/a/gcc"); + + CHECK(helper("/abs/" CCACHE_NAME " gcc", "", CCACHE_NAME) == "ccache"); + CHECK(helper("/abs/" CCACHE_NAME " rel/gcc", "", CCACHE_NAME) == "rel/gcc"); + CHECK(helper("/abs/" CCACHE_NAME " /a/gcc", "", CCACHE_NAME) == "/a/gcc"); + + // If compiler is not found then throw, unless the compiler has a relative + // or absolute path. + CHECK_THROWS(helper(CCACHE_NAME " gcc", "", "")); + CHECK(helper(CCACHE_NAME " rel/gcc", "", "") == "rel/gcc"); + CHECK(helper(CCACHE_NAME " /abs/gcc", "", "") == "/abs/gcc"); + + CHECK_THROWS(helper("rel/" CCACHE_NAME " gcc", "", "")); + CHECK(helper("rel/" CCACHE_NAME " rel/gcc", "", "") == "rel/gcc"); + CHECK(helper("rel/" CCACHE_NAME " /abs/gcc", "", "") == "/abs/gcc"); + + CHECK_THROWS(helper("/abs/" CCACHE_NAME " gcc", "", "")); + CHECK(helper("/abs/" CCACHE_NAME " rel/gcc", "", "") == "rel/gcc"); + CHECK(helper("/abs/" CCACHE_NAME " /abs/gcc", "", "") == "/abs/gcc"); + } + + SUBCASE("config") + { + // In case the first parameter is gcc it must be a link to ccache so use + // config value instead. + CHECK(helper("gcc", "config") == "resolved_config"); + CHECK(helper("gcc", "rel/config") == "resolved_rel/config"); + CHECK(helper("gcc", "/abs/config") == "resolved_/abs/config"); + CHECK(helper("rel/gcc", "config") == "resolved_config"); + CHECK(helper("rel/gcc", "rel/config") == "resolved_rel/config"); + CHECK(helper("rel/gcc", "/abs/config") == "resolved_/abs/config"); + CHECK(helper("/abs/gcc", "config") == "resolved_config"); + CHECK(helper("/abs/gcc", "rel/config") == "resolved_rel/config"); + CHECK(helper("/abs/gcc", "/abs/config") == "resolved_/abs/config"); + + // In case the first parameter is ccache, use the configuration value unless + // the second parameter is a relative or absolute path. + CHECK(helper(CCACHE_NAME " gcc", "config") == "resolved_config"); + CHECK(helper(CCACHE_NAME " gcc", "rel/config") == "resolved_rel/config"); + CHECK(helper(CCACHE_NAME " gcc", "/abs/config") == "resolved_/abs/config"); + CHECK(helper(CCACHE_NAME " rel/gcc", "config") == "rel/gcc"); + CHECK(helper(CCACHE_NAME " /abs/gcc", "config") == "/abs/gcc"); + + // Same as above with relative path to ccache. + CHECK(helper("r/" CCACHE_NAME " gcc", "conf") == "resolved_conf"); + CHECK(helper("r/" CCACHE_NAME " gcc", "rel/conf") == "resolved_rel/conf"); + CHECK(helper("r/" CCACHE_NAME " gcc", "/abs/conf") == "resolved_/abs/conf"); + CHECK(helper("r/" CCACHE_NAME " rel/gcc", "conf") == "rel/gcc"); + CHECK(helper("r/" CCACHE_NAME " /abs/gcc", "conf") == "/abs/gcc"); + + // Same as above with absolute path to ccache. + CHECK(helper("/a/" CCACHE_NAME " gcc", "conf") == "resolved_conf"); + CHECK(helper("/a/" CCACHE_NAME " gcc", "rel/conf") == "resolved_rel/conf"); + CHECK(helper("/a/" CCACHE_NAME " gcc", "/a/conf") == "resolved_/a/conf"); + CHECK(helper("/a/" CCACHE_NAME " rel/gcc", "conf") == "rel/gcc"); + CHECK(helper("/a/" CCACHE_NAME " /abs/gcc", "conf") == "/abs/gcc"); + } +} + +TEST_SUITE_END();