blob: 737f209b797a4e2b9f5448dc404e687416b5b5de [file] [log] [blame]
// Copyright 2018 The Goma Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "file_path_util.h"
#include "absl/strings/string_view.h"
#include "compiler_flag_type_specific.h"
#include "compiler_flags_parser.h"
#include "compiler_specific.h"
#include "glog/logging.h"
#include "gtest/gtest.h"
#include "path.h"
#include "unittest_util.h"
#include "util.h"
MSVC_PUSH_DISABLE_WARNING_FOR_PROTO()
#include "prototmp/goma_data.pb.h"
MSVC_POP_WARNING()
namespace devtools_goma {
namespace {
#ifdef _WIN32
constexpr absl::string_view kRootDir("C:\\");
#else
constexpr absl::string_view kRootDir("/");
#endif
#ifdef _WIN32
string LocateExecutable(const char* cwd_in,
const char* path_in,
const char* pathext_in,
const char* cmd_in) {
string path;
if (path_in == nullptr) {
path = devtools_goma::GetEnv("PATH");
CHECK(!path.empty());
} else {
path.assign(path_in);
}
string pathext;
if (pathext_in == nullptr) {
pathext = devtools_goma::GetEnv("PATHEXT");
CHECK(!pathext.empty());
} else {
pathext.assign(pathext_in);
}
string exec_path;
if (devtools_goma::GetRealExecutablePath(nullptr, cmd_in, cwd_in, path,
pathext, &exec_path, nullptr,
nullptr)) {
return exec_path;
}
return "";
}
#endif
} // namespace
TEST(FilePathUtil, GetRealExecutablePath) {
// TODO: write test for POSIX.
#ifdef _WIN32
string located = LocateExecutable("", nullptr, nullptr, "cmd");
EXPECT_GT(located.size(), 3UL);
// Shouls accept command with an extension.
located = LocateExecutable("", nullptr, nullptr, "cmd.exe");
EXPECT_GT(located.size(), 7UL);
// Should ignore case.
located = LocateExecutable("", nullptr, nullptr, "cmd.ExE");
EXPECT_GT(located.size(), 7UL);
// Not existing file.
located = LocateExecutable("", nullptr, nullptr, "shall_not_have_this_file");
EXPECT_TRUE(located.empty());
// Empty PATHEXT. Default pathext is used. i.e. it should not be empty.
located = LocateExecutable("", nullptr, "", "cmd");
EXPECT_GT(located.size(), 3UL);
// Strange PATHEXT. Nothing should match.
located = LocateExecutable("", nullptr, ".non_exist_pathext", "cmd");
EXPECT_TRUE(located.empty());
// Expected PATHEXT.
located = LocateExecutable("", nullptr, ".exe", "cmd");
EXPECT_GT(located.size(), 3UL);
// Expected PATHEXT with upper case letters.
located = LocateExecutable("", nullptr, ".EXE", "cmd");
EXPECT_GT(located.size(), 3UL);
// Unexpected PATHEXT.
located = LocateExecutable("", nullptr, ".com", "cmd");
EXPECT_TRUE(located.empty());
// Extension is not listed in PATHEXT. Nothing should match.
located = LocateExecutable("", nullptr, ".com", "cmd.exe");
EXPECT_TRUE(located.empty());
// Expected PATHEXT comes after unexpected PATHEXT.
located = LocateExecutable("", nullptr, ".com;.exe", "cmd");
EXPECT_GT(located.size(), 3UL);
// Expected PATHEXT comes after unexpected PATHEXT (upper case letters).
located = LocateExecutable("", nullptr, ".COM;.EXE", "cmd");
EXPECT_GT(located.size(), 3UL);
// Expected PATHEXT should be automatically added even if full-path given.
string expected = located;
string input = located.substr(0, located.length() - 4);
EXPECT_FALSE(input.empty());
located = LocateExecutable("", "", nullptr, input.c_str());
EXPECT_EQ(expected, located);
#endif
// TODO: revise this using TmpdirUtil.
}
TEST(FilePathUtilTest, IsLocalCompilerPathValidWithEmptyLocalCompilerPath) {
ExecReq req;
req.mutable_command_spec()->set_name("gcc");
req.mutable_command_spec()->set_version("1.2.3");
req.mutable_command_spec()->set_target("x86_64-linux-gnu");
req.mutable_command_spec()->set_binary_hash("deadbeef");
req.set_cwd("/tmp");
std::vector<string> args = {"gcc", "-c", "dummy.cc"};
std::unique_ptr<CompilerFlags> flag(CompilerFlagsParser::MustNew(args, "."));
EXPECT_TRUE(IsLocalCompilerPathValid("dummy", req, flag->compiler_name()));
}
TEST(FilePathUtilTest, IsLocalCompilerPathValidWithSameCommandSpec) {
ExecReq req;
req.mutable_command_spec()->set_name("gcc");
req.mutable_command_spec()->set_version("1.2.3");
req.mutable_command_spec()->set_target("x86_64-linux-gnu");
req.mutable_command_spec()->set_binary_hash("deadbeef");
req.set_cwd("/tmp");
req.mutable_command_spec()->set_local_compiler_path("/usr/bin/gcc");
std::vector<string> args = {"gcc", "-c", "dummy.cc"};
std::unique_ptr<CompilerFlags> flag(CompilerFlagsParser::MustNew(args, "."));
EXPECT_TRUE(IsLocalCompilerPathValid("dummy", req, flag->compiler_name()));
}
TEST(FilePathUtilTest, IsLocalCompilerPathValidWithUnknownName) {
ExecReq req;
req.mutable_command_spec()->set_name("gcc");
req.mutable_command_spec()->set_version("1.2.3");
req.mutable_command_spec()->set_target("x86_64-linux-gnu");
req.mutable_command_spec()->set_binary_hash("deadbeef");
req.set_cwd("/tmp");
req.mutable_command_spec()->set_local_compiler_path("/usr/bin/id");
std::vector<string> args = {"gcc", "-c", "dummy.cc"};
std::unique_ptr<CompilerFlags> flag(CompilerFlagsParser::MustNew(args, "."));
EXPECT_FALSE(IsLocalCompilerPathValid("dummy", req, flag->compiler_name()));
}
TEST(FilePathUtilTest, IsLocalCompilerPathValidWithCommandSpecMismatch) {
ExecReq req;
req.mutable_command_spec()->set_name("clang");
req.mutable_command_spec()->set_version("1.2.3");
req.mutable_command_spec()->set_target("x86_64-linux-gnu");
req.mutable_command_spec()->set_binary_hash("deadbeef");
req.set_cwd("/tmp");
req.mutable_command_spec()->set_local_compiler_path("/usr/bin/gcc");
std::vector<string> args = {"gcc", "-c", "dummy.cc"};
std::unique_ptr<CompilerFlags> flag(CompilerFlagsParser::MustNew(args, "."));
EXPECT_FALSE(IsLocalCompilerPathValid("dummy", req, flag->compiler_name()));
}
TEST(FilePathUtilTest, IsLocalCompilerPathValidWithArgsMismatch) {
ExecReq req;
req.mutable_command_spec()->set_name("gcc");
req.mutable_command_spec()->set_version("1.2.3");
req.mutable_command_spec()->set_target("x86_64-linux-gnu");
req.mutable_command_spec()->set_binary_hash("deadbeef");
req.set_cwd("/tmp");
req.mutable_command_spec()->set_local_compiler_path("/usr/bin/gcc");
std::vector<string> args = {"clang", "-c", "dummy.cc"};
std::unique_ptr<CompilerFlags> flag(CompilerFlagsParser::MustNew(args, "."));
EXPECT_FALSE(IsLocalCompilerPathValid("dummy", req, flag->compiler_name()));
}
TEST(FilePathUtilTest, IsLocalCompilerPathValidWithSameCommandSpecClExe) {
ExecReq req;
req.mutable_command_spec()->set_name("cl.exe");
req.mutable_command_spec()->set_version("1.2.3");
req.mutable_command_spec()->set_target("x86");
req.mutable_command_spec()->set_binary_hash("deadbeef");
req.set_cwd("/tmp");
req.mutable_command_spec()->set_local_compiler_path("c:\\dummy\\cl.exe");
std::vector<string> args = {"c:\\dummy\\cl.exe", "/c", "dummy.cc"};
std::unique_ptr<CompilerFlags> flag(CompilerFlagsParser::MustNew(args, "."));
EXPECT_TRUE(IsLocalCompilerPathValid("dummy", req, flag->compiler_name()));
}
TEST(FilePathUtilTest, IsLocalCompilerPathValidWithOmittingExtension) {
ExecReq req;
req.mutable_command_spec()->set_name("cl.exe");
req.mutable_command_spec()->set_version("1.2.3");
req.mutable_command_spec()->set_target("x86");
req.mutable_command_spec()->set_binary_hash("deadbeef");
req.set_cwd("/tmp");
req.mutable_command_spec()->set_local_compiler_path("c:\\dummy\\cl");
std::vector<string> args = {"cl", "/c", "dummy.cc"};
std::unique_ptr<CompilerFlags> flag(CompilerFlagsParser::MustNew(args, "."));
EXPECT_TRUE(IsLocalCompilerPathValid("dummy", req, flag->compiler_name()));
}
TEST(FilePathUtilTest, IsLocalCompilerPathValidWithUnknownNameClExe) {
ExecReq req;
req.mutable_command_spec()->set_name("cl.exe");
req.mutable_command_spec()->set_version("1.2.3");
req.mutable_command_spec()->set_target("x86");
req.mutable_command_spec()->set_binary_hash("deadbeef");
req.set_cwd("/tmp");
req.mutable_command_spec()->set_local_compiler_path(
"c:\\dummy\\shutdown.exe");
std::vector<string> args = {"c:\\dummy\\cl.exe", "/c", "dummy.cc"};
std::unique_ptr<CompilerFlags> flag(CompilerFlagsParser::MustNew(args, "."));
EXPECT_FALSE(IsLocalCompilerPathValid("dummy", req, flag->compiler_name()));
}
TEST(FilePathUtilTest, IsLocalCompilerPathValidWithCommandSpecMismatchClExe) {
ExecReq req;
req.mutable_command_spec()->set_name("clang-cl");
req.mutable_command_spec()->set_version("1.2.3");
req.mutable_command_spec()->set_target("x86");
req.mutable_command_spec()->set_binary_hash("deadbeef");
req.set_cwd("/tmp");
req.mutable_command_spec()->set_local_compiler_path("c:\\dummy\\cl.exe");
std::vector<string> args = {"c:\\dummy\\cl.exe", "/c", "dummy.cc"};
std::unique_ptr<CompilerFlags> flag(CompilerFlagsParser::MustNew(args, "."));
EXPECT_FALSE(IsLocalCompilerPathValid("dummy", req, flag->compiler_name()));
}
TEST(FilePathUtilTest, IsLocalCompilerPathValidWithArgsMismatchClExe) {
ExecReq req;
req.mutable_command_spec()->set_name("cl.exe");
req.mutable_command_spec()->set_version("1.2.3");
req.mutable_command_spec()->set_target("x86");
req.mutable_command_spec()->set_binary_hash("deadbeef");
req.set_cwd("/tmp");
req.mutable_command_spec()->set_local_compiler_path("c:\\dummy\\cl.exe");
std::vector<string> args = {"c:\\dummy\\clang-cl.exe", "/c", "dummy.cc"};
std::unique_ptr<CompilerFlags> flag(CompilerFlagsParser::MustNew(args, "."));
EXPECT_FALSE(IsLocalCompilerPathValid("dummy", req, flag->compiler_name()));
}
// TODO: add other combinations if necessary.
TEST(FilePathUtilTest, RemoveDuplicateFiles) {
{
// different filepath
std::set<std::string> filenames{file::JoinPath(kRootDir, "foo", "bar.cc"),
file::JoinPath(kRootDir, "foo", "baz.cc")};
std::vector<std::string> removed;
RemoveDuplicateFiles("", &filenames, &removed);
std::set<std::string> expected{file::JoinPath(kRootDir, "foo", "bar.cc"),
file::JoinPath(kRootDir, "foo", "baz.cc")};
EXPECT_EQ(filenames, expected);
EXPECT_TRUE(removed.empty());
}
{
std::set<std::string> filenames{
file::JoinPath(kRootDir, "fOO"),
file::JoinPath(kRootDir, "Foo"),
file::JoinPath(kRootDir, "fOo"),
file::JoinPath(absl::AsciiStrToLower(kRootDir), "FOO"),
file::JoinPath(absl::AsciiStrToLower(kRootDir), "foO"),
};
std::vector<std::string> removed;
RemoveDuplicateFiles("", &filenames, &removed);
#ifdef _WIN32
// Windows: case-insensitive, use the case variation of the first non-unique
// Windows path that was encountered, based on case-sensitive ordering of
// strings within std::set.
std::set<std::string> expected{
file::JoinPath(kRootDir, "Foo"),
};
std::vector<std::string> expected_removed{
file::JoinPath(kRootDir, "fOO"),
file::JoinPath(kRootDir, "fOo"),
file::JoinPath(absl::AsciiStrToLower(kRootDir), "FOO"),
file::JoinPath(absl::AsciiStrToLower(kRootDir), "foO"),
};
#else
// non-Windows: different filepath if case is not same.
std::set<std::string> expected{
file::JoinPath(kRootDir, "fOO"),
file::JoinPath(kRootDir, "Foo"),
file::JoinPath(kRootDir, "fOo"),
file::JoinPath(absl::AsciiStrToLower(kRootDir), "FOO"),
file::JoinPath(absl::AsciiStrToLower(kRootDir), "foO"),
};
std::vector<std::string> expected_removed;
#endif // _WIN32
EXPECT_EQ(filenames, expected);
EXPECT_EQ(removed, expected_removed);
}
{
// same filepath when JoinPathRespectAbsolute
std::set<std::string> filenames{"bar.cc",
file::JoinPath(kRootDir, "foo", "bar.cc")};
std::vector<std::string> removed;
RemoveDuplicateFiles(file::JoinPath(kRootDir, "foo"), &filenames,
&removed);
std::set<std::string> expected{"bar.cc"};
std::vector<std::string> expected_removed{
file::JoinPath(kRootDir, "foo", "bar.cc"),
};
EXPECT_EQ(filenames, expected);
EXPECT_EQ(removed, expected_removed);
}
{
// same filepath when JoinPathRespectAbsolute with ..
std::set<std::string> filenames{
file::JoinPath("..", "bar.cc"),
file::JoinPath(kRootDir, "foo", "baz", "..", "bar.cc")};
std::vector<std::string> removed;
RemoveDuplicateFiles(file::JoinPath(kRootDir, "foo", "baz"), &filenames,
&removed);
std::set<std::string> expected{file::JoinPath("..", "bar.cc")};
std::vector<std::string> expected_removed {
file::JoinPath(kRootDir, "foo", "baz", "..", "bar.cc"),
};
EXPECT_EQ(filenames, expected);
EXPECT_EQ(removed, expected_removed);
}
{
// different filepath when JoinPathRespectAbsolute
std::set<std::string> filenames{
file::JoinPath(kRootDir, "foo", "baz", "..", "bar.cc"),
file::JoinPath(kRootDir, "foo", "bar.cc")};
std::vector<std::string> removed;
RemoveDuplicateFiles("", &filenames, &removed);
std::set<std::string> expected{
file::JoinPath(kRootDir, "foo", "baz", "..", "bar.cc"),
file::JoinPath(kRootDir, "foo", "bar.cc")};
EXPECT_EQ(filenames, expected);
EXPECT_TRUE(removed.empty());
}
{
// different filepath when JoinPathRespectAbsolute
std::set<std::string> filenames{file::JoinPath("baz", "..", "bar.cc"),
file::JoinPath(kRootDir, "foo", "bar.cc")};
std::vector<std::string> removed;
RemoveDuplicateFiles(file::JoinPath(kRootDir, "foo"), &filenames,
&removed);
std::set<std::string> expected{file::JoinPath("baz", "..", "bar.cc"),
file::JoinPath(kRootDir, "foo", "bar.cc")};
EXPECT_EQ(filenames, expected);
EXPECT_TRUE(removed.empty());
}
{
// different filepath when JoinPathRespectAbsolute
std::set<std::string> filenames{file::JoinPath("..", "bar.cc"),
file::JoinPath(kRootDir, "foo", "bar.cc")};
std::vector<std::string> removed;
RemoveDuplicateFiles(file::JoinPath(kRootDir, "foo", "baz"), &filenames,
&removed);
std::set<std::string> expected{file::JoinPath("..", "bar.cc"),
file::JoinPath(kRootDir, "foo", "bar.cc")};
EXPECT_EQ(filenames, expected);
EXPECT_TRUE(removed.empty());
}
{
// We want to test path X and path Y where
// 1. X < Y (alphabetical order)
// 2. JoinPathRespectAbsolute(cwd, X) == JoinPathRespectAbsolutePath(cwd, Y)
// 3. len(X) > len(Y).
//
// Due to (1) and (2), If X is relative path, Y should be absolute path
// or vice versa. Since we don't resolve path and due to (3),
// Y must be relative path, and X must be absolute path.
//
// So, relative path should start with a character which is larger than
// 'C' (on Win) or '/' (on non Win).
std::set<std::string> filenames{
file::JoinPath(kRootDir, "a", "a", "bar.cc"),
file::JoinPath("a", "bar.cc"),
};
ASSERT_EQ(file::JoinPath(kRootDir, "a", "a", "bar.cc"), *filenames.begin());
std::vector<std::string> removed;
RemoveDuplicateFiles(file::JoinPath(kRootDir, "a"), &filenames, &removed);
std::set<std::string> expected{
file::JoinPath("a", "bar.cc"),
};
std::vector<std::string> expected_removed{
file::JoinPath(kRootDir, "a", "a", "bar.cc"),
};
EXPECT_EQ(filenames, expected);
EXPECT_EQ(removed, expected_removed);
}
}
} // namespace devtools_goma