github.com/maruel/nin@v0.0.0-20220112143044-f35891e3ce7e/nin_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  	"errors"
    19  	"os"
    20  	"strings"
    21  	"testing"
    22  )
    23  
    24  // A base test fixture that includes a State object with a
    25  // builtin "cat" rule.
    26  type StateTestWithBuiltinRules struct {
    27  	t     *testing.T
    28  	state State
    29  }
    30  
    31  func NewStateTestWithBuiltinRules(t *testing.T) StateTestWithBuiltinRules {
    32  	s := StateTestWithBuiltinRules{
    33  		t:     t,
    34  		state: NewState(),
    35  	}
    36  	s.AddCatRule(&s.state)
    37  	return s
    38  }
    39  
    40  // Add a "cat" rule to \a state.  Used by some tests; it's
    41  // otherwise done by the ctor to state.
    42  func (s *StateTestWithBuiltinRules) AddCatRule(state *State) {
    43  	s.AssertParse(state, "rule cat\n  command = cat $in > $out\n", ParseManifestOpts{})
    44  }
    45  
    46  // Short way to get a Node by its path from state.
    47  func (s *StateTestWithBuiltinRules) GetNode(path string) *Node {
    48  	if strings.ContainsAny(path, "/\\") {
    49  		s.t.Helper()
    50  		s.t.Fatal(path)
    51  	}
    52  	return s.state.GetNode(path, 0)
    53  }
    54  
    55  func (s *StateTestWithBuiltinRules) AssertParse(state *State, input string, opts ParseManifestOpts) {
    56  	// In unit tests, inject the terminating 0 byte. In real code, it is injected
    57  	// by RealDiskInterface.ReadFile.
    58  	if err := ParseManifest(state, nil, opts, "input", []byte(input+"\x00")); err != nil {
    59  		s.t.Helper()
    60  		s.t.Fatal(err)
    61  	}
    62  	verifyGraph(s.t, state)
    63  }
    64  
    65  func (s *StateTestWithBuiltinRules) AssertHash(expected string, actual uint64) {
    66  	if HashCommand(expected) != actual {
    67  		s.t.Helper()
    68  		s.t.Fatalf("want %08x; got %08x", expected, actual)
    69  	}
    70  }
    71  
    72  func assertParseManifest(t *testing.T, input string, state *State) {
    73  	// In unit tests, inject the terminating 0 byte. In real code, it is injected
    74  	// by RealDiskInterface.ReadFile.
    75  	if err := ParseManifest(state, nil, ParseManifestOpts{}, "input", []byte(input+"\x00")); err != nil {
    76  		t.Helper()
    77  		t.Fatal(err)
    78  	}
    79  	verifyGraph(t, state)
    80  }
    81  
    82  func verifyGraph(t *testing.T, state *State) {
    83  	for _, e := range state.Edges {
    84  		if len(e.Outputs) == 0 {
    85  			t.Fatal("all edges need at least one output")
    86  		}
    87  		for _, inNode := range e.Inputs {
    88  			found := false
    89  			for _, oe := range inNode.OutEdges {
    90  				if oe == e {
    91  					found = true
    92  				}
    93  			}
    94  			if !found {
    95  				t.Fatal("each edge's inputs must have the edge as out-edge")
    96  			}
    97  		}
    98  		for _, outNode := range e.Outputs {
    99  			if outNode.InEdge != e {
   100  				t.Fatal("each edge's output must have the edge as in-edge")
   101  			}
   102  		}
   103  	}
   104  
   105  	// The union of all in- and out-edges of each nodes should be exactly edges.
   106  	nodeEdgeSet := map[*Edge]struct{}{}
   107  	for _, n := range state.Paths {
   108  		if n.InEdge != nil {
   109  			nodeEdgeSet[n.InEdge] = struct{}{}
   110  		}
   111  		for _, oe := range n.OutEdges {
   112  			nodeEdgeSet[oe] = struct{}{}
   113  		}
   114  	}
   115  	if len(state.Edges) != len(nodeEdgeSet) {
   116  		t.Fatal("the union of all in- and out-edges must match State.edges")
   117  	}
   118  }
   119  
   120  // An implementation of DiskInterface that uses an in-memory representation
   121  // of disk state.  It also logs file accesses and directory creations
   122  // so it can be used by tests to verify disk access patterns.
   123  type VirtualFileSystem struct {
   124  	// In the C++ code, it's an ordered set. The only test cases that depends on
   125  	// this is TestBuildTest_MakeDirs.
   126  	directoriesMade map[string]struct{}
   127  	filesRead       []string
   128  	files           FileMap
   129  	filesRemoved    map[string]struct{}
   130  	filesCreated    map[string]struct{}
   131  
   132  	// A simple fake timestamp for file operations.
   133  	now TimeStamp
   134  }
   135  
   136  // An entry for a single in-memory file.
   137  type Entry struct {
   138  	mtime     TimeStamp
   139  	statError error // If mtime is -1.
   140  	contents  []byte
   141  }
   142  type FileMap map[string]Entry
   143  
   144  func NewVirtualFileSystem() VirtualFileSystem {
   145  	return VirtualFileSystem{
   146  		directoriesMade: map[string]struct{}{},
   147  		files:           FileMap{},
   148  		filesRemoved:    map[string]struct{}{},
   149  		filesCreated:    map[string]struct{}{},
   150  		now:             1,
   151  	}
   152  }
   153  
   154  // Tick "time" forwards; subsequent file operations will be newer than
   155  // previous ones.
   156  func (v *VirtualFileSystem) Tick() TimeStamp {
   157  	v.now++
   158  	return v.now
   159  }
   160  
   161  // "Create" a file with contents.
   162  func (v *VirtualFileSystem) Create(path string, contents string) {
   163  	f := v.files[path]
   164  	f.mtime = v.now
   165  	// Make a copy in case it's a unsafeString() to a buffer that could be
   166  	// mutated later.
   167  	f.contents = []byte(contents)
   168  	v.files[path] = f
   169  	v.filesCreated[path] = struct{}{}
   170  }
   171  
   172  // DiskInterface
   173  func (v *VirtualFileSystem) Stat(path string) (TimeStamp, error) {
   174  	i, ok := v.files[path]
   175  	if ok {
   176  		return i.mtime, i.statError
   177  	}
   178  	return 0, nil
   179  }
   180  
   181  func (v *VirtualFileSystem) WriteFile(path string, contents string) error {
   182  	v.Create(path, contents)
   183  	return nil
   184  }
   185  
   186  func (v *VirtualFileSystem) MakeDir(path string) error {
   187  	// Should check if a file exists with the same name.
   188  	v.directoriesMade[path] = struct{}{}
   189  	return nil
   190  }
   191  
   192  func (v *VirtualFileSystem) ReadFile(path string) ([]byte, error) {
   193  	v.filesRead = append(v.filesRead, path)
   194  	i, ok := v.files[path]
   195  	if ok {
   196  		if len(i.contents) == 0 {
   197  			return nil, nil
   198  		}
   199  		// Return a copy since a lot of the code modify the buffer in-place.
   200  		n := make([]byte, len(i.contents)+1)
   201  		copy(n, i.contents)
   202  		return n, nil
   203  	}
   204  	return nil, os.ErrNotExist
   205  }
   206  
   207  func (v *VirtualFileSystem) RemoveFile(path string) error {
   208  	if _, ok := v.directoriesMade[path]; ok {
   209  		return errors.New("can't remove directory in unit tests; not true in practice")
   210  	}
   211  	if _, ok := v.files[path]; ok {
   212  		delete(v.files, path)
   213  		v.filesRemoved[path] = struct{}{}
   214  		return nil
   215  	}
   216  	return os.ErrNotExist
   217  }
   218  
   219  // CreateTempDirAndEnter creates a temporary directory and "cd" into it.
   220  func CreateTempDirAndEnter(t *testing.T) string {
   221  	old, err := os.Getwd()
   222  	if err != nil {
   223  		t.Fatal(err)
   224  	}
   225  	tempDir := t.TempDir()
   226  	if err := os.Chdir(tempDir); err != nil {
   227  		t.Fatal(err)
   228  	}
   229  	t.Cleanup(func() {
   230  		if err := os.Chdir(old); err != nil {
   231  			t.Error(err)
   232  		}
   233  	})
   234  	return tempDir
   235  }