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 using namespace std;
31
32 namespace {
33
34 const char kTestFilename[] = "BuildLogTest-tempfile";
35
36 struct BuildLogTest : public StateTestWithBuiltinRules, public BuildLogUser {
SetUp__anoncdef269b0111::BuildLogTest37 virtual void SetUp() {
38 // In case a crashing test left a stale file behind.
39 unlink(kTestFilename);
40 }
TearDown__anoncdef269b0111::BuildLogTest41 virtual void TearDown() {
42 unlink(kTestFilename);
43 }
IsPathDead__anoncdef269b0111::BuildLogTest44 virtual bool IsPathDead(StringPiece s) const { return false; }
45 };
46
TEST_F(BuildLogTest,WriteRead)47 TEST_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
TEST_F(BuildLogTest,FirstWriteAddsSignature)75 TEST_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
TEST_F(BuildLogTest,DoubleEntry)105 TEST_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
TEST_F(BuildLogTest,Truncate)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 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
137 struct stat statbuf;
138 ASSERT_EQ(0, stat(kTestFilename, &statbuf));
139 ASSERT_GT(statbuf.st_size, 0);
140
141 // For all possible truncations of the input file, assert that we don't
142 // crash when parsing.
143 for (off_t size = statbuf.st_size; size > 0; --size) {
144 BuildLog log2;
145 string err;
146 EXPECT_TRUE(log2.OpenForWrite(kTestFilename, *this, &err));
147 ASSERT_EQ("", err);
148 log2.RecordCommand(state_.edges_[0], 15, 18);
149 log2.RecordCommand(state_.edges_[1], 20, 25);
150 log2.Close();
151
152 ASSERT_TRUE(Truncate(kTestFilename, size, &err));
153
154 BuildLog log3;
155 err.clear();
156 ASSERT_TRUE(log3.Load(kTestFilename, &err) == LOAD_SUCCESS || !err.empty());
157 }
158 }
159
TEST_F(BuildLogTest,ObsoleteOldVersion)160 TEST_F(BuildLogTest, ObsoleteOldVersion) {
161 FILE* f = fopen(kTestFilename, "wb");
162 fprintf(f, "# ninja log v3\n");
163 fprintf(f, "123 456 0 out command\n");
164 fclose(f);
165
166 string err;
167 BuildLog log;
168 EXPECT_TRUE(log.Load(kTestFilename, &err));
169 ASSERT_NE(err.find("version"), string::npos);
170 }
171
TEST_F(BuildLogTest,SpacesInOutputV4)172 TEST_F(BuildLogTest, SpacesInOutputV4) {
173 FILE* f = fopen(kTestFilename, "wb");
174 fprintf(f, "# ninja log v4\n");
175 fprintf(f, "123\t456\t456\tout with space\tcommand\n");
176 fclose(f);
177
178 string err;
179 BuildLog log;
180 EXPECT_TRUE(log.Load(kTestFilename, &err));
181 ASSERT_EQ("", err);
182
183 BuildLog::LogEntry* e = log.LookupByOutput("out with space");
184 ASSERT_TRUE(e);
185 ASSERT_EQ(123, e->start_time);
186 ASSERT_EQ(456, e->end_time);
187 ASSERT_EQ(456, e->mtime);
188 ASSERT_NO_FATAL_FAILURE(AssertHash("command", e->command_hash));
189 }
190
TEST_F(BuildLogTest,DuplicateVersionHeader)191 TEST_F(BuildLogTest, DuplicateVersionHeader) {
192 // Old versions of ninja accidentally wrote multiple version headers to the
193 // build log on Windows. This shouldn't crash, and the second version header
194 // should be ignored.
195 FILE* f = fopen(kTestFilename, "wb");
196 fprintf(f, "# ninja log v4\n");
197 fprintf(f, "123\t456\t456\tout\tcommand\n");
198 fprintf(f, "# ninja log v4\n");
199 fprintf(f, "456\t789\t789\tout2\tcommand2\n");
200 fclose(f);
201
202 string err;
203 BuildLog log;
204 EXPECT_TRUE(log.Load(kTestFilename, &err));
205 ASSERT_EQ("", err);
206
207 BuildLog::LogEntry* e = log.LookupByOutput("out");
208 ASSERT_TRUE(e);
209 ASSERT_EQ(123, e->start_time);
210 ASSERT_EQ(456, e->end_time);
211 ASSERT_EQ(456, e->mtime);
212 ASSERT_NO_FATAL_FAILURE(AssertHash("command", e->command_hash));
213
214 e = log.LookupByOutput("out2");
215 ASSERT_TRUE(e);
216 ASSERT_EQ(456, e->start_time);
217 ASSERT_EQ(789, e->end_time);
218 ASSERT_EQ(789, e->mtime);
219 ASSERT_NO_FATAL_FAILURE(AssertHash("command2", e->command_hash));
220 }
221
222 struct TestDiskInterface : public DiskInterface {
Stat__anoncdef269b0111::TestDiskInterface223 virtual TimeStamp Stat(const string& path, string* err) const {
224 return 4;
225 }
WriteFile__anoncdef269b0111::TestDiskInterface226 virtual bool WriteFile(const string& path, const string& contents) {
227 assert(false);
228 return true;
229 }
MakeDir__anoncdef269b0111::TestDiskInterface230 virtual bool MakeDir(const string& path) {
231 assert(false);
232 return false;
233 }
ReadFile__anoncdef269b0111::TestDiskInterface234 virtual Status ReadFile(const string& path, string* contents, string* err) {
235 assert(false);
236 return NotFound;
237 }
RemoveFile__anoncdef269b0111::TestDiskInterface238 virtual int RemoveFile(const string& path) {
239 assert(false);
240 return 0;
241 }
242 };
243
TEST_F(BuildLogTest,Restat)244 TEST_F(BuildLogTest, Restat) {
245 FILE* f = fopen(kTestFilename, "wb");
246 fprintf(f, "# ninja log v4\n"
247 "1\t2\t3\tout\tcommand\n");
248 fclose(f);
249 std::string err;
250 BuildLog log;
251 EXPECT_TRUE(log.Load(kTestFilename, &err));
252 ASSERT_EQ("", err);
253 BuildLog::LogEntry* e = log.LookupByOutput("out");
254 ASSERT_EQ(3, e->mtime);
255
256 TestDiskInterface testDiskInterface;
257 char out2[] = { 'o', 'u', 't', '2', 0 };
258 char* filter2[] = { out2 };
259 EXPECT_TRUE(log.Restat(kTestFilename, testDiskInterface, 1, filter2, &err));
260 ASSERT_EQ("", err);
261 e = log.LookupByOutput("out");
262 ASSERT_EQ(3, e->mtime); // unchanged, since the filter doesn't match
263
264 EXPECT_TRUE(log.Restat(kTestFilename, testDiskInterface, 0, NULL, &err));
265 ASSERT_EQ("", err);
266 e = log.LookupByOutput("out");
267 ASSERT_EQ(4, e->mtime);
268 }
269
TEST_F(BuildLogTest,VeryLongInputLine)270 TEST_F(BuildLogTest, VeryLongInputLine) {
271 // Ninja's build log buffer is currently 256kB. Lines longer than that are
272 // silently ignored, but don't affect parsing of other lines.
273 FILE* f = fopen(kTestFilename, "wb");
274 fprintf(f, "# ninja log v4\n");
275 fprintf(f, "123\t456\t456\tout\tcommand start");
276 for (size_t i = 0; i < (512 << 10) / strlen(" more_command"); ++i)
277 fputs(" more_command", f);
278 fprintf(f, "\n");
279 fprintf(f, "456\t789\t789\tout2\tcommand2\n");
280 fclose(f);
281
282 string err;
283 BuildLog log;
284 EXPECT_TRUE(log.Load(kTestFilename, &err));
285 ASSERT_EQ("", err);
286
287 BuildLog::LogEntry* e = log.LookupByOutput("out");
288 ASSERT_EQ(NULL, e);
289
290 e = log.LookupByOutput("out2");
291 ASSERT_TRUE(e);
292 ASSERT_EQ(456, e->start_time);
293 ASSERT_EQ(789, e->end_time);
294 ASSERT_EQ(789, e->mtime);
295 ASSERT_NO_FATAL_FAILURE(AssertHash("command2", e->command_hash));
296 }
297
TEST_F(BuildLogTest,MultiTargetEdge)298 TEST_F(BuildLogTest, MultiTargetEdge) {
299 AssertParse(&state_,
300 "build out out.d: cat\n");
301
302 BuildLog log;
303 log.RecordCommand(state_.edges_[0], 21, 22);
304
305 ASSERT_EQ(2u, log.entries().size());
306 BuildLog::LogEntry* e1 = log.LookupByOutput("out");
307 ASSERT_TRUE(e1);
308 BuildLog::LogEntry* e2 = log.LookupByOutput("out.d");
309 ASSERT_TRUE(e2);
310 ASSERT_EQ("out", e1->output);
311 ASSERT_EQ("out.d", e2->output);
312 ASSERT_EQ(21, e1->start_time);
313 ASSERT_EQ(21, e2->start_time);
314 ASSERT_EQ(22, e2->end_time);
315 ASSERT_EQ(22, e2->end_time);
316 }
317
318 struct BuildLogRecompactTest : public BuildLogTest {
IsPathDead__anoncdef269b0111::BuildLogRecompactTest319 virtual bool IsPathDead(StringPiece s) const { return s == "out2"; }
320 };
321
TEST_F(BuildLogRecompactTest,Recompact)322 TEST_F(BuildLogRecompactTest, Recompact) {
323 AssertParse(&state_,
324 "build out: cat in\n"
325 "build out2: cat in\n");
326
327 BuildLog log1;
328 string err;
329 EXPECT_TRUE(log1.OpenForWrite(kTestFilename, *this, &err));
330 ASSERT_EQ("", err);
331 // Record the same edge several times, to trigger recompaction
332 // the next time the log is opened.
333 for (int i = 0; i < 200; ++i)
334 log1.RecordCommand(state_.edges_[0], 15, 18 + i);
335 log1.RecordCommand(state_.edges_[1], 21, 22);
336 log1.Close();
337
338 // Load...
339 BuildLog log2;
340 EXPECT_TRUE(log2.Load(kTestFilename, &err));
341 ASSERT_EQ("", err);
342 ASSERT_EQ(2u, log2.entries().size());
343 ASSERT_TRUE(log2.LookupByOutput("out"));
344 ASSERT_TRUE(log2.LookupByOutput("out2"));
345 // ...and force a recompaction.
346 EXPECT_TRUE(log2.OpenForWrite(kTestFilename, *this, &err));
347 log2.Close();
348
349 // "out2" is dead, it should've been removed.
350 BuildLog log3;
351 EXPECT_TRUE(log2.Load(kTestFilename, &err));
352 ASSERT_EQ("", err);
353 ASSERT_EQ(1u, log2.entries().size());
354 ASSERT_TRUE(log2.LookupByOutput("out"));
355 ASSERT_FALSE(log2.LookupByOutput("out2"));
356 }
357
358 } // anonymous namespace
359