blob: d64743f746459a08a9b90cb017deca431721798c [file] [log] [blame]
// 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();
}