Ninja
build_log_test.cc
Go to the documentation of this file.
1 // Copyright 2011 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 "build_log.h"
16 
17 #include "util.h"
18 #include "test.h"
19 
20 #include <sys/stat.h>
21 #ifdef _WIN32
22 #include <fcntl.h>
23 #include <share.h>
24 #else
25 #include <sys/types.h>
26 #include <unistd.h>
27 #endif
28 #include <cassert>
29 
30 namespace {
31 
32 const char kTestFilename[] = "BuildLogTest-tempfile";
33 
34 struct BuildLogTest : public StateTestWithBuiltinRules, public BuildLogUser {
35  virtual void SetUp() {
36  // In case a crashing test left a stale file behind.
38  }
39  virtual void TearDown() {
41  }
42  virtual bool IsPathDead(StringPiece s) const { return false; }
43 };
44 
45 TEST_F(BuildLogTest, WriteRead) {
46  AssertParse(&state_,
47 "build out: cat mid\n"
48 "build mid: cat in\n");
49 
50  BuildLog log1;
51  std::string err;
52  EXPECT_TRUE(log1.OpenForWrite(kTestFilename, *this, &err));
53  ASSERT_EQ("", err);
54  log1.RecordCommand(state_.edges_[0], 15, 18);
55  log1.RecordCommand(state_.edges_[1], 20, 25);
56  log1.Close();
57 
58  BuildLog log2;
59  EXPECT_TRUE(log2.Load(kTestFilename, &err));
60  ASSERT_EQ("", err);
61 
62  ASSERT_EQ(2u, log1.entries().size());
63  ASSERT_EQ(2u, log2.entries().size());
64  BuildLog::LogEntry* e1 = log1.LookupByOutput("out");
65  ASSERT_TRUE(e1);
66  BuildLog::LogEntry* e2 = log2.LookupByOutput("out");
67  ASSERT_TRUE(e2);
68  ASSERT_TRUE(*e1 == *e2);
69  ASSERT_EQ(15, e1->start_time);
70  ASSERT_EQ("out", e1->output);
71 }
72 
73 TEST_F(BuildLogTest, FirstWriteAddsSignature) {
74  const char kExpectedVersion[] = "# ninja log vX\n";
75  const size_t kVersionPos = strlen(kExpectedVersion) - 2; // Points at 'X'.
76 
77  BuildLog log;
78  std::string contents, err;
79 
80  EXPECT_TRUE(log.OpenForWrite(kTestFilename, *this, &err));
81  ASSERT_EQ("", err);
82  log.Close();
83 
84  ASSERT_EQ(0, ReadFile(kTestFilename, &contents, &err));
85  ASSERT_EQ("", err);
86  if (contents.size() >= kVersionPos)
87  contents[kVersionPos] = 'X';
88  EXPECT_EQ(kExpectedVersion, contents);
89 
90  // Opening the file anew shouldn't add a second version string.
91  EXPECT_TRUE(log.OpenForWrite(kTestFilename, *this, &err));
92  ASSERT_EQ("", err);
93  log.Close();
94 
95  contents.clear();
96  ASSERT_EQ(0, ReadFile(kTestFilename, &contents, &err));
97  ASSERT_EQ("", err);
98  if (contents.size() >= kVersionPos)
99  contents[kVersionPos] = 'X';
100  EXPECT_EQ(kExpectedVersion, contents);
101 }
102 
103 TEST_F(BuildLogTest, DoubleEntry) {
104  FILE* f = fopen(kTestFilename, "wb");
105  fprintf(f, "# ninja log v7\n");
106  fprintf(f, "0\t1\t2\tout\t%" PRIx64 "\n",
107  BuildLog::LogEntry::HashCommand("command abc"));
108  fprintf(f, "0\t1\t2\tout\t%" PRIx64 "\n",
109  BuildLog::LogEntry::HashCommand("command def"));
110  fclose(f);
111 
112  std::string err;
113  BuildLog log;
114  EXPECT_TRUE(log.Load(kTestFilename, &err));
115  ASSERT_EQ("", err);
116 
117  BuildLog::LogEntry* e = log.LookupByOutput("out");
118  ASSERT_TRUE(e);
119  ASSERT_NO_FATAL_FAILURE(AssertHash("command def", e->command_hash));
120 }
121 
122 TEST_F(BuildLogTest, Truncate) {
123  AssertParse(&state_,
124 "build out: cat mid\n"
125 "build mid: cat in\n");
126 
127  {
128  BuildLog log1;
129  std::string err;
130  EXPECT_TRUE(log1.OpenForWrite(kTestFilename, *this, &err));
131  ASSERT_EQ("", err);
132  log1.RecordCommand(state_.edges_[0], 15, 18);
133  log1.RecordCommand(state_.edges_[1], 20, 25);
134  log1.Close();
135  }
136 #ifdef __USE_LARGEFILE64
137  struct stat64 statbuf;
138  ASSERT_EQ(0, stat64(kTestFilename, &statbuf));
139 #else
140  struct stat statbuf;
141  ASSERT_EQ(0, stat(kTestFilename, &statbuf));
142 #endif
143  ASSERT_GT(statbuf.st_size, 0);
144 
145  // For all possible truncations of the input file, assert that we don't
146  // crash when parsing.
147  for (off_t size = statbuf.st_size; size > 0; --size) {
148  BuildLog log2;
149  std::string err;
150  EXPECT_TRUE(log2.OpenForWrite(kTestFilename, *this, &err));
151  ASSERT_EQ("", err);
152  log2.RecordCommand(state_.edges_[0], 15, 18);
153  log2.RecordCommand(state_.edges_[1], 20, 25);
154  log2.Close();
155 
156  ASSERT_TRUE(Truncate(kTestFilename, size, &err));
157 
158  BuildLog log3;
159  err.clear();
160  ASSERT_TRUE(log3.Load(kTestFilename, &err) == LOAD_SUCCESS || !err.empty());
161  }
162 }
163 
164 TEST_F(BuildLogTest, ObsoleteOldVersion) {
165  FILE* f = fopen(kTestFilename, "wb");
166  fprintf(f, "# ninja log v3\n");
167  fprintf(f, "123 456 0 out command\n");
168  fclose(f);
169 
170  std::string err;
171  BuildLog log;
172  EXPECT_TRUE(log.Load(kTestFilename, &err));
173  ASSERT_NE(err.find("version"), std::string::npos);
174 }
175 
176 TEST_F(BuildLogTest, SpacesInOutput) {
177  FILE* f = fopen(kTestFilename, "wb");
178  fprintf(f, "# ninja log v7\n");
179  fprintf(f, "123\t456\t456\tout with space\t%" PRIx64 "\n",
181  fclose(f);
182 
183  std::string err;
184  BuildLog log;
185  EXPECT_TRUE(log.Load(kTestFilename, &err));
186  ASSERT_EQ("", err);
187 
188  BuildLog::LogEntry* e = log.LookupByOutput("out with space");
189  ASSERT_TRUE(e);
190  ASSERT_EQ(123, e->start_time);
191  ASSERT_EQ(456, e->end_time);
192  ASSERT_EQ(456, e->mtime);
193  ASSERT_NO_FATAL_FAILURE(AssertHash("command", e->command_hash));
194 }
195 
196 TEST_F(BuildLogTest, DuplicateVersionHeader) {
197  // Old versions of ninja accidentally wrote multiple version headers to the
198  // build log on Windows. This shouldn't crash, and the second version header
199  // should be ignored.
200  FILE* f = fopen(kTestFilename, "wb");
201  fprintf(f, "# ninja log v7\n");
202  fprintf(f, "123\t456\t456\tout\t%" PRIx64 "\n",
204  fprintf(f, "# ninja log v7\n");
205  fprintf(f, "456\t789\t789\tout2\t%" PRIx64 "\n",
206  BuildLog::LogEntry::HashCommand("command2"));
207  fclose(f);
208 
209  std::string err;
210  BuildLog log;
211  EXPECT_TRUE(log.Load(kTestFilename, &err));
212  ASSERT_EQ("", err);
213 
214  BuildLog::LogEntry* e = log.LookupByOutput("out");
215  ASSERT_TRUE(e);
216  ASSERT_EQ(123, e->start_time);
217  ASSERT_EQ(456, e->end_time);
218  ASSERT_EQ(456, e->mtime);
219  ASSERT_NO_FATAL_FAILURE(AssertHash("command", e->command_hash));
220 
221  e = log.LookupByOutput("out2");
222  ASSERT_TRUE(e);
223  ASSERT_EQ(456, e->start_time);
224  ASSERT_EQ(789, e->end_time);
225  ASSERT_EQ(789, e->mtime);
226  ASSERT_NO_FATAL_FAILURE(AssertHash("command2", e->command_hash));
227 }
228 
229 struct TestDiskInterface : public DiskInterface {
230  TimeStamp Stat(const std::string& path, std::string* err) const override {
231  return 4;
232  }
233  bool WriteFile(const std::string& path, const std::string& contents,
234  bool crlf_on_windows) override {
235  assert(false);
236  return true;
237  }
238  bool MakeDir(const std::string& path) override {
239  assert(false);
240  return false;
241  }
242  Status ReadFile(const std::string& path, std::string* contents,
243  std::string* err) override {
244  assert(false);
245  return NotFound;
246  }
247  int RemoveFile(const std::string& path) override {
248  assert(false);
249  return 0;
250  }
251 };
252 
253 TEST_F(BuildLogTest, Restat) {
254  FILE* f = fopen(kTestFilename, "wb");
255  fprintf(f, "# ninja log v7\n"
256  "1\t2\t3\tout\tcommand\n");
257  fclose(f);
258  std::string err;
259  BuildLog log;
260  EXPECT_TRUE(log.Load(kTestFilename, &err));
261  ASSERT_EQ("", err);
262  BuildLog::LogEntry* e = log.LookupByOutput("out");
263  ASSERT_EQ(3, e->mtime);
264 
265  TestDiskInterface testDiskInterface;
266  char out2[] = { 'o', 'u', 't', '2', 0 };
267  char* filter2[] = { out2 };
268  EXPECT_TRUE(log.Restat(kTestFilename, testDiskInterface, 1, filter2, &err));
269  ASSERT_EQ("", err);
270  e = log.LookupByOutput("out");
271  ASSERT_EQ(3, e->mtime); // unchanged, since the filter doesn't match
272 
273  EXPECT_TRUE(log.Restat(kTestFilename, testDiskInterface, 0, NULL, &err));
274  ASSERT_EQ("", err);
275  e = log.LookupByOutput("out");
276  ASSERT_EQ(4, e->mtime);
277 }
278 
279 TEST_F(BuildLogTest, VeryLongInputLine) {
280  // Ninja's build log buffer is currently 256kB. Lines longer than that are
281  // silently ignored, but don't affect parsing of other lines.
282  FILE* f = fopen(kTestFilename, "wb");
283  fprintf(f, "# ninja log v7\n");
284  fprintf(f, "123\t456\t456\tout\tcommand start");
285  for (size_t i = 0; i < (512 << 10) / strlen(" more_command"); ++i)
286  fputs(" more_command", f);
287  fprintf(f, "\n");
288  fprintf(f, "456\t789\t789\tout2\t%" PRIx64 "\n",
289  BuildLog::LogEntry::HashCommand("command2"));
290  fclose(f);
291 
292  std::string err;
293  BuildLog log;
294  EXPECT_TRUE(log.Load(kTestFilename, &err));
295  ASSERT_EQ("", err);
296 
297  BuildLog::LogEntry* e = log.LookupByOutput("out");
298  ASSERT_EQ(NULL, e);
299 
300  e = log.LookupByOutput("out2");
301  ASSERT_TRUE(e);
302  ASSERT_EQ(456, e->start_time);
303  ASSERT_EQ(789, e->end_time);
304  ASSERT_EQ(789, e->mtime);
305  ASSERT_NO_FATAL_FAILURE(AssertHash("command2", e->command_hash));
306 }
307 
308 TEST_F(BuildLogTest, MultiTargetEdge) {
309  AssertParse(&state_,
310 "build out out.d: cat\n");
311 
312  BuildLog log;
313  log.RecordCommand(state_.edges_[0], 21, 22);
314 
315  ASSERT_EQ(2u, log.entries().size());
316  BuildLog::LogEntry* e1 = log.LookupByOutput("out");
317  ASSERT_TRUE(e1);
318  BuildLog::LogEntry* e2 = log.LookupByOutput("out.d");
319  ASSERT_TRUE(e2);
320  ASSERT_EQ("out", e1->output);
321  ASSERT_EQ("out.d", e2->output);
322  ASSERT_EQ(21, e1->start_time);
323  ASSERT_EQ(21, e2->start_time);
324  ASSERT_EQ(22, e2->end_time);
325  ASSERT_EQ(22, e2->end_time);
326 }
327 
328 struct BuildLogRecompactTest : public BuildLogTest {
329  virtual bool IsPathDead(StringPiece s) const { return s == "out2"; }
330 };
331 
332 TEST_F(BuildLogRecompactTest, Recompact) {
333  AssertParse(&state_,
334 "build out: cat in\n"
335 "build out2: cat in\n");
336 
337  BuildLog log1;
338  std::string err;
339  EXPECT_TRUE(log1.OpenForWrite(kTestFilename, *this, &err));
340  ASSERT_EQ("", err);
341  // Record the same edge several times, to trigger recompaction
342  // the next time the log is opened.
343  for (int i = 0; i < 200; ++i)
344  log1.RecordCommand(state_.edges_[0], 15, 18 + i);
345  log1.RecordCommand(state_.edges_[1], 21, 22);
346  log1.Close();
347 
348  // Load...
349  BuildLog log2;
350  EXPECT_TRUE(log2.Load(kTestFilename, &err));
351  ASSERT_EQ("", err);
352  ASSERT_EQ(2u, log2.entries().size());
353  ASSERT_TRUE(log2.LookupByOutput("out"));
354  ASSERT_TRUE(log2.LookupByOutput("out2"));
355  // ...and force a recompaction.
356  EXPECT_TRUE(log2.OpenForWrite(kTestFilename, *this, &err));
357  log2.Close();
358 
359  // "out2" is dead, it should've been removed.
360  BuildLog log3;
361  EXPECT_TRUE(log2.Load(kTestFilename, &err));
362  ASSERT_EQ("", err);
363  ASSERT_EQ(1u, log2.entries().size());
364  ASSERT_TRUE(log2.LookupByOutput("out"));
365  ASSERT_FALSE(log2.LookupByOutput("out2"));
366 }
367 
368 } // anonymous namespace
const char kTestFilename[]
TEST_F(PlanTest, Basic)
Definition: build_test.cc:67
@ LOAD_SUCCESS
Definition: load_status.h:20
Can answer questions about the manifest for the BuildLog.
Definition: build_log.h:32
virtual bool IsPathDead(StringPiece s) const =0
Return if a given output is no longer part of the build manifest.
TimeStamp mtime
Definition: build_log.h:65
uint64_t command_hash
Definition: build_log.h:62
static uint64_t HashCommand(StringPiece command)
Definition: build_log.cc:60
std::string output
Definition: build_log.h:61
Store a log of every command ran for every build.
Definition: build_log.h:45
LogEntry * LookupByOutput(const std::string &path)
Lookup a previously-run command by its output path.
Definition: build_log.cc:327
void Close()
Definition: build_log.cc:125
LoadStatus Load(const std::string &path, std::string *err)
Load the on-disk log.
Definition: build_log.cc:208
const Entries & entries() const
Definition: build_log.h:96
bool Restat(StringPiece path, const DiskInterface &disk_interface, int output_count, char **outputs, std::string *err)
Restat all outputs in the log.
Definition: build_log.cc:389
bool OpenForWrite(const std::string &path, const BuildLogUser &user, std::string *err)
Prepares writing to the log file without actually opening it - that will happen when/if it's needed.
Definition: build_log.cc:77
bool RecordCommand(Edge *edge, int start_time, int end_time, TimeStamp mtime=0)
Definition: build_log.cc:90
Interface for accessing the disk.
virtual bool WriteFile(const std::string &path, const std::string &contents, bool crlf_on_windows)=0
Create a file, with the specified name and contents If crlf_on_windows is true, will be converted t...
virtual bool MakeDir(const std::string &path)=0
Create a directory, returning false on failure.
virtual int RemoveFile(const std::string &path)=0
Remove the file named path.
virtual TimeStamp Stat(const std::string &path, std::string *err) const =0
stat() a file, returning the mtime, or 0 if missing and -1 on other errors.
virtual Status ReadFile(const std::string &path, std::string *contents, std::string *err)=0
Read and store in given string.
A base test fixture that includes a State object with a builtin "cat" rule.
Definition: test.h:30
Abstract interface to object that tracks the status of a build: completion fraction,...
Definition: status.h:27
StringPiece represents a slice of a string whose memory is managed externally.
Definition: string_piece.h:25
void AssertParse(State *state, const char *input, ManifestParserOptions opts)
Definition: test.cc:100
void AssertHash(const char *expected, uint64_t actual)
Definition: test.cc:109
int64_t TimeStamp
Definition: timestamp.h:31
int platformAwareUnlink(const char *filename)
Definition: util.cc:1025
bool Truncate(const string &path, size_t size, string *err)
Definition: util.cc:1007
int ReadFile(const string &path, string *contents, string *err)
Definition: util.cc:415
#define PRIx64
Definition: win32port.h:35