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  }