github.com/maruel/nin@v0.0.0-20220112143044-f35891e3ce7e/build_log_test.go (about) 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 package nin 16 17 import ( 18 "bytes" 19 "errors" 20 "fmt" 21 "io/ioutil" 22 "os" 23 "path/filepath" 24 "strings" 25 "testing" 26 ) 27 28 type BuildLogTest struct { 29 StateTestWithBuiltinRules 30 } 31 32 func NewBuildLogTest(t *testing.T) *BuildLogTest { 33 return &BuildLogTest{NewStateTestWithBuiltinRules(t)} 34 } 35 36 func (b *BuildLogTest) IsPathDead(s string) bool { 37 return false 38 } 39 40 func TestBuildLogTest_WriteRead(t *testing.T) { 41 b := NewBuildLogTest(t) 42 b.AssertParse(&b.state, "build out: cat mid\nbuild mid: cat in\n", ParseManifestOpts{}) 43 44 log1 := NewBuildLog() 45 defer log1.Close() 46 testFilename := filepath.Join(t.TempDir(), "BuildLogTest-tempfile") 47 if err := log1.OpenForWrite(testFilename, b); err != nil { 48 t.Fatal(err) 49 } 50 log1.RecordCommand(b.state.Edges[0], 15, 18, 0) 51 log1.RecordCommand(b.state.Edges[1], 20, 25, 0) 52 log1.Close() 53 54 log2 := NewBuildLog() 55 defer log2.Close() 56 if s, err := log2.Load(testFilename); s != LoadSuccess && err != nil { 57 t.Fatal(s, err) 58 } 59 60 if 2 != len(log1.Entries) { 61 t.Fatal("expected equal") 62 } 63 if 2 != len(log2.Entries) { 64 t.Fatal("expected equal") 65 } 66 e1 := log1.Entries["out"] 67 if e1 == nil { 68 t.Fatal("expected true") 69 } 70 e2 := log2.Entries["out"] 71 if e2 == nil { 72 t.Fatal("expected true") 73 } 74 if *e1 != *e2 { 75 t.Fatal("expected true") 76 } 77 if 15 != e1.startTime { 78 t.Fatal("expected equal") 79 } 80 if "out" != e1.output { 81 t.Fatal("expected equal") 82 } 83 } 84 85 func TestBuildLogTest_FirstWriteAddsSignature(t *testing.T) { 86 b := NewBuildLogTest(t) 87 // Bump when the version is changed. 88 expectedVersion := []byte("# ninja log v5\n") 89 90 log := NewBuildLog() 91 defer log.Close() 92 testFilename := filepath.Join(t.TempDir(), "BuildLogTest-tempfile") 93 if err := log.OpenForWrite(testFilename, b); err != nil { 94 t.Fatal(err) 95 } 96 log.Close() 97 98 contents, err2 := ioutil.ReadFile(testFilename) 99 if err2 != nil { 100 t.Fatal(err2) 101 } 102 if !bytes.Equal(expectedVersion, contents) { 103 t.Fatal(string(contents)) 104 } 105 106 // Opening the file anew shouldn't add a second version string. 107 if err := log.OpenForWrite(testFilename, b); err != nil { 108 t.Fatal(err) 109 } 110 log.Close() 111 112 contents, err2 = ioutil.ReadFile(testFilename) 113 if err2 != nil { 114 t.Fatal(err2) 115 } 116 if !bytes.Equal(expectedVersion, contents) { 117 t.Fatal(string(contents)) 118 } 119 } 120 121 func TestBuildLogTest_DoubleEntry(t *testing.T) { 122 b := NewBuildLogTest(t) 123 testFilename := filepath.Join(t.TempDir(), "BuildLogTest-tempfile") 124 content := "# ninja log v4\n0\t1\t2\tout\tcommand abc\n3\t4\t5\tout\tcommand def\n" 125 if err := ioutil.WriteFile(testFilename, []byte(content), 0o600); err != nil { 126 t.Fatal(err) 127 } 128 129 log := NewBuildLog() 130 defer log.Close() 131 if s, err := log.Load(testFilename); s != LoadSuccess && err != nil { 132 t.Fatal(s, err) 133 } 134 135 e := log.Entries["out"] 136 if e == nil { 137 t.Fatal("expected true") 138 } 139 b.AssertHash("command def", e.commandHash) 140 } 141 142 func TestBuildLogTest_Truncate(t *testing.T) { 143 b := NewBuildLogTest(t) 144 b.AssertParse(&b.state, "build out: cat mid\nbuild mid: cat in\n", ParseManifestOpts{}) 145 testFilename := filepath.Join(t.TempDir(), "BuildLogTest-tempfile") 146 147 { 148 log1 := NewBuildLog() 149 defer log1.Close() 150 if err := log1.OpenForWrite(testFilename, b); err != nil { 151 t.Fatal(err) 152 } 153 log1.RecordCommand(b.state.Edges[0], 15, 18, 0) 154 log1.RecordCommand(b.state.Edges[1], 20, 25, 0) 155 log1.Close() 156 } 157 158 // For all possible truncations of the input file, assert that we don't 159 // crash when parsing. 160 for size := getFileSize(t, testFilename); size > 0; size-- { 161 log2 := NewBuildLog() 162 defer log2.Close() 163 if err := log2.OpenForWrite(testFilename, b); err != nil { 164 t.Fatal(err) 165 } 166 log2.RecordCommand(b.state.Edges[0], 15, 18, 0) 167 log2.RecordCommand(b.state.Edges[1], 20, 25, 0) 168 log2.Close() 169 170 if err := os.Truncate(testFilename, int64(size)); err != nil { 171 t.Fatal(err) 172 } 173 174 log3 := NewBuildLog() 175 defer log3.Close() 176 if s, err := log3.Load(testFilename); s != LoadSuccess && err != nil { 177 t.Fatal(s, err) 178 } 179 log3.Close() 180 } 181 } 182 183 func TestBuildLogTest_ObsoleteOldVersion(t *testing.T) { 184 testFilename := filepath.Join(t.TempDir(), "BuildLogTest-tempfile") 185 content := []byte("# ninja log v3\n123 456 0 out command\n") 186 if err := ioutil.WriteFile(testFilename, content, 0o600); err != nil { 187 t.Fatal(err) 188 } 189 190 log := NewBuildLog() 191 defer log.Close() 192 if s, err := log.Load(testFilename); s != LoadSuccess && err == nil { 193 t.Fatal(s, err) 194 } else if !strings.Contains(err.Error(), "version") { 195 t.Fatal(s, err) 196 } 197 } 198 199 func TestBuildLogTest_SpacesInOutputV4(t *testing.T) { 200 b := NewBuildLogTest(t) 201 testFilename := filepath.Join(t.TempDir(), "BuildLogTest-tempfile") 202 content := []byte("# ninja log v4\n123\t456\t456\tout with space\tcommand\n") 203 if err := ioutil.WriteFile(testFilename, content, 0o600); err != nil { 204 t.Fatal(err) 205 } 206 207 log := NewBuildLog() 208 defer log.Close() 209 if s, err := log.Load(testFilename); s != LoadSuccess && err != nil { 210 t.Fatal(s, err) 211 } 212 213 e := log.Entries["out with space"] 214 if e == nil { 215 t.Fatal("expected true") 216 } 217 if 123 != e.startTime { 218 t.Fatal("expected equal") 219 } 220 if 456 != e.endTime { 221 t.Fatal("expected equal") 222 } 223 if 456 != e.mtime { 224 t.Fatal("expected equal") 225 } 226 b.AssertHash("command", e.commandHash) 227 } 228 229 func TestBuildLogTest_DuplicateVersionHeader(t *testing.T) { 230 b := NewBuildLogTest(t) 231 // Old versions of ninja accidentally wrote multiple version headers to the 232 // build log on Windows. This shouldn't crash, and the second version header 233 // should be ignored. 234 testFilename := filepath.Join(t.TempDir(), "BuildLogTest-tempfile") 235 content := []byte("# ninja log v4\n123\t456\t456\tout\tcommand\n# ninja log v4\n456\t789\t789\tout2\tcommand2\n") 236 if err := ioutil.WriteFile(testFilename, content, 0o600); err != nil { 237 t.Fatal(err) 238 } 239 240 log := NewBuildLog() 241 defer log.Close() 242 if s, err := log.Load(testFilename); s != LoadSuccess && err != nil { 243 t.Fatal(s, err) 244 } 245 246 e := log.Entries["out"] 247 if e == nil { 248 t.Fatal("expected true") 249 } 250 if 123 != e.startTime { 251 t.Fatal("expected equal") 252 } 253 if 456 != e.endTime { 254 t.Fatal("expected equal") 255 } 256 if 456 != e.mtime { 257 t.Fatal("expected equal") 258 } 259 b.AssertHash("command", e.commandHash) 260 261 e = log.Entries["out2"] 262 if e == nil { 263 t.Fatal("expected true") 264 } 265 if 456 != e.startTime { 266 t.Fatal("expected equal") 267 } 268 if 789 != e.endTime { 269 t.Fatal("expected equal") 270 } 271 if 789 != e.mtime { 272 t.Fatal("expected equal") 273 } 274 b.AssertHash("command2", e.commandHash) 275 } 276 277 type TestDiskInterface struct { 278 t *testing.T 279 } 280 281 func (t *TestDiskInterface) Stat(path string) (TimeStamp, error) { 282 return 4, nil 283 } 284 func (t *TestDiskInterface) WriteFile(path string, contents string) error { 285 t.t.Fatal("Should not be reached") 286 return errors.New("not implemented") 287 } 288 func (t *TestDiskInterface) MakeDir(path string) error { 289 t.t.Fatal("Should not be reached") 290 return errors.New("not implemented") 291 } 292 func (t *TestDiskInterface) ReadFile(path string) ([]byte, error) { 293 t.t.Fatal("Should not be reached") 294 return nil, errors.New("not implemented") 295 } 296 func (t *TestDiskInterface) RemoveFile(path string) error { 297 t.t.Fatal("Should not be reached") 298 return errors.New("not implemented") 299 } 300 301 func TestBuildLogTest_Restat(t *testing.T) { 302 testFilename := filepath.Join(t.TempDir(), "BuildLogTest-tempfile") 303 content := []byte("# ninja log v4\n1\t2\t3\tout\tcommand\n") 304 if err := ioutil.WriteFile(testFilename, content, 0o600); err != nil { 305 t.Fatal(err) 306 } 307 log := NewBuildLog() 308 defer log.Close() 309 if s, err := log.Load(testFilename); s != LoadSuccess && err != nil { 310 t.Fatal(s, err) 311 } 312 e := log.Entries["out"] 313 if 3 != e.mtime { 314 t.Fatal("expected equal") 315 } 316 317 // TODO(maruel): The original test case is broken. 318 testDiskInterface := TestDiskInterface{t} 319 if err := log.Restat(testFilename, &testDiskInterface, []string{"out2"}); err != nil { 320 t.Fatal(err) 321 } 322 e = log.Entries["out"] 323 if 3 != e.mtime { 324 t.Fatal(e.mtime) 325 } // unchanged, since the filter doesn't match 326 327 if err := log.Restat(testFilename, &testDiskInterface, nil); err != nil { 328 t.Fatal(err) 329 } 330 e = log.Entries["out"] 331 if 4 != e.mtime { 332 t.Fatal("expected equal") 333 } 334 } 335 336 func TestBuildLogTest_VeryLongInputLine(t *testing.T) { 337 b := NewBuildLogTest(t) 338 // Ninja's build log buffer in C++ is currently 256kB. Lines longer than that 339 // are silently ignored, but don't affect parsing of other lines. 340 testFilename := filepath.Join(t.TempDir(), "BuildLogTest-tempfile") 341 f, err2 := os.OpenFile(testFilename, os.O_CREATE|os.O_WRONLY, 0o600) 342 if err2 != nil { 343 t.Fatal(err2) 344 } 345 fmt.Fprintf(f, "# ninja log v4\n") 346 fmt.Fprintf(f, "123\t456\t456\tout\tcommand start") 347 for i := 0; i < (512<<10)/len(" more_command"); i++ { 348 f.WriteString(" more_command") 349 } 350 fmt.Fprintf(f, "\n") 351 fmt.Fprintf(f, "456\t789\t789\tout2\tcommand2\n") 352 f.Close() 353 354 log := NewBuildLog() 355 defer log.Close() 356 if s, err := log.Load(testFilename); s != LoadSuccess && err != nil { 357 t.Fatal(s, err) 358 } 359 360 // Difference from C++ version! 361 // In the Go version, lines are not ignored. 362 e := log.Entries["out"] 363 if nil == e { 364 t.Fatal("expected equal") 365 } 366 367 e = log.Entries["out2"] 368 if e == nil { 369 t.Fatal("expected true") 370 } 371 if 456 != e.startTime { 372 t.Fatal("expected equal") 373 } 374 if 789 != e.endTime { 375 t.Fatal("expected equal") 376 } 377 if 789 != e.mtime { 378 t.Fatal("expected equal") 379 } 380 b.AssertHash("command2", e.commandHash) 381 } 382 383 func TestBuildLogTest_MultiTargetEdge(t *testing.T) { 384 b := NewBuildLogTest(t) 385 b.AssertParse(&b.state, "build out out.d: cat\n", ParseManifestOpts{}) 386 387 log := NewBuildLog() 388 defer log.Close() 389 log.RecordCommand(b.state.Edges[0], 21, 22, 0) 390 391 if 2 != len(log.Entries) { 392 t.Fatal("expected equal") 393 } 394 e1 := log.Entries["out"] 395 if e1 == nil { 396 t.Fatal("expected true") 397 } 398 e2 := log.Entries["out.d"] 399 if e2 == nil { 400 t.Fatal("expected true") 401 } 402 if "out" != e1.output { 403 t.Fatal("expected equal") 404 } 405 if "out.d" != e2.output { 406 t.Fatal("expected equal") 407 } 408 if 21 != e1.startTime { 409 t.Fatal("expected equal") 410 } 411 if 21 != e2.startTime { 412 t.Fatal("expected equal") 413 } 414 if 22 != e2.endTime { 415 t.Fatal("expected equal") 416 } 417 if 22 != e2.endTime { 418 t.Fatal("expected equal") 419 } 420 } 421 422 type BuildLogRecompactTest struct { 423 *BuildLogTest 424 } 425 426 func (b *BuildLogRecompactTest) IsPathDead(s string) bool { 427 return s == "out2" 428 } 429 430 func NewBuildLogRecompactTest(t *testing.T) *BuildLogRecompactTest { 431 return &BuildLogRecompactTest{NewBuildLogTest(t)} 432 } 433 434 func TestBuildLogRecompactTest_Recompact(t *testing.T) { 435 b := NewBuildLogRecompactTest(t) 436 b.AssertParse(&b.state, "build out: cat in\nbuild out2: cat in\n", ParseManifestOpts{}) 437 testFilename := filepath.Join(t.TempDir(), "BuildLogTest-tempfile") 438 439 { 440 log1 := NewBuildLog() 441 defer log1.Close() 442 if err := log1.OpenForWrite(testFilename, b); err != nil { 443 t.Fatal(err) 444 } 445 // Record the same edge several times, to trigger recompaction 446 // the next time the log is opened. 447 for i := 0; i < 200; i++ { 448 log1.RecordCommand(b.state.Edges[0], 15, int32(18+i), 0) 449 } 450 log1.RecordCommand(b.state.Edges[1], 21, 22, 0) 451 log1.Close() 452 } 453 454 // Load... 455 { 456 log2 := NewBuildLog() 457 defer log2.Close() 458 if s, err := log2.Load(testFilename); s != LoadSuccess && err != nil { 459 t.Fatal(s, err) 460 } 461 if 2 != len(log2.Entries) { 462 t.Fatal("expected equal") 463 } 464 if log2.Entries["out"] == nil { 465 t.Fatal("expected true") 466 } 467 if log2.Entries["out2"] == nil { 468 t.Fatal("expected true") 469 } 470 // ...and force a recompaction. 471 if err := log2.OpenForWrite(testFilename, b); err != nil { 472 t.Fatal(err) 473 } 474 log2.Close() 475 } 476 477 // "out2" is dead, it should've been removed. 478 { 479 log3 := NewBuildLog() 480 defer log3.Close() 481 if s, err := log3.Load(testFilename); s != LoadSuccess && err != nil { 482 t.Fatal(s, err) 483 } 484 if 1 != len(log3.Entries) { 485 t.Fatalf("%#v", log3.Entries) 486 } 487 if log3.Entries["out"] == nil { 488 t.Fatal("expected true") 489 } 490 if log3.Entries["out2"] != nil { 491 t.Fatal("expected false") 492 } 493 log3.Close() 494 } 495 } 496 497 func TestHashCommand(t *testing.T) { 498 if got := HashCommand(cmdHashCommand); got != 0x7c3f62c6da547bcb { 499 t.Fatal(got) 500 } 501 if got := HashCommand("short"); got != 0x6de374e6eaf07e1a { 502 t.Fatal(got) 503 } 504 } 505 506 var optGuardBenchmarkHashCommand uint64 507 508 // Found the command by printing the longest command ran when building 509 // ninja_test. 510 const cmdHashCommand = "rm -f build/libninja.a && ar crs build/libninja.a build/browse.o build/build.o build/build_log.o build/clean.o build/clparser.o build/debug_flags.o build/depfile_parser.o build/deps_log.o build/disk_interface.o build/dyndep.o build/dyndep_parser.o build/edit_distance.o build/eval_env.o build/graph.o build/graphviz.o build/json.o build/lexer.o build/line_printer.o build/manifest_parser.o build/metrics.o build/missing_deps.o build/parser.o build/state.o build/status.o build/string_piece_util.o build/util.o build/version.o build/subprocess-posix.o" 511 512 // BenchmarkHashCommand runs a benchmark against HashCommand() with both a 513 // large and a short string. 514 func BenchmarkHashCommand(b *testing.B) { 515 b.ReportAllocs() 516 v := optGuardBenchmarkHashCommand 517 for i := 0; i < b.N; i++ { 518 v += HashCommand(cmdHashCommand) 519 v += HashCommand("short") 520 } 521 optGuardBenchmarkHashCommand = v 522 }