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__anon3766454f0111::BuildLogTest37   virtual void SetUp() {
38     // In case a crashing test left a stale file behind.
39     unlink(kTestFilename);
40   }
TearDown__anon3766454f0111::BuildLogTest41   virtual void TearDown() {
42     unlink(kTestFilename);
43   }
IsPathDead__anon3766454f0111::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__anon3766454f0111::TestDiskInterface223   virtual TimeStamp Stat(const string& path, string* err) const {
224     return 4;
225   }
WriteFile__anon3766454f0111::TestDiskInterface226   virtual bool WriteFile(const string& path, const string& contents) {
227     assert(false);
228     return true;
229   }
MakeDir__anon3766454f0111::TestDiskInterface230   virtual bool MakeDir(const string& path) {
231     assert(false);
232     return false;
233   }
ReadFile__anon3766454f0111::TestDiskInterface234   virtual Status ReadFile(const string& path, string* contents, string* err) {
235     assert(false);
236     return NotFound;
237   }
RemoveFile__anon3766454f0111::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__anon3766454f0111::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