Ninja
deps_log_test.cc
Go to the documentation of this file.
1 // Copyright 2012 Google Inc. All Rights Reserved.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #include "deps_log.h"
16 
17 #include "graph.h"
18 #include "util.h"
19 #include "test.h"
20 
21 namespace {
22 
23 const char kTestFilename[] = "DepsLogTest-tempfile";
24 
25 struct DepsLogTest : public testing::Test {
26  virtual void SetUp() {
27  // In case a crashing test left a stale file behind.
28  unlink(kTestFilename);
29  }
30  virtual void TearDown() {
31  unlink(kTestFilename);
32  }
33 };
34 
35 TEST_F(DepsLogTest, WriteRead) {
36  State state1;
37  DepsLog log1;
38  string err;
39  EXPECT_TRUE(log1.OpenForWrite(kTestFilename, &err));
40  ASSERT_EQ("", err);
41 
42  {
43  vector<Node*> deps;
44  deps.push_back(state1.GetNode("foo.h"));
45  deps.push_back(state1.GetNode("bar.h"));
46  log1.RecordDeps(state1.GetNode("out.o"), 1, deps);
47 
48  deps.clear();
49  deps.push_back(state1.GetNode("foo.h"));
50  deps.push_back(state1.GetNode("bar2.h"));
51  log1.RecordDeps(state1.GetNode("out2.o"), 2, deps);
52 
53  DepsLog::Deps* log_deps = log1.GetDeps(state1.GetNode("out.o"));
54  ASSERT_TRUE(log_deps);
55  ASSERT_EQ(1, log_deps->mtime);
56  ASSERT_EQ(2, log_deps->node_count);
57  ASSERT_EQ("foo.h", log_deps->nodes[0]->path());
58  ASSERT_EQ("bar.h", log_deps->nodes[1]->path());
59  }
60 
61  log1.Close();
62 
63  State state2;
64  DepsLog log2;
65  EXPECT_TRUE(log2.Load(kTestFilename, &state2, &err));
66  ASSERT_EQ("", err);
67 
68  ASSERT_EQ(log1.nodes().size(), log2.nodes().size());
69  for (int i = 0; i < (int)log1.nodes().size(); ++i) {
70  Node* node1 = log1.nodes()[i];
71  Node* node2 = log2.nodes()[i];
72  ASSERT_EQ(i, node1->id());
73  ASSERT_EQ(node1->id(), node2->id());
74  }
75 
76  // Spot-check the entries in log2.
77  DepsLog::Deps* log_deps = log2.GetDeps(state2.GetNode("out2.o"));
78  ASSERT_TRUE(log_deps);
79  ASSERT_EQ(2, log_deps->mtime);
80  ASSERT_EQ(2, log_deps->node_count);
81  ASSERT_EQ("foo.h", log_deps->nodes[0]->path());
82  ASSERT_EQ("bar2.h", log_deps->nodes[1]->path());
83 }
84 
85 // Verify that adding the same deps twice doesn't grow the file.
86 TEST_F(DepsLogTest, DoubleEntry) {
87  // Write some deps to the file and grab its size.
88  int file_size;
89  {
90  State state;
91  DepsLog log;
92  string err;
93  EXPECT_TRUE(log.OpenForWrite(kTestFilename, &err));
94  ASSERT_EQ("", err);
95 
96  vector<Node*> deps;
97  deps.push_back(state.GetNode("foo.h"));
98  deps.push_back(state.GetNode("bar.h"));
99  log.RecordDeps(state.GetNode("out.o"), 1, deps);
100  log.Close();
101 
102  struct stat st;
103  ASSERT_EQ(0, stat(kTestFilename, &st));
104  file_size = (int)st.st_size;
105  ASSERT_GT(file_size, 0);
106  }
107 
108  // Now reload the file, and readd the same deps.
109  {
110  State state;
111  DepsLog log;
112  string err;
113  EXPECT_TRUE(log.Load(kTestFilename, &state, &err));
114 
115  EXPECT_TRUE(log.OpenForWrite(kTestFilename, &err));
116  ASSERT_EQ("", err);
117 
118  vector<Node*> deps;
119  deps.push_back(state.GetNode("foo.h"));
120  deps.push_back(state.GetNode("bar.h"));
121  log.RecordDeps(state.GetNode("out.o"), 1, deps);
122  log.Close();
123 
124  struct stat st;
125  ASSERT_EQ(0, stat(kTestFilename, &st));
126  int file_size_2 = (int)st.st_size;
127  ASSERT_EQ(file_size, file_size_2);
128  }
129 }
130 
131 // Verify that adding the new deps works and can be compacted away.
132 TEST_F(DepsLogTest, Recompact) {
133  // Write some deps to the file and grab its size.
134  int file_size;
135  {
136  State state;
137  DepsLog log;
138  string err;
139  ASSERT_TRUE(log.OpenForWrite(kTestFilename, &err));
140  ASSERT_EQ("", err);
141 
142  vector<Node*> deps;
143  deps.push_back(state.GetNode("foo.h"));
144  deps.push_back(state.GetNode("bar.h"));
145  log.RecordDeps(state.GetNode("out.o"), 1, deps);
146 
147  deps.clear();
148  deps.push_back(state.GetNode("foo.h"));
149  deps.push_back(state.GetNode("baz.h"));
150  log.RecordDeps(state.GetNode("other_out.o"), 1, deps);
151 
152  log.Close();
153 
154  struct stat st;
155  ASSERT_EQ(0, stat(kTestFilename, &st));
156  file_size = (int)st.st_size;
157  ASSERT_GT(file_size, 0);
158  }
159 
160  // Now reload the file, and add slighly different deps.
161  int file_size_2;
162  {
163  State state;
164  DepsLog log;
165  string err;
166  ASSERT_TRUE(log.Load(kTestFilename, &state, &err));
167 
168  ASSERT_TRUE(log.OpenForWrite(kTestFilename, &err));
169  ASSERT_EQ("", err);
170 
171  vector<Node*> deps;
172  deps.push_back(state.GetNode("foo.h"));
173  log.RecordDeps(state.GetNode("out.o"), 1, deps);
174  log.Close();
175 
176  struct stat st;
177  ASSERT_EQ(0, stat(kTestFilename, &st));
178  file_size_2 = (int)st.st_size;
179  // The file should grow to record the new deps.
180  ASSERT_GT(file_size_2, file_size);
181  }
182 
183  // Now reload the file, verify the new deps have replaced the old, then
184  // recompact.
185  {
186  State state;
187  DepsLog log;
188  string err;
189  ASSERT_TRUE(log.Load(kTestFilename, &state, &err));
190 
191  Node* out = state.GetNode("out.o");
192  DepsLog::Deps* deps = log.GetDeps(out);
193  ASSERT_TRUE(deps);
194  ASSERT_EQ(1, deps->mtime);
195  ASSERT_EQ(1, deps->node_count);
196  ASSERT_EQ("foo.h", deps->nodes[0]->path());
197 
198  Node* other_out = state.GetNode("other_out.o");
199  deps = log.GetDeps(other_out);
200  ASSERT_TRUE(deps);
201  ASSERT_EQ(1, deps->mtime);
202  ASSERT_EQ(2, deps->node_count);
203  ASSERT_EQ("foo.h", deps->nodes[0]->path());
204  ASSERT_EQ("baz.h", deps->nodes[1]->path());
205 
206  ASSERT_TRUE(log.Recompact(kTestFilename, &err));
207 
208  // The in-memory deps graph should still be valid after recompaction.
209  deps = log.GetDeps(out);
210  ASSERT_TRUE(deps);
211  ASSERT_EQ(1, deps->mtime);
212  ASSERT_EQ(1, deps->node_count);
213  ASSERT_EQ("foo.h", deps->nodes[0]->path());
214  ASSERT_EQ(out, log.nodes()[out->id()]);
215 
216  deps = log.GetDeps(other_out);
217  ASSERT_TRUE(deps);
218  ASSERT_EQ(1, deps->mtime);
219  ASSERT_EQ(2, deps->node_count);
220  ASSERT_EQ("foo.h", deps->nodes[0]->path());
221  ASSERT_EQ("baz.h", deps->nodes[1]->path());
222  ASSERT_EQ(other_out, log.nodes()[other_out->id()]);
223 
224  // The file should have shrunk a bit for the smaller deps.
225  struct stat st;
226  ASSERT_EQ(0, stat(kTestFilename, &st));
227  int file_size_3 = (int)st.st_size;
228  ASSERT_LT(file_size_3, file_size_2);
229  }
230 }
231 
232 // Verify that invalid file headers cause a new build.
233 TEST_F(DepsLogTest, InvalidHeader) {
234  const char *kInvalidHeaders[] = {
235  "", // Empty file.
236  "# ninjad", // Truncated first line.
237  "# ninjadeps\n", // No version int.
238  "# ninjadeps\n\001\002", // Truncated version int.
239  "# ninjadeps\n\001\002\003\004" // Invalid version int.
240  };
241  for (size_t i = 0; i < sizeof(kInvalidHeaders) / sizeof(kInvalidHeaders[0]);
242  ++i) {
243  FILE* deps_log = fopen(kTestFilename, "wb");
244  ASSERT_TRUE(deps_log != NULL);
245  ASSERT_EQ(
246  strlen(kInvalidHeaders[i]),
247  fwrite(kInvalidHeaders[i], 1, strlen(kInvalidHeaders[i]), deps_log));
248  ASSERT_EQ(0 ,fclose(deps_log));
249 
250  string err;
251  DepsLog log;
252  State state;
253  ASSERT_TRUE(log.Load(kTestFilename, &state, &err));
254  EXPECT_EQ("bad deps log signature or version; starting over", err);
255  }
256 }
257 
258 // Simulate what happens when loading a truncated log file.
259 TEST_F(DepsLogTest, Truncated) {
260  // Create a file with some entries.
261  {
262  State state;
263  DepsLog log;
264  string err;
265  EXPECT_TRUE(log.OpenForWrite(kTestFilename, &err));
266  ASSERT_EQ("", err);
267 
268  vector<Node*> deps;
269  deps.push_back(state.GetNode("foo.h"));
270  deps.push_back(state.GetNode("bar.h"));
271  log.RecordDeps(state.GetNode("out.o"), 1, deps);
272 
273  deps.clear();
274  deps.push_back(state.GetNode("foo.h"));
275  deps.push_back(state.GetNode("bar2.h"));
276  log.RecordDeps(state.GetNode("out2.o"), 2, deps);
277 
278  log.Close();
279  }
280 
281  // Get the file size.
282  struct stat st;
283  ASSERT_EQ(0, stat(kTestFilename, &st));
284 
285  // Try reloading at truncated sizes.
286  // Track how many nodes/deps were found; they should decrease with
287  // smaller sizes.
288  int node_count = 5;
289  int deps_count = 2;
290  for (int size = (int)st.st_size; size > 0; --size) {
291  string err;
292  ASSERT_TRUE(Truncate(kTestFilename, size, &err));
293 
294  State state;
295  DepsLog log;
296  EXPECT_TRUE(log.Load(kTestFilename, &state, &err));
297  if (!err.empty()) {
298  // At some point the log will be so short as to be unparseable.
299  break;
300  }
301 
302  ASSERT_GE(node_count, (int)log.nodes().size());
303  node_count = log.nodes().size();
304 
305  // Count how many non-NULL deps entries there are.
306  int new_deps_count = 0;
307  for (vector<DepsLog::Deps*>::const_iterator i = log.deps().begin();
308  i != log.deps().end(); ++i) {
309  if (*i)
310  ++new_deps_count;
311  }
312  ASSERT_GE(deps_count, new_deps_count);
313  deps_count = new_deps_count;
314  }
315 }
316 
317 // Run the truncation-recovery logic.
318 TEST_F(DepsLogTest, TruncatedRecovery) {
319  // Create a file with some entries.
320  {
321  State state;
322  DepsLog log;
323  string err;
324  EXPECT_TRUE(log.OpenForWrite(kTestFilename, &err));
325  ASSERT_EQ("", err);
326 
327  vector<Node*> deps;
328  deps.push_back(state.GetNode("foo.h"));
329  deps.push_back(state.GetNode("bar.h"));
330  log.RecordDeps(state.GetNode("out.o"), 1, deps);
331 
332  deps.clear();
333  deps.push_back(state.GetNode("foo.h"));
334  deps.push_back(state.GetNode("bar2.h"));
335  log.RecordDeps(state.GetNode("out2.o"), 2, deps);
336 
337  log.Close();
338  }
339 
340  // Shorten the file, corrupting the last record.
341  struct stat st;
342  ASSERT_EQ(0, stat(kTestFilename, &st));
343  string err;
344  ASSERT_TRUE(Truncate(kTestFilename, st.st_size - 2, &err));
345 
346  // Load the file again, add an entry.
347  {
348  State state;
349  DepsLog log;
350  string err;
351  EXPECT_TRUE(log.Load(kTestFilename, &state, &err));
352  ASSERT_EQ("premature end of file; recovering", err);
353  err.clear();
354 
355  // The truncated entry should've been discarded.
356  EXPECT_EQ(NULL, log.GetDeps(state.GetNode("out2.o")));
357 
358  EXPECT_TRUE(log.OpenForWrite(kTestFilename, &err));
359  ASSERT_EQ("", err);
360 
361  // Add a new entry.
362  vector<Node*> deps;
363  deps.push_back(state.GetNode("foo.h"));
364  deps.push_back(state.GetNode("bar2.h"));
365  log.RecordDeps(state.GetNode("out2.o"), 3, deps);
366 
367  log.Close();
368  }
369 
370  // Load the file a third time to verify appending after a mangled
371  // entry doesn't break things.
372  {
373  State state;
374  DepsLog log;
375  string err;
376  EXPECT_TRUE(log.Load(kTestFilename, &state, &err));
377 
378  // The truncated entry should exist.
379  DepsLog::Deps* deps = log.GetDeps(state.GetNode("out2.o"));
380  ASSERT_TRUE(deps);
381  }
382 }
383 
384 } // anonymous namespace
Node * GetNode(StringPiece path)
Definition: state.cc:112
Information about a node in the dependency graph: the file, whether it's dirty, mtime, etc.
Definition: graph.h:35
Node ** nodes
Definition: deps_log.h:80
As build commands run they can output extra dependency information (e.g.
Definition: deps_log.h:63
bool OpenForWrite(const string &path, string *err)
Definition: deps_log.cc:39
TEST_F(PlanTest, Basic)
Definition: build_test.cc:49
Deps * GetDeps(Node *node)
Definition: deps_log.cc:248
const char kTestFilename[]
int node_count
Definition: deps_log.h:79
bool Load(const string &path, State *state, string *err)
Definition: deps_log.cc:143
bool Recompact(const string &path, string *err)
Rewrite the known log entries, throwing away old data.
Definition: deps_log.cc:256
const string & path() const
Definition: graph.h:73
void Close()
Definition: deps_log.cc:137
int id() const
Definition: graph.h:83
bool Truncate(const string &path, size_t size, string *err)
Truncates a file to the given size.
Definition: util.cc:363
const vector< Deps * > & deps() const
Definition: deps_log.h:90
Global state (file status, loaded rules) for a single run.
Definition: state.h:83
const vector< Node * > & nodes() const
Used for tests.
Definition: deps_log.h:89
bool RecordDeps(Node *node, TimeStamp mtime, const vector< Node * > &nodes)
Definition: deps_log.cc:71