| // Copyright 2011 Google Inc. All Rights Reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #ifdef _WIN32 |
| #include <direct.h> // Has to be before util.h is included. |
| #endif |
| |
| #include "test.h" |
| |
| #include <algorithm> |
| |
| #include <errno.h> |
| #include <stdlib.h> |
| #ifdef _WIN32 |
| #include <io.h> |
| #include <sys/stat.h> |
| #include <windows.h> |
| #else |
| #include <unistd.h> |
| #endif |
| |
| #include "build_log.h" |
| #include "graph.h" |
| #include "manifest_parser.h" |
| #include "util.h" |
| |
| #ifdef _AIX |
| extern "C" { |
| // GCC "helpfully" strips the definition of mkdtemp out on AIX. |
| // The function is still present, so if we define it ourselves |
| // it will work perfectly fine. |
| extern char* mkdtemp(char* name_template); |
| } |
| #endif |
| |
| using namespace std; |
| |
| namespace { |
| |
| #ifdef _WIN32 |
| /// Windows has no mkdtemp. Implement it in terms of _mktemp_s. |
| char* mkdtemp(char* name_template) { |
| // The mktemp_s() implementation of the Wine C runtime is broken and |
| // will consistently fail after 26 calls(!) by returning EEXIST. |
| // Reimplement the function in this case. |
| const size_t max_tries = 32; |
| bool found_one = false; |
| size_t len = ::strlen(name_template); |
| // Count the number of X's in the template, must be at least 6. |
| size_t num_xs = 0; |
| while (num_xs < len && name_template[len - 1 - num_xs] == 'X') |
| num_xs++; |
| if (num_xs < 6) { |
| errno = EINVAL; |
| perror("_mktemp_s"); |
| return NULL; |
| } |
| |
| // rand() is really not very random on Wine by default :-/ |
| // leading to surprising conflicts. Same is true for GetCurrentProcessId() |
| // or GetCurrentProcess() so use time |
| FILETIME ft = {}; |
| ::GetSystemTimeAsFileTime(&ft); |
| srand(static_cast<unsigned>(ft.dwLowDateTime)); |
| |
| auto get_random_alnum = []() -> char { |
| int index = rand() % 62; |
| if (index < 26) |
| return static_cast<char>('A' + index); |
| index -= 26; |
| if (index < 26) |
| return static_cast<char>('a' + index); |
| index -= 26; |
| return static_cast<char>('0' + index); |
| }; |
| char* start = name_template + len - num_xs; |
| for (size_t tries = 0; tries < max_tries; ++tries) { |
| for (size_t n = 0; n < num_xs; ++n) |
| start[n] = get_random_alnum(); |
| struct _stat st = {}; |
| int ret = _stat(name_template, &st); |
| if (ret < 0 && errno == ENOENT) { |
| errno = 0; |
| found_one = true; |
| break; |
| } |
| } |
| if (!found_one) { |
| // The Posix mkdtemp() specification does not specify which errno |
| // value to use in this case, neither does the Linux manpage, so |
| // just select EINVAL, which is what the Win32 _mktemp_s() returns |
| // on such failures (note: the Wine version returns EEXIST!). |
| errno = EINVAL; |
| perror("_mktemp_s"); |
| return NULL; |
| } |
| |
| int err = _mkdir(name_template); |
| if (err < 0) { |
| perror("mkdir"); |
| return NULL; |
| } |
| |
| return name_template; |
| } |
| #endif // _WIN32 |
| |
| /// Return system temporary directory. Result always has a trailing separator, |
| /// except when empty. |
| std::string GetSystemTempDir() { |
| #ifdef _WIN32 |
| std::string result; |
| result.resize(PATH_MAX); |
| DWORD ret = GetTempPath(result.size(), &result[0]); |
| result.resize(static_cast<size_t>(ret)); |
| if (!result.empty()) { |
| if (result.back() != '/' && result.back() != '\\') |
| result.push_back('\\'); |
| } |
| return result; |
| #else |
| std::string result = "/tmp/"; |
| const char* tempdir = getenv("TMPDIR"); |
| if (tempdir) { |
| result = tempdir; |
| if (!result.empty() && result.back() != '/') |
| result.push_back('/'); |
| } |
| return result; |
| #endif |
| } |
| |
| } // anonymous namespace |
| |
| |
| ::testing::AsString<void*, void>::AsString(const void* ptr) { |
| char temp[32]; |
| ::snprintf(temp, sizeof(temp), "%p", ptr); |
| str_ = temp; |
| } |
| |
| StateTestWithBuiltinRules::StateTestWithBuiltinRules() { |
| AddCatRule(&state_); |
| } |
| |
| void StateTestWithBuiltinRules::AddCatRule(State* state) { |
| AssertParse(state, |
| "rule cat\n" |
| " command = cat $in > $out\n"); |
| } |
| |
| Node* StateTestWithBuiltinRules::GetNode(const string& path) { |
| EXPECT_FALSE(strpbrk(path.c_str(), "/\\")); |
| return state_.GetNode(path, 0); |
| } |
| |
| void AssertParse(State* state, const char* input, |
| ManifestParserOptions opts) { |
| ManifestParser parser(state, NULL, opts); |
| string err; |
| EXPECT_TRUE(parser.ParseTest(input, &err)); |
| ASSERT_EQ("", err); |
| VerifyGraph(*state); |
| } |
| |
| void AssertHash(const char* expected, uint64_t actual) { |
| ASSERT_EQ(BuildLog::LogEntry::HashCommand(expected), actual); |
| } |
| |
| void VerifyGraph(const State& state) { |
| for (vector<Edge*>::const_iterator e = state.edges_.begin(); |
| e != state.edges_.end(); ++e) { |
| // All edges need at least one output. |
| EXPECT_FALSE((*e)->outputs_.empty()); |
| // Check that the edge's inputs have the edge as out-edge. |
| for (vector<Node*>::const_iterator in_node = (*e)->inputs_.begin(); |
| in_node != (*e)->inputs_.end(); ++in_node) { |
| const vector<Edge*>& out_edges = (*in_node)->out_edges(); |
| EXPECT_NE(find(out_edges.begin(), out_edges.end(), *e), |
| out_edges.end()); |
| } |
| // Check that the edge's outputs have the edge as in-edge. |
| for (vector<Node*>::const_iterator out_node = (*e)->outputs_.begin(); |
| out_node != (*e)->outputs_.end(); ++out_node) { |
| EXPECT_EQ((*out_node)->in_edge(), *e); |
| } |
| } |
| |
| // The union of all in- and out-edges of each nodes should be exactly edges_. |
| set<const Edge*> node_edge_set; |
| for (State::Paths::const_iterator p = state.paths_.begin(); |
| p != state.paths_.end(); ++p) { |
| const Node* n = p->second; |
| if (n->in_edge()) |
| node_edge_set.insert(n->in_edge()); |
| node_edge_set.insert(n->out_edges().begin(), n->out_edges().end()); |
| } |
| set<const Edge*> edge_set(state.edges_.begin(), state.edges_.end()); |
| EXPECT_EQ(node_edge_set, edge_set); |
| } |
| |
| void VirtualFileSystem::Create(const string& path, |
| const string& contents) { |
| files_[path].mtime = now_; |
| files_[path].contents = contents; |
| files_created_.insert(path); |
| } |
| |
| TimeStamp VirtualFileSystem::Stat(const string& path, string* err) const { |
| FileMap::const_iterator i = files_.find(path); |
| if (i != files_.end()) { |
| *err = i->second.stat_error; |
| return i->second.mtime; |
| } |
| return 0; |
| } |
| |
| bool VirtualFileSystem::WriteFile(const string& path, const string& contents) { |
| Create(path, contents); |
| return true; |
| } |
| |
| bool VirtualFileSystem::MakeDir(const string& path) { |
| directories_made_.push_back(path); |
| return true; // success |
| } |
| |
| FileReader::Status VirtualFileSystem::ReadFile(const string& path, |
| string* contents, |
| string* err) { |
| files_read_.push_back(path); |
| FileMap::iterator i = files_.find(path); |
| if (i != files_.end()) { |
| *contents = i->second.contents; |
| return Okay; |
| } |
| *err = strerror(ENOENT); |
| return NotFound; |
| } |
| |
| int VirtualFileSystem::RemoveFile(const string& path) { |
| if (find(directories_made_.begin(), directories_made_.end(), path) |
| != directories_made_.end()) |
| return -1; |
| FileMap::iterator i = files_.find(path); |
| if (i != files_.end()) { |
| files_.erase(i); |
| files_removed_.insert(path); |
| return 0; |
| } else { |
| return 1; |
| } |
| } |
| |
| ScopedTempDir::ScopedTempDir() : original_dir_(GetCurrentDir()) {} |
| |
| ScopedTempDir::~ScopedTempDir() { |
| Cleanup(); |
| } |
| |
| void ScopedTempDir::CreateAndEnter(const string& name) { |
| temp_dir_name_ = GetSystemTempDir(); |
| if (temp_dir_name_.empty()) |
| Fatal("couldn't get system temp dir"); |
| |
| // Create a temporary subdirectory of that. |
| temp_dir_name_ += name; |
| temp_dir_name_ += "-XXXXXX"; |
| char* tempname = mkdtemp(&temp_dir_name_[0]); |
| if (!tempname) |
| ErrnoFatal("mkdtemp"); |
| |
| // chdir into the new temporary directory. |
| if (chdir(temp_dir_name_.c_str()) < 0) |
| ErrnoFatal("chdir"); |
| } |
| |
| void ScopedTempDir::Cleanup() { |
| if (temp_dir_name_.empty()) |
| return; // Something went wrong earlier or already cleaned. |
| |
| // Move out of the directory we're about to clobber. |
| if (chdir(original_dir_.c_str()) < 0) |
| ErrnoFatal("chdir"); |
| |
| #ifdef _WIN32 |
| string command = "rmdir /s /q " + temp_dir_name_; |
| #else |
| string command = "rm -rf " + temp_dir_name_; |
| #endif |
| if (system(command.c_str()) < 0) |
| ErrnoFatal("system"); |
| |
| temp_dir_name_.clear(); |
| } |