xref: /third_party/ninja/src/build_log_test.cc (revision 695b41ee)
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
30using namespace std;
31
32namespace {
33
34const char kTestFilename[] = "BuildLogTest-tempfile";
35
36struct 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
47TEST_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
75TEST_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
105TEST_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
124TEST_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
166TEST_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
178TEST_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
198TEST_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
231struct TestDiskInterface : public DiskInterface {
232    virtual TimeStamp Stat(const string& path, string* err) const {
233        return 4;
234    }
235    virtual bool WriteFile(const string& path, const string& contents) {
236        assert(false);
237        return true;
238    }
239    virtual bool MakeDir(const string& path) {
240        assert(false);
241        return false;
242    }
243    virtual Status ReadFile(const string& path, string* contents, string* err) {
244        assert(false);
245        return NotFound;
246    }
247    virtual int RemoveFile(const string& path) {
248        assert(false);
249        return 0;
250    }
251};
252
253TEST_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
279TEST_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
308TEST_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
328struct BuildLogRecompactTest : public BuildLogTest {
329    virtual bool IsPathDead(StringPiece s) const { return s == "out2"; }
330};
331
332TEST_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