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__anon74978b1d0111::BuildLogTest37 virtual void SetUp() {
38 // In case a crashing test left a stale file behind.
39 unlink(kTestFilename);
40 }
TearDown__anon74978b1d0111::BuildLogTest41 virtual void TearDown() {
42 unlink(kTestFilename);
43 }
IsPathDead__anon74978b1d0111::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 v6\n");
108 fprintf(f, "0\t1\t2\tout\t%" PRIx64 "\n",
109 BuildLog::LogEntry::HashCommand("command abc"));
110 fprintf(f, "0\t1\t2\tout\t%" PRIx64 "\n",
111 BuildLog::LogEntry::HashCommand("command def"));
112 fclose(f);
113
114 string err;
115 BuildLog log;
116 EXPECT_TRUE(log.Load(kTestFilename, &err));
117 ASSERT_EQ("", err);
118
119 BuildLog::LogEntry* e = log.LookupByOutput("out");
120 ASSERT_TRUE(e);
121 ASSERT_NO_FATAL_FAILURE(AssertHash("command def", e->command_hash));
122 }
123
TEST_F(BuildLogTest,Truncate)124 TEST_F(BuildLogTest, Truncate) {
125 AssertParse(&state_,
126 "build out: cat mid\n"
127 "build mid: cat in\n");
128
129 {
130 BuildLog log1;
131 string err;
132 EXPECT_TRUE(log1.OpenForWrite(kTestFilename, *this, &err));
133 ASSERT_EQ("", err);
134 log1.RecordCommand(state_.edges_[0], 15, 18);
135 log1.RecordCommand(state_.edges_[1], 20, 25);
136 log1.Close();
137 }
138 #ifdef __USE_LARGEFILE64
139 struct stat64 statbuf;
140 ASSERT_EQ(0, stat64(kTestFilename, &statbuf));
141 #else
142 struct stat statbuf;
143 ASSERT_EQ(0, stat(kTestFilename, &statbuf));
144 #endif
145 ASSERT_GT(statbuf.st_size, 0);
146
147 // For all possible truncations of the input file, assert that we don't
148 // crash when parsing.
149 for (off_t size = statbuf.st_size; size > 0; --size) {
150 BuildLog log2;
151 string err;
152 EXPECT_TRUE(log2.OpenForWrite(kTestFilename, *this, &err));
153 ASSERT_EQ("", err);
154 log2.RecordCommand(state_.edges_[0], 15, 18);
155 log2.RecordCommand(state_.edges_[1], 20, 25);
156 log2.Close();
157
158 ASSERT_TRUE(Truncate(kTestFilename, size, &err));
159
160 BuildLog log3;
161 err.clear();
162 ASSERT_TRUE(log3.Load(kTestFilename, &err) == LOAD_SUCCESS || !err.empty());
163 }
164 }
165
TEST_F(BuildLogTest,ObsoleteOldVersion)166 TEST_F(BuildLogTest, ObsoleteOldVersion) {
167 FILE* f = fopen(kTestFilename, "wb");
168 fprintf(f, "# ninja log v3\n");
169 fprintf(f, "123 456 0 out command\n");
170 fclose(f);
171
172 string err;
173 BuildLog log;
174 EXPECT_TRUE(log.Load(kTestFilename, &err));
175 ASSERT_NE(err.find("version"), string::npos);
176 }
177
TEST_F(BuildLogTest,SpacesInOutput)178 TEST_F(BuildLogTest, SpacesInOutput) {
179 FILE* f = fopen(kTestFilename, "wb");
180 fprintf(f, "# ninja log v6\n");
181 fprintf(f, "123\t456\t456\tout with space\t%" PRIx64 "\n",
182 BuildLog::LogEntry::HashCommand("command"));
183 fclose(f);
184
185 string err;
186 BuildLog log;
187 EXPECT_TRUE(log.Load(kTestFilename, &err));
188 ASSERT_EQ("", err);
189
190 BuildLog::LogEntry* e = log.LookupByOutput("out with space");
191 ASSERT_TRUE(e);
192 ASSERT_EQ(123, e->start_time);
193 ASSERT_EQ(456, e->end_time);
194 ASSERT_EQ(456, e->mtime);
195 ASSERT_NO_FATAL_FAILURE(AssertHash("command", e->command_hash));
196 }
197
TEST_F(BuildLogTest,DuplicateVersionHeader)198 TEST_F(BuildLogTest, DuplicateVersionHeader) {
199 // Old versions of ninja accidentally wrote multiple version headers to the
200 // build log on Windows. This shouldn't crash, and the second version header
201 // should be ignored.
202 FILE* f = fopen(kTestFilename, "wb");
203 fprintf(f, "# ninja log v6\n");
204 fprintf(f, "123\t456\t456\tout\t%" PRIx64 "\n",
205 BuildLog::LogEntry::HashCommand("command"));
206 fprintf(f, "# ninja log v6\n");
207 fprintf(f, "456\t789\t789\tout2\t%" PRIx64 "\n",
208 BuildLog::LogEntry::HashCommand("command2"));
209 fclose(f);
210
211 string err;
212 BuildLog log;
213 EXPECT_TRUE(log.Load(kTestFilename, &err));
214 ASSERT_EQ("", err);
215
216 BuildLog::LogEntry* e = log.LookupByOutput("out");
217 ASSERT_TRUE(e);
218 ASSERT_EQ(123, e->start_time);
219 ASSERT_EQ(456, e->end_time);
220 ASSERT_EQ(456, e->mtime);
221 ASSERT_NO_FATAL_FAILURE(AssertHash("command", e->command_hash));
222
223 e = log.LookupByOutput("out2");
224 ASSERT_TRUE(e);
225 ASSERT_EQ(456, e->start_time);
226 ASSERT_EQ(789, e->end_time);
227 ASSERT_EQ(789, e->mtime);
228 ASSERT_NO_FATAL_FAILURE(AssertHash("command2", e->command_hash));
229 }
230
231 struct TestDiskInterface : public DiskInterface {
Stat__anon74978b1d0111::TestDiskInterface232 virtual TimeStamp Stat(const string& path, string* err) const {
233 return 4;
234 }
WriteFile__anon74978b1d0111::TestDiskInterface235 virtual bool WriteFile(const string& path, const string& contents) {
236 assert(false);
237 return true;
238 }
MakeDir__anon74978b1d0111::TestDiskInterface239 virtual bool MakeDir(const string& path) {
240 assert(false);
241 return false;
242 }
ReadFile__anon74978b1d0111::TestDiskInterface243 virtual Status ReadFile(const string& path, string* contents, string* err) {
244 assert(false);
245 return NotFound;
246 }
RemoveFile__anon74978b1d0111::TestDiskInterface247 virtual int RemoveFile(const string& path) {
248 assert(false);
249 return 0;
250 }
251 };
252
TEST_F(BuildLogTest,Restat)253 TEST_F(BuildLogTest, Restat) {
254 FILE* f = fopen(kTestFilename, "wb");
255 fprintf(f, "# ninja log v6\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
TEST_F(BuildLogTest,VeryLongInputLine)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 v6\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 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
TEST_F(BuildLogTest,MultiTargetEdge)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 {
IsPathDead__anon74978b1d0111::BuildLogRecompactTest329 virtual bool IsPathDead(StringPiece s) const { return s == "out2"; }
330 };
331
TEST_F(BuildLogRecompactTest,Recompact)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 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
369