github.com/tiagovtristao/plz@v13.4.0+incompatible/src/build/build_step_test.go (about) 1 // Tests around the main part of the build process. 2 // These are somewhat fiddly because by its nature the code has many side effects. 3 // We attempt to minimise some through mocking. 4 // 5 // Note that because the tests run in an indeterminate order and maybe in parallel 6 // they all have to be careful to use distinct build targets. 7 8 package build 9 10 import ( 11 "fmt" 12 "io" 13 "io/ioutil" 14 "os" 15 "path" 16 "strings" 17 "testing" 18 19 "github.com/stretchr/testify/assert" 20 "gopkg.in/op/go-logging.v1" 21 22 "github.com/thought-machine/please/src/core" 23 "github.com/thought-machine/please/src/fs" 24 ) 25 26 var cache core.Cache 27 28 func TestBuildTargetWithNoDeps(t *testing.T) { 29 state, target := newState("//package1:target1") 30 target.AddOutput("file1") 31 err := buildTarget(1, state, target) 32 assert.NoError(t, err) 33 assert.Equal(t, core.Built, target.State()) 34 } 35 36 func TestFailedBuildTarget(t *testing.T) { 37 state, target := newState("//package1:target1a") 38 target.Command = "false" 39 err := buildTarget(1, state, target) 40 assert.Error(t, err) 41 } 42 43 func TestBuildTargetWhichNeedsRebuilding(t *testing.T) { 44 // The output file for this target already exists, but it should still get rebuilt 45 // because there's no rule hash file. 46 state, target := newState("//package1:target2") 47 target.AddOutput("file2") 48 err := buildTarget(1, state, target) 49 assert.NoError(t, err) 50 assert.Equal(t, core.Built, target.State()) 51 } 52 53 func TestBuildTargetWhichDoesntNeedRebuilding(t *testing.T) { 54 // We write a rule hash for this target before building it, so we don't need to build again. 55 state, target := newState("//package1:target3") 56 target.AddOutput("file3") 57 assert.NoError(t, writeRuleHash(state, target)) 58 err := buildTarget(1, state, target) 59 assert.NoError(t, err) 60 assert.Equal(t, core.Reused, target.State()) 61 } 62 63 func TestModifiedBuildTargetStillNeedsRebuilding(t *testing.T) { 64 // Similar to above, but if we change the target such that the rule hash no longer matches, 65 // it should get rebuilt. 66 state, target := newState("//package1:target4") 67 target.AddOutput("file4") 68 assert.NoError(t, writeRuleHash(state, target)) 69 target.Command = "echo 'wibble wibble wibble' > $OUT" 70 target.RuleHash = nil // Have to force a reset of this 71 err := buildTarget(1, state, target) 72 assert.NoError(t, err) 73 assert.Equal(t, core.Built, target.State()) 74 } 75 76 func TestSymlinkedOutputs(t *testing.T) { 77 // Test behaviour when the output is a symlink. 78 state, target := newState("//package1:target5") 79 target.AddOutput("file5") 80 target.AddSource(core.FileLabel{File: "src5", Package: "package1"}) 81 target.Command = "ln -s $SRC $OUT" 82 err := buildTarget(1, state, target) 83 assert.NoError(t, err) 84 assert.Equal(t, core.Built, target.State()) 85 } 86 87 func TestPreBuildFunction(t *testing.T) { 88 // Test modifying a command in the pre-build function. 89 state, target := newState("//package1:target6") 90 target.AddOutput("file6") 91 target.Command = "" // Target now won't produce the needed output 92 target.PreBuildFunction = preBuildFunction(func(target *core.BuildTarget) error { 93 target.Command = "echo 'wibble wibble wibble' > $OUT" 94 return nil 95 }) 96 err := buildTarget(1, state, target) 97 assert.NoError(t, err) 98 assert.Equal(t, core.Built, target.State()) 99 } 100 101 func TestPostBuildFunction(t *testing.T) { 102 // Test modifying a command in the post-build function. 103 state, target := newState("//package1:target7") 104 target.Command = "echo 'wibble wibble wibble' | tee file7" 105 target.PostBuildFunction = postBuildFunction(func(target *core.BuildTarget, output string) error { 106 target.AddOutput("file7") 107 assert.Equal(t, "wibble wibble wibble", output) 108 return nil 109 }) 110 err := buildTarget(1, state, target) 111 assert.NoError(t, err) 112 assert.Equal(t, core.Built, target.State()) 113 assert.Equal(t, []string{"file7"}, target.Outputs()) 114 } 115 116 func TestCacheRetrieval(t *testing.T) { 117 // Test retrieving stuff from the cache 118 state, target := newState("//package1:target8") 119 target.AddOutput("file8") 120 target.Command = "false" // Will fail if we try to build it. 121 state.Cache = cache 122 err := buildTarget(1, state, target) 123 assert.NoError(t, err) 124 assert.Equal(t, core.Cached, target.State()) 125 } 126 127 func TestPostBuildFunctionAndCache(t *testing.T) { 128 // Test the often subtle and quick to anger interaction of post-build function and cache. 129 // In this case when it fails to retrieve the post-build output it should still call the function after building. 130 state, target := newState("//package1:target9") 131 target.AddOutput("file9") 132 target.Command = "echo 'wibble wibble wibble' | tee $OUT" 133 called := false 134 target.PostBuildFunction = postBuildFunction(func(target *core.BuildTarget, output string) error { 135 called = true 136 assert.Equal(t, "wibble wibble wibble", output) 137 return nil 138 }) 139 state.Cache = cache 140 err := buildTarget(1, state, target) 141 assert.NoError(t, err) 142 assert.Equal(t, core.Built, target.State()) 143 assert.True(t, called) 144 } 145 146 func TestPostBuildFunctionAndCache2(t *testing.T) { 147 // Test the often subtle and quick to anger interaction of post-build function and cache. 148 // In this case it succeeds in retrieving the post-build output but must still call the function. 149 state, target := newState("//package1:target10") 150 target.AddOutput("file10") 151 target.Command = "echo 'wibble wibble wibble' | tee $OUT" 152 called := false 153 target.PostBuildFunction = postBuildFunction(func(target *core.BuildTarget, output string) error { 154 assert.False(t, called, "Must only call post-build function once (issue #113)") 155 called = true 156 assert.Equal(t, "retrieved from cache", output) // comes from implementation below 157 return nil 158 }) 159 state.Cache = cache 160 err := buildTarget(1, state, target) 161 assert.NoError(t, err) 162 assert.Equal(t, core.Cached, target.State()) 163 assert.True(t, called) 164 } 165 166 func TestInitPyCreation(t *testing.T) { 167 state, _ := newState("//pypkg:wevs") 168 target1 := newPyFilegroup(state, "//pypkg:target1", "file1.py") 169 target2 := newPyFilegroup(state, "//pypkg:target2", "__init__.py") 170 assert.NoError(t, buildFilegroup(state, target1)) 171 assert.True(t, fs.FileExists("plz-out/gen/pypkg/__init__.py")) 172 assert.NoError(t, buildFilegroup(state, target2)) 173 d, err := ioutil.ReadFile("plz-out/gen/pypkg/__init__.py") 174 assert.NoError(t, err) 175 assert.Equal(t, `"""output from //pypkg:target2"""`, strings.TrimSpace(string(d))) 176 } 177 178 func TestRecursiveInitPyCreation(t *testing.T) { 179 state, _ := newState("//package1/package2:wevs") 180 target1 := newPyFilegroup(state, "//package1/package2:target1", "file1.py") 181 assert.NoError(t, buildFilegroup(state, target1)) 182 assert.True(t, fs.FileExists("plz-out/gen/package1/package2/__init__.py")) 183 assert.True(t, fs.FileExists("plz-out/gen/package1/__init__.py")) 184 } 185 186 func TestCreatePlzOutGo(t *testing.T) { 187 state, target := newState("//gopkg:target") 188 target.AddLabel("link:plz-out/go/${PKG}/src") 189 target.AddOutput("file1.go") 190 assert.False(t, fs.PathExists("plz-out/go")) 191 assert.NoError(t, buildTarget(1, state, target)) 192 assert.True(t, fs.PathExists("plz-out/go/gopkg/src/file1.go")) 193 } 194 195 func TestLicenceEnforcement(t *testing.T) { 196 state, target := newState("//pkg:good") 197 state.Config.Licences.Reject = append(state.Config.Licences.Reject, "gpl") 198 state.Config.Licences.Accept = append(state.Config.Licences.Accept, "mit") 199 200 // Target specifying no licence should not panic. 201 checkLicences(state, target) 202 203 // A license (non case sensitive) that is not in the list of accepted licenses will panic. 204 assert.Panics(t, func() { 205 target.Licences = append(target.Licences, "Bsd") 206 checkLicences(state, target) 207 }, "A target with a non-accepted licence will panic") 208 209 // Accepting bsd should resolve the panic 210 state.Config.Licences.Accept = append(state.Config.Licences.Accept, "BSD") 211 checkLicences(state, target) 212 213 // Now construct a new "bad" target. 214 state, target = newState("//pkg:bad") 215 state.Config.Licences.Reject = append(state.Config.Licences.Reject, "gpl") 216 state.Config.Licences.Accept = append(state.Config.Licences.Accept, "mit") 217 218 // Adding an explicitly rejected licence should panic no matter what. 219 target.Licences = append(target.Licences, "GPL") 220 assert.Panics(t, func() { 221 checkLicences(state, target) 222 }, "Trying to add GPL should panic (case insensitive)") 223 } 224 225 func TestFileGroupBinDir(t *testing.T) { 226 state, target := newState("//package1:bindir") 227 //target.AddOutput("test_data") 228 target.AddSource(core.FileLabel{File: "package2", Package: target.Label.PackageName}) 229 target.IsBinary = true 230 target.IsFilegroup = true 231 232 err := buildFilegroup(state, target) 233 assert.NoError(t, err) 234 235 assert.True(t, fs.PathExists("plz-out/bin/package1/package2/")) 236 assert.True(t, fs.FileExists("plz-out/bin/package1/package2/file1.py")) 237 assert.True(t, fs.IsDirectory("plz-out/bin/package1/package2/")) 238 239 // Ensure correct permission on directory 240 info, err := os.Stat("plz-out/bin/package1/package2/") 241 assert.NoError(t, err) 242 assert.Equal(t, os.FileMode(0755), info.Mode().Perm()) 243 } 244 245 func newState(label string) (*core.BuildState, *core.BuildTarget) { 246 config, _ := core.ReadConfigFiles(nil, "") 247 state := core.NewBuildState(1, nil, 4, config) 248 target := core.NewBuildTarget(core.ParseBuildLabel(label, "")) 249 target.Command = fmt.Sprintf("echo 'output of %s' > $OUT", target.Label) 250 state.Graph.AddTarget(target) 251 state.Parser = &fakeParser{} 252 return state, target 253 } 254 255 func newPyFilegroup(state *core.BuildState, label, filename string) *core.BuildTarget { 256 target := core.NewBuildTarget(core.ParseBuildLabel(label, "")) 257 target.AddSource(core.FileLabel{File: filename, Package: target.Label.PackageName}) 258 target.AddOutput(filename) 259 target.AddLabel("py") 260 target.IsFilegroup = true 261 state.Graph.AddTarget(target) 262 return target 263 } 264 265 // Fake cache implementation with hardcoded behaviour for the various tests above. 266 type mockCache struct{} 267 268 func (*mockCache) Store(target *core.BuildTarget, key []byte, files ...string) { 269 } 270 271 func (*mockCache) StoreExtra(target *core.BuildTarget, key []byte, file string) { 272 } 273 274 func (*mockCache) Retrieve(target *core.BuildTarget, key []byte) bool { 275 if target.Label.Name == "target8" { 276 ioutil.WriteFile("plz-out/gen/package1/file8", []byte("retrieved from cache"), 0664) 277 return true 278 } else if target.Label.Name == "target10" { 279 ioutil.WriteFile("plz-out/gen/package1/file10", []byte("retrieved from cache"), 0664) 280 return true 281 } 282 return false 283 } 284 285 func (*mockCache) RetrieveExtra(target *core.BuildTarget, key []byte, file string) bool { 286 if target.Label.Name == "target10" && file == target.PostBuildOutputFileName() { 287 ioutil.WriteFile(postBuildOutputFileName(target), []byte("retrieved from cache"), 0664) 288 return true 289 } 290 return false 291 } 292 293 func (*mockCache) Clean(target *core.BuildTarget) {} 294 func (*mockCache) CleanAll() {} 295 func (*mockCache) Shutdown() {} 296 297 type fakeParser struct { 298 } 299 300 func (fake *fakeParser) ParseFile(state *core.BuildState, pkg *core.Package, filename string) error { 301 return nil 302 } 303 304 func (fake *fakeParser) ParseReader(state *core.BuildState, pkg *core.Package, r io.ReadSeeker) error { 305 return nil 306 } 307 308 func (fake *fakeParser) RunPreBuildFunction(threadID int, state *core.BuildState, target *core.BuildTarget) error { 309 return target.PreBuildFunction.Call(target) 310 } 311 312 func (fake *fakeParser) RunPostBuildFunction(threadID int, state *core.BuildState, target *core.BuildTarget, output string) error { 313 return target.PostBuildFunction.Call(target, output) 314 } 315 316 type preBuildFunction func(*core.BuildTarget) error 317 type postBuildFunction func(*core.BuildTarget, string) error 318 319 func (f preBuildFunction) Call(target *core.BuildTarget) error { return f(target) } 320 func (f preBuildFunction) String() string { return "" } 321 func (f postBuildFunction) Call(target *core.BuildTarget, output string) error { 322 return f(target, output) 323 } 324 func (f postBuildFunction) String() string { return "" } 325 326 func TestMain(m *testing.M) { 327 cache = &mockCache{} 328 backend := logging.NewLogBackend(os.Stderr, "", 0) 329 backendLeveled := logging.AddModuleLevel(backend) 330 backendLeveled.SetLevel(logging.DEBUG, "") 331 logging.SetBackend(backend, backendLeveled) 332 // Move ourselves to the root of the test data tree 333 wd, _ := os.Getwd() 334 core.RepoRoot = path.Join(wd, "src/build/test_data") 335 Init(nil) 336 if err := os.Chdir(core.RepoRoot); err != nil { 337 panic(err) 338 } 339 os.Exit(m.Run()) 340 }