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
30using namespace std;
31
32namespace {
33
34const char kTestFilename[] = "BuildLogTest-tempfile";
35
36struct BuildLogTest : public StateTestWithBuiltinRules, public BuildLogUser {
37 virtual void SetUp() {
38 // In case a crashing test left a stale file behind.
39 unlink(kTestFilename);
40 }
41 virtual void TearDown() {
42 unlink(kTestFilename);
43 }
44 virtual bool IsPathDead(StringPiece s) const { return false; }
45};
46
47TEST_F(BuildLogTest, WriteRead) {
48 AssertParse(&state_,
49"build out: cat mid\n"
50"build mid: cat in\n");
51
52 BuildLog log1;
53 string err;
54 EXPECT_TRUE(log1.OpenForWrite(kTestFilename, *this, &err));
55 ASSERT_EQ("", err);
56 log1.RecordCommand(state_.edges_[0], 15, 18);
57 log1.RecordCommand(state_.edges_[1], 20, 25);
58 log1.Close();
59
60 BuildLog log2;
61 EXPECT_TRUE(log2.Load(kTestFilename, &err));
62 ASSERT_EQ("", err);
63
64 ASSERT_EQ(2u, log1.entries().size());
65 ASSERT_EQ(2u, log2.entries().size());
66 BuildLog::LogEntry* e1 = log1.LookupByOutput("out");
67 ASSERT_TRUE(e1);
68 BuildLog::LogEntry* e2 = log2.LookupByOutput("out");
69 ASSERT_TRUE(e2);
70 ASSERT_TRUE(*e1 == *e2);
71 ASSERT_EQ(15, e1->start_time);
72 ASSERT_EQ("out", e1->output);
73}
74
75TEST_F(BuildLogTest, FirstWriteAddsSignature) {
76 const char kExpectedVersion[] = "# ninja log vX\n";
77 const size_t kVersionPos = strlen(kExpectedVersion) - 2; // Points at 'X'.
78
79 BuildLog log;
80 string contents, err;
81
82 EXPECT_TRUE(log.OpenForWrite(kTestFilename, *this, &err));
83 ASSERT_EQ("", err);
84 log.Close();
85
86 ASSERT_EQ(0, ReadFile(kTestFilename, &contents, &err));
87 ASSERT_EQ("", err);
88 if (contents.size() >= kVersionPos)
89 contents[kVersionPos] = 'X';
90 EXPECT_EQ(kExpectedVersion, contents);
91
92 // Opening the file anew shouldn't add a second version string.
93 EXPECT_TRUE(log.OpenForWrite(kTestFilename, *this, &err));
94 ASSERT_EQ("", err);
95 log.Close();
96
97 contents.clear();
98 ASSERT_EQ(0, ReadFile(kTestFilename, &contents, &err));
99 ASSERT_EQ("", err);
100 if (contents.size() >= kVersionPos)
101 contents[kVersionPos] = 'X';
102 EXPECT_EQ(kExpectedVersion, contents);
103}
104
105TEST_F(BuildLogTest, DoubleEntry) {
106 FILE* f = fopen(kTestFilename, "wb");
107 fprintf(f, "# ninja log v4\n");
108 fprintf(f, "0\t1\t2\tout\tcommand abc\n");
109 fprintf(f, "3\t4\t5\tout\tcommand def\n");
110 fclose(f);
111
112 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 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 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 string err;
171 BuildLog log;
172 EXPECT_TRUE(log.Load(kTestFilename, &err));
173 ASSERT_NE(err.find("version"), string::npos);
174}
175
176TEST_F(BuildLogTest, SpacesInOutputV4) {
177 FILE* f = fopen(kTestFilename, "wb");
178 fprintf(f, "# ninja log v4\n");
179 fprintf(f, "123\t456\t456\tout with space\tcommand\n");
180 fclose(f);
181
182 string err;
183 BuildLog log;
184 EXPECT_TRUE(log.Load(kTestFilename, &err));
185 ASSERT_EQ("", err);
186
187 BuildLog::LogEntry* e = log.LookupByOutput("out with space");
188 ASSERT_TRUE(e);
189 ASSERT_EQ(123, e->start_time);
190 ASSERT_EQ(456, e->end_time);
191 ASSERT_EQ(456, e->mtime);
192 ASSERT_NO_FATAL_FAILURE(AssertHash("command", e->command_hash));
193}
194
195TEST_F(BuildLogTest, DuplicateVersionHeader) {
196 // Old versions of ninja accidentally wrote multiple version headers to the
197 // build log on Windows. This shouldn't crash, and the second version header
198 // should be ignored.
199 FILE* f = fopen(kTestFilename, "wb");
200 fprintf(f, "# ninja log v4\n");
201 fprintf(f, "123\t456\t456\tout\tcommand\n");
202 fprintf(f, "# ninja log v4\n");
203 fprintf(f, "456\t789\t789\tout2\tcommand2\n");
204 fclose(f);
205
206 string err;
207 BuildLog log;
208 EXPECT_TRUE(log.Load(kTestFilename, &err));
209 ASSERT_EQ("", err);
210
211 BuildLog::LogEntry* e = log.LookupByOutput("out");
212 ASSERT_TRUE(e);
213 ASSERT_EQ(123, e->start_time);
214 ASSERT_EQ(456, e->end_time);
215 ASSERT_EQ(456, e->mtime);
216 ASSERT_NO_FATAL_FAILURE(AssertHash("command", e->command_hash));
217
218 e = log.LookupByOutput("out2");
219 ASSERT_TRUE(e);
220 ASSERT_EQ(456, e->start_time);
221 ASSERT_EQ(789, e->end_time);
222 ASSERT_EQ(789, e->mtime);
223 ASSERT_NO_FATAL_FAILURE(AssertHash("command2", e->command_hash));
224}
225
226struct TestDiskInterface : public DiskInterface {
227 virtual TimeStamp Stat(const string& path, string* err) const {
228 return 4;
229 }
230 virtual bool WriteFile(const string& path, const string& contents) {
231 assert(false);
232 return true;
233 }
234 virtual bool MakeDir(const string& path) {
235 assert(false);
236 return false;
237 }
238 virtual Status ReadFile(const string& path, string* contents, string* err) {
239 assert(false);
240 return NotFound;
241 }
242 virtual int RemoveFile(const string& path) {
243 assert(false);
244 return 0;
245 }
246};
247
248TEST_F(BuildLogTest, Restat) {
249 FILE* f = fopen(kTestFilename, "wb");
250 fprintf(f, "# ninja log v4\n"
251 "1\t2\t3\tout\tcommand\n");
252 fclose(f);
253 std::string err;
254 BuildLog log;
255 EXPECT_TRUE(log.Load(kTestFilename, &err));
256 ASSERT_EQ("", err);
257 BuildLog::LogEntry* e = log.LookupByOutput("out");
258 ASSERT_EQ(3, e->mtime);
259
260 TestDiskInterface testDiskInterface;
261 char out2[] = { 'o', 'u', 't', '2', 0 };
262 char* filter2[] = { out2 };
263 EXPECT_TRUE(log.Restat(kTestFilename, testDiskInterface, 1, filter2, &err));
264 ASSERT_EQ("", err);
265 e = log.LookupByOutput("out");
266 ASSERT_EQ(3, e->mtime); // unchanged, since the filter doesn't match
267
268 EXPECT_TRUE(log.Restat(kTestFilename, testDiskInterface, 0, NULL, &err));
269 ASSERT_EQ("", err);
270 e = log.LookupByOutput("out");
271 ASSERT_EQ(4, e->mtime);
272}
273
274TEST_F(BuildLogTest, VeryLongInputLine) {
275 // Ninja's build log buffer is currently 256kB. Lines longer than that are
276 // silently ignored, but don't affect parsing of other lines.
277 FILE* f = fopen(kTestFilename, "wb");
278 fprintf(f, "# ninja log v4\n");
279 fprintf(f, "123\t456\t456\tout\tcommand start");
280 for (size_t i = 0; i < (512 << 10) / strlen(" more_command"); ++i)
281 fputs(" more_command", f);
282 fprintf(f, "\n");
283 fprintf(f, "456\t789\t789\tout2\tcommand2\n");
284 fclose(f);
285
286 string err;
287 BuildLog log;
288 EXPECT_TRUE(log.Load(kTestFilename, &err));
289 ASSERT_EQ("", err);
290
291 BuildLog::LogEntry* e = log.LookupByOutput("out");
292 ASSERT_EQ(NULL, e);
293
294 e = log.LookupByOutput("out2");
295 ASSERT_TRUE(e);
296 ASSERT_EQ(456, e->start_time);
297 ASSERT_EQ(789, e->end_time);
298 ASSERT_EQ(789, e->mtime);
299 ASSERT_NO_FATAL_FAILURE(AssertHash("command2", e->command_hash));
300}
301
302TEST_F(BuildLogTest, MultiTargetEdge) {
303 AssertParse(&state_,
304"build out out.d: cat\n");
305
306 BuildLog log;
307 log.RecordCommand(state_.edges_[0], 21, 22);
308
309 ASSERT_EQ(2u, log.entries().size());
310 BuildLog::LogEntry* e1 = log.LookupByOutput("out");
311 ASSERT_TRUE(e1);
312 BuildLog::LogEntry* e2 = log.LookupByOutput("out.d");
313 ASSERT_TRUE(e2);
314 ASSERT_EQ("out", e1->output);
315 ASSERT_EQ("out.d", e2->output);
316 ASSERT_EQ(21, e1->start_time);
317 ASSERT_EQ(21, e2->start_time);
318 ASSERT_EQ(22, e2->end_time);
319 ASSERT_EQ(22, e2->end_time);
320}
321
322struct BuildLogRecompactTest : public BuildLogTest {
323 virtual bool IsPathDead(StringPiece s) const { return s == "out2"; }
324};
325
326TEST_F(BuildLogRecompactTest, Recompact) {
327 AssertParse(&state_,
328"build out: cat in\n"
329"build out2: cat in\n");
330
331 BuildLog log1;
332 string err;
333 EXPECT_TRUE(log1.OpenForWrite(kTestFilename, *this, &err));
334 ASSERT_EQ("", err);
335 // Record the same edge several times, to trigger recompaction
336 // the next time the log is opened.
337 for (int i = 0; i < 200; ++i)
338 log1.RecordCommand(state_.edges_[0], 15, 18 + i);
339 log1.RecordCommand(state_.edges_[1], 21, 22);
340 log1.Close();
341
342 // Load...
343 BuildLog log2;
344 EXPECT_TRUE(log2.Load(kTestFilename, &err));
345 ASSERT_EQ("", err);
346 ASSERT_EQ(2u, log2.entries().size());
347 ASSERT_TRUE(log2.LookupByOutput("out"));
348 ASSERT_TRUE(log2.LookupByOutput("out2"));
349 // ...and force a recompaction.
350 EXPECT_TRUE(log2.OpenForWrite(kTestFilename, *this, &err));
351 log2.Close();
352
353 // "out2" is dead, it should've been removed.
354 BuildLog log3;
355 EXPECT_TRUE(log2.Load(kTestFilename, &err));
356 ASSERT_EQ("", err);
357 ASSERT_EQ(1u, log2.entries().size());
358 ASSERT_TRUE(log2.LookupByOutput("out"));
359 ASSERT_FALSE(log2.LookupByOutput("out2"));
360}
361
362} // anonymous namespace
363