#include "deps_log.h"
#include <sys/stat.h>
#ifndef _WIN32
#include <unistd.h>
#endif
#include "graph.h"
#include "util.h"
#include "test.h"
using namespace std;
namespace {
const char kTestFilename[] = "DepsLogTest-tempfile";
struct DepsLogTest : public testing::Test {
virtual void SetUp() {
unlink(kTestFilename);
}
virtual void TearDown() {
unlink(kTestFilename);
}
};
TEST_F(DepsLogTest, WriteRead) {
State state1;
DepsLog log1;
string err;
EXPECT_TRUE(log1.OpenForWrite(kTestFilename, &err));
ASSERT_EQ("", err);
{
vector<Node*> deps;
deps.push_back(state1.GetNode("foo.h", 0));
deps.push_back(state1.GetNode("bar.h", 0));
log1.RecordDeps(state1.GetNode("out.o", 0), 1, deps);
deps.clear();
deps.push_back(state1.GetNode("foo.h", 0));
deps.push_back(state1.GetNode("bar2.h", 0));
log1.RecordDeps(state1.GetNode("out2.o", 0), 2, deps);
DepsLog::Deps* log_deps = log1.GetDeps(state1.GetNode("out.o", 0));
ASSERT_TRUE(log_deps);
ASSERT_EQ(1, log_deps->mtime);
ASSERT_EQ(2, log_deps->node_count);
ASSERT_EQ("foo.h", log_deps->nodes[0]->path());
ASSERT_EQ("bar.h", log_deps->nodes[1]->path());
}
log1.Close();
State state2;
DepsLog log2;
EXPECT_TRUE(log2.Load(kTestFilename, &state2, &err));
ASSERT_EQ("", err);
ASSERT_EQ(log1.nodes().size(), log2.nodes().size());
for (int i = 0; i < (int)log1.nodes().size(); ++i) {
Node* node1 = log1.nodes()[i];
Node* node2 = log2.nodes()[i];
ASSERT_EQ(i, node1->id());
ASSERT_EQ(node1->id(), node2->id());
}
DepsLog::Deps* log_deps = log2.GetDeps(state2.GetNode("out2.o", 0));
ASSERT_TRUE(log_deps);
ASSERT_EQ(2, log_deps->mtime);
ASSERT_EQ(2, log_deps->node_count);
ASSERT_EQ("foo.h", log_deps->nodes[0]->path());
ASSERT_EQ("bar2.h", log_deps->nodes[1]->path());
}
TEST_F(DepsLogTest, LotsOfDeps) {
const int kNumDeps = 100000;
State state1;
DepsLog log1;
string err;
EXPECT_TRUE(log1.OpenForWrite(kTestFilename, &err));
ASSERT_EQ("", err);
{
vector<Node*> deps;
for (int i = 0; i < kNumDeps; ++i) {
char buf[32];
sprintf(buf, "file%d.h", i);
deps.push_back(state1.GetNode(buf, 0));
}
log1.RecordDeps(state1.GetNode("out.o", 0), 1, deps);
DepsLog::Deps* log_deps = log1.GetDeps(state1.GetNode("out.o", 0));
ASSERT_EQ(kNumDeps, log_deps->node_count);
}
log1.Close();
State state2;
DepsLog log2;
EXPECT_TRUE(log2.Load(kTestFilename, &state2, &err));
ASSERT_EQ("", err);
DepsLog::Deps* log_deps = log2.GetDeps(state2.GetNode("out.o", 0));
ASSERT_EQ(kNumDeps, log_deps->node_count);
}
TEST_F(DepsLogTest, DoubleEntry) {
int file_size;
{
State state;
DepsLog log;
string err;
EXPECT_TRUE(log.OpenForWrite(kTestFilename, &err));
ASSERT_EQ("", err);
vector<Node*> deps;
deps.push_back(state.GetNode("foo.h", 0));
deps.push_back(state.GetNode("bar.h", 0));
log.RecordDeps(state.GetNode("out.o", 0), 1, deps);
log.Close();
struct stat st;
ASSERT_EQ(0, stat(kTestFilename, &st));
file_size = (int)st.st_size;
ASSERT_GT(file_size, 0);
}
{
State state;
DepsLog log;
string err;
EXPECT_TRUE(log.Load(kTestFilename, &state, &err));
EXPECT_TRUE(log.OpenForWrite(kTestFilename, &err));
ASSERT_EQ("", err);
vector<Node*> deps;
deps.push_back(state.GetNode("foo.h", 0));
deps.push_back(state.GetNode("bar.h", 0));
log.RecordDeps(state.GetNode("out.o", 0), 1, deps);
log.Close();
struct stat st;
ASSERT_EQ(0, stat(kTestFilename, &st));
int file_size_2 = (int)st.st_size;
ASSERT_EQ(file_size, file_size_2);
}
}
TEST_F(DepsLogTest, Recompact) {
const char kManifest[] =
"rule cc\n"
" command = cc\n"
" deps = gcc\n"
"build out.o: cc\n"
"build other_out.o: cc\n";
int file_size;
{
State state;
ASSERT_NO_FATAL_FAILURE(AssertParse(&state, kManifest));
DepsLog log;
string err;
ASSERT_TRUE(log.OpenForWrite(kTestFilename, &err));
ASSERT_EQ("", err);
vector<Node*> deps;
deps.push_back(state.GetNode("foo.h", 0));
deps.push_back(state.GetNode("bar.h", 0));
log.RecordDeps(state.GetNode("out.o", 0), 1, deps);
deps.clear();
deps.push_back(state.GetNode("foo.h", 0));
deps.push_back(state.GetNode("baz.h", 0));
log.RecordDeps(state.GetNode("other_out.o", 0), 1, deps);
log.Close();
struct stat st;
ASSERT_EQ(0, stat(kTestFilename, &st));
file_size = (int)st.st_size;
ASSERT_GT(file_size, 0);
}
int file_size_2;
{
State state;
ASSERT_NO_FATAL_FAILURE(AssertParse(&state, kManifest));
DepsLog log;
string err;
ASSERT_TRUE(log.Load(kTestFilename, &state, &err));
ASSERT_TRUE(log.OpenForWrite(kTestFilename, &err));
ASSERT_EQ("", err);
vector<Node*> deps;
deps.push_back(state.GetNode("foo.h", 0));
log.RecordDeps(state.GetNode("out.o", 0), 1, deps);
log.Close();
struct stat st;
ASSERT_EQ(0, stat(kTestFilename, &st));
file_size_2 = (int)st.st_size;
ASSERT_GT(file_size_2, file_size);
}
int file_size_3;
{
State state;
ASSERT_NO_FATAL_FAILURE(AssertParse(&state, kManifest));
DepsLog log;
string err;
ASSERT_TRUE(log.Load(kTestFilename, &state, &err));
Node* out = state.GetNode("out.o", 0);
DepsLog::Deps* deps = log.GetDeps(out);
ASSERT_TRUE(deps);
ASSERT_EQ(1, deps->mtime);
ASSERT_EQ(1, deps->node_count);
ASSERT_EQ("foo.h", deps->nodes[0]->path());
Node* other_out = state.GetNode("other_out.o", 0);
deps = log.GetDeps(other_out);
ASSERT_TRUE(deps);
ASSERT_EQ(1, deps->mtime);
ASSERT_EQ(2, deps->node_count);
ASSERT_EQ("foo.h", deps->nodes[0]->path());
ASSERT_EQ("baz.h", deps->nodes[1]->path());
ASSERT_TRUE(log.Recompact(kTestFilename, &err));
deps = log.GetDeps(out);
ASSERT_TRUE(deps);
ASSERT_EQ(1, deps->mtime);
ASSERT_EQ(1, deps->node_count);
ASSERT_EQ("foo.h", deps->nodes[0]->path());
ASSERT_EQ(out, log.nodes()[out->id()]);
deps = log.GetDeps(other_out);
ASSERT_TRUE(deps);
ASSERT_EQ(1, deps->mtime);
ASSERT_EQ(2, deps->node_count);
ASSERT_EQ("foo.h", deps->nodes[0]->path());
ASSERT_EQ("baz.h", deps->nodes[1]->path());
ASSERT_EQ(other_out, log.nodes()[other_out->id()]);
struct stat st;
ASSERT_EQ(0, stat(kTestFilename, &st));
file_size_3 = (int)st.st_size;
ASSERT_LT(file_size_3, file_size_2);
}
{
State state;
DepsLog log;
string err;
ASSERT_TRUE(log.Load(kTestFilename, &state, &err));
Node* out = state.GetNode("out.o", 0);
DepsLog::Deps* deps = log.GetDeps(out);
ASSERT_TRUE(deps);
ASSERT_EQ(1, deps->mtime);
ASSERT_EQ(1, deps->node_count);
ASSERT_EQ("foo.h", deps->nodes[0]->path());
Node* other_out = state.GetNode("other_out.o", 0);
deps = log.GetDeps(other_out);
ASSERT_TRUE(deps);
ASSERT_EQ(1, deps->mtime);
ASSERT_EQ(2, deps->node_count);
ASSERT_EQ("foo.h", deps->nodes[0]->path());
ASSERT_EQ("baz.h", deps->nodes[1]->path());
ASSERT_TRUE(log.Recompact(kTestFilename, &err));
deps = log.GetDeps(out);
ASSERT_FALSE(deps);
deps = log.GetDeps(other_out);
ASSERT_FALSE(deps);
ASSERT_EQ(-1, state.LookupNode("foo.h")->id());
ASSERT_EQ(-1, state.LookupNode("baz.h")->id());
struct stat st;
ASSERT_EQ(0, stat(kTestFilename, &st));
int file_size_4 = (int)st.st_size;
ASSERT_LT(file_size_4, file_size_3);
}
}
TEST_F(DepsLogTest, InvalidHeader) {
const char *kInvalidHeaders[] = {
"", "# ninjad", "# ninjadeps\n", "# ninjadeps\n\001\002", "# ninjadeps\n\001\002\003\004" };
for (size_t i = 0; i < sizeof(kInvalidHeaders) / sizeof(kInvalidHeaders[0]);
++i) {
FILE* deps_log = fopen(kTestFilename, "wb");
ASSERT_TRUE(deps_log != NULL);
ASSERT_EQ(
strlen(kInvalidHeaders[i]),
fwrite(kInvalidHeaders[i], 1, strlen(kInvalidHeaders[i]), deps_log));
ASSERT_EQ(0 ,fclose(deps_log));
string err;
DepsLog log;
State state;
ASSERT_TRUE(log.Load(kTestFilename, &state, &err));
EXPECT_EQ("bad deps log signature or version; starting over", err);
}
}
TEST_F(DepsLogTest, Truncated) {
{
State state;
DepsLog log;
string err;
EXPECT_TRUE(log.OpenForWrite(kTestFilename, &err));
ASSERT_EQ("", err);
vector<Node*> deps;
deps.push_back(state.GetNode("foo.h", 0));
deps.push_back(state.GetNode("bar.h", 0));
log.RecordDeps(state.GetNode("out.o", 0), 1, deps);
deps.clear();
deps.push_back(state.GetNode("foo.h", 0));
deps.push_back(state.GetNode("bar2.h", 0));
log.RecordDeps(state.GetNode("out2.o", 0), 2, deps);
log.Close();
}
struct stat st;
ASSERT_EQ(0, stat(kTestFilename, &st));
int node_count = 5;
int deps_count = 2;
for (int size = (int)st.st_size; size > 0; --size) {
string err;
ASSERT_TRUE(Truncate(kTestFilename, size, &err));
State state;
DepsLog log;
EXPECT_TRUE(log.Load(kTestFilename, &state, &err));
if (!err.empty()) {
break;
}
ASSERT_GE(node_count, (int)log.nodes().size());
node_count = log.nodes().size();
int new_deps_count = 0;
for (vector<DepsLog::Deps*>::const_iterator i = log.deps().begin();
i != log.deps().end(); ++i) {
if (*i)
++new_deps_count;
}
ASSERT_GE(deps_count, new_deps_count);
deps_count = new_deps_count;
}
}
TEST_F(DepsLogTest, TruncatedRecovery) {
{
State state;
DepsLog log;
string err;
EXPECT_TRUE(log.OpenForWrite(kTestFilename, &err));
ASSERT_EQ("", err);
vector<Node*> deps;
deps.push_back(state.GetNode("foo.h", 0));
deps.push_back(state.GetNode("bar.h", 0));
log.RecordDeps(state.GetNode("out.o", 0), 1, deps);
deps.clear();
deps.push_back(state.GetNode("foo.h", 0));
deps.push_back(state.GetNode("bar2.h", 0));
log.RecordDeps(state.GetNode("out2.o", 0), 2, deps);
log.Close();
}
{
struct stat st;
ASSERT_EQ(0, stat(kTestFilename, &st));
string err;
ASSERT_TRUE(Truncate(kTestFilename, st.st_size - 2, &err));
}
{
State state;
DepsLog log;
string err;
EXPECT_TRUE(log.Load(kTestFilename, &state, &err));
ASSERT_EQ("premature end of file; recovering", err);
err.clear();
EXPECT_EQ(NULL, log.GetDeps(state.GetNode("out2.o", 0)));
EXPECT_TRUE(log.OpenForWrite(kTestFilename, &err));
ASSERT_EQ("", err);
vector<Node*> deps;
deps.push_back(state.GetNode("foo.h", 0));
deps.push_back(state.GetNode("bar2.h", 0));
log.RecordDeps(state.GetNode("out2.o", 0), 3, deps);
log.Close();
}
{
State state;
DepsLog log;
string err;
EXPECT_TRUE(log.Load(kTestFilename, &state, &err));
DepsLog::Deps* deps = log.GetDeps(state.GetNode("out2.o", 0));
ASSERT_TRUE(deps);
}
}
TEST_F(DepsLogTest, ReverseDepsNodes) {
State state;
DepsLog log;
string err;
EXPECT_TRUE(log.OpenForWrite(kTestFilename, &err));
ASSERT_EQ("", err);
vector<Node*> deps;
deps.push_back(state.GetNode("foo.h", 0));
deps.push_back(state.GetNode("bar.h", 0));
log.RecordDeps(state.GetNode("out.o", 0), 1, deps);
deps.clear();
deps.push_back(state.GetNode("foo.h", 0));
deps.push_back(state.GetNode("bar2.h", 0));
log.RecordDeps(state.GetNode("out2.o", 0), 2, deps);
log.Close();
Node* rev_deps = log.GetFirstReverseDepsNode(state.GetNode("foo.h", 0));
EXPECT_TRUE(rev_deps == state.GetNode("out.o", 0) ||
rev_deps == state.GetNode("out2.o", 0));
rev_deps = log.GetFirstReverseDepsNode(state.GetNode("bar.h", 0));
EXPECT_TRUE(rev_deps == state.GetNode("out.o", 0));
}
}