• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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