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 }