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 { |
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 | |
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 | |
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 | |
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 | |
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 | #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 | |
164 | TEST_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 | |
176 | TEST_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 | |
195 | TEST_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 | |
226 | struct 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 | |
248 | TEST_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 | |
274 | TEST_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 | |
302 | TEST_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 | |
322 | struct BuildLogRecompactTest : public BuildLogTest { |
323 | virtual bool IsPathDead(StringPiece s) const { return s == "out2" ; } |
324 | }; |
325 | |
326 | TEST_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 | |