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
30namespace {
31
32const char kTestFilename[] = "BuildLogTest-tempfile";
33
34struct 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
45TEST_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
73TEST_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
103TEST_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
122TEST_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
164TEST_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
176TEST_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
196TEST_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",
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
229struct 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
253TEST_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
279TEST_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",
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
308TEST_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
328struct BuildLogRecompactTest : public BuildLogTest {
329 virtual bool IsPathDead(StringPiece s) const { return s == "out2"; }
330};
331
332TEST_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
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
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
const Entries & entries() const
Definition build_log.h:96
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.
A base test fixture that includes a State object with a builtin "cat" rule.
Definition test.h:30
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