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