1// Copyright 2017 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 15package microfactory 16 17import ( 18 "flag" 19 "io/ioutil" 20 "os" 21 "path/filepath" 22 "reflect" 23 "runtime" 24 "testing" 25 "time" 26) 27 28func TestSimplePackagePathMap(t *testing.T) { 29 t.Parallel() 30 31 pkgMap := pkgPathMappingVar{&Config{}} 32 flags := flag.NewFlagSet("", flag.ContinueOnError) 33 flags.Var(&pkgMap, "m", "") 34 err := flags.Parse([]string{ 35 "-m", "android/soong=build/soong/", 36 "-m", "github.com/google/blueprint/=build/blueprint", 37 }) 38 if err != nil { 39 t.Fatal(err) 40 } 41 42 compare := func(got, want interface{}) { 43 if !reflect.DeepEqual(got, want) { 44 t.Errorf("Unexpected values in .pkgs:\nwant: %v\n got: %v", 45 want, got) 46 } 47 } 48 49 wantPkgs := []string{"android/soong", "github.com/google/blueprint"} 50 compare(pkgMap.pkgs, wantPkgs) 51 compare(pkgMap.paths[wantPkgs[0]], "build/soong") 52 compare(pkgMap.paths[wantPkgs[1]], "build/blueprint") 53 54 got, ok, err := pkgMap.Path("android/soong/ui/test") 55 if err != nil { 56 t.Error("Unexpected error in pkgMap.Path(soong):", err) 57 } else if !ok { 58 t.Error("Expected a result from pkgMap.Path(soong)") 59 } else { 60 compare(got, "build/soong/ui/test") 61 } 62 63 got, ok, err = pkgMap.Path("github.com/google/blueprint") 64 if err != nil { 65 t.Error("Unexpected error in pkgMap.Path(blueprint):", err) 66 } else if !ok { 67 t.Error("Expected a result from pkgMap.Path(blueprint)") 68 } else { 69 compare(got, "build/blueprint") 70 } 71} 72 73func TestBadPackagePathMap(t *testing.T) { 74 t.Parallel() 75 76 pkgMap := pkgPathMappingVar{&Config{}} 77 if _, _, err := pkgMap.Path("testing"); err == nil { 78 t.Error("Expected error if no maps are specified") 79 } 80 if err := pkgMap.Set(""); err == nil { 81 t.Error("Expected error with blank argument, but none returned") 82 } 83 if err := pkgMap.Set("a=a"); err != nil { 84 t.Errorf("Unexpected error: %v", err) 85 } 86 if err := pkgMap.Set("a=b"); err == nil { 87 t.Error("Expected error with duplicate package prefix, but none returned") 88 } 89 if _, ok, err := pkgMap.Path("testing"); err != nil { 90 t.Errorf("Unexpected error: %v", err) 91 } else if ok { 92 t.Error("Expected testing to be consider in the stdlib") 93 } 94} 95 96// TestSingleBuild ensures that just a basic build works. 97func TestSingleBuild(t *testing.T) { 98 t.Parallel() 99 100 setupDir(t, func(config *Config, dir string, loadPkg loadPkgFunc) { 101 // The output binary 102 out := filepath.Join(dir, "out", "test") 103 104 pkg := loadPkg() 105 106 if err := pkg.Compile(config, filepath.Join(dir, "out")); err != nil { 107 t.Fatal("Got error when compiling:", err) 108 } 109 110 if err := pkg.Link(config, out); err != nil { 111 t.Fatal("Got error when linking:", err) 112 } 113 114 if _, err := os.Stat(out); err != nil { 115 t.Error("Cannot stat output:", err) 116 } 117 }) 118} 119 120// testBuildAgain triggers two builds, running the modify function in between 121// each build. It verifies that the second build did or did not actually need 122// to rebuild anything based on the shouldRebuild argument. 123func testBuildAgain(t *testing.T, 124 shouldRecompile, shouldRelink bool, 125 modify func(config *Config, dir string, loadPkg loadPkgFunc), 126 after func(pkg *GoPackage)) { 127 128 t.Parallel() 129 130 setupDir(t, func(config *Config, dir string, loadPkg loadPkgFunc) { 131 // The output binary 132 out := filepath.Join(dir, "out", "test") 133 134 pkg := loadPkg() 135 136 if err := pkg.Compile(config, filepath.Join(dir, "out")); err != nil { 137 t.Fatal("Got error when compiling:", err) 138 } 139 140 if err := pkg.Link(config, out); err != nil { 141 t.Fatal("Got error when linking:", err) 142 } 143 144 var firstTime time.Time 145 if stat, err := os.Stat(out); err == nil { 146 firstTime = stat.ModTime() 147 } else { 148 t.Fatal("Failed to stat output file:", err) 149 } 150 151 // mtime on HFS+ (the filesystem on darwin) are stored with 1 152 // second granularity, so the timestamp checks will fail unless 153 // we wait at least a second. Sleeping 1.1s to be safe. 154 if runtime.GOOS == "darwin" { 155 time.Sleep(1100 * time.Millisecond) 156 } 157 158 modify(config, dir, loadPkg) 159 160 pkg = loadPkg() 161 162 if err := pkg.Compile(config, filepath.Join(dir, "out")); err != nil { 163 t.Fatal("Got error when compiling:", err) 164 } 165 if shouldRecompile { 166 if !pkg.rebuilt { 167 t.Fatal("Package should have recompiled, but was not recompiled.") 168 } 169 } else { 170 if pkg.rebuilt { 171 t.Fatal("Package should not have needed to be recompiled, but was recompiled.") 172 } 173 } 174 175 if err := pkg.Link(config, out); err != nil { 176 t.Fatal("Got error while linking:", err) 177 } 178 if shouldRelink { 179 if !pkg.rebuilt { 180 t.Error("Package should have relinked, but was not relinked.") 181 } 182 } else { 183 if pkg.rebuilt { 184 t.Error("Package should not have needed to be relinked, but was relinked.") 185 } 186 } 187 188 if stat, err := os.Stat(out); err == nil { 189 if shouldRelink { 190 if stat.ModTime() == firstTime { 191 t.Error("Output timestamp should be different, but both were", firstTime) 192 } 193 } else { 194 if stat.ModTime() != firstTime { 195 t.Error("Output timestamp should be the same.") 196 t.Error(" first:", firstTime) 197 t.Error("second:", stat.ModTime()) 198 } 199 } 200 } else { 201 t.Fatal("Failed to stat output file:", err) 202 } 203 204 after(pkg) 205 }) 206} 207 208// TestRebuildAfterNoChanges ensures that we don't rebuild if nothing 209// changes 210func TestRebuildAfterNoChanges(t *testing.T) { 211 testBuildAgain(t, false, false, func(config *Config, dir string, loadPkg loadPkgFunc) {}, func(pkg *GoPackage) {}) 212} 213 214// TestRebuildAfterTimestamp ensures that we don't rebuild because 215// timestamps of important files have changed. We should only rebuild if the 216// content hashes are different. 217func TestRebuildAfterTimestampChange(t *testing.T) { 218 testBuildAgain(t, false, false, func(config *Config, dir string, loadPkg loadPkgFunc) { 219 // Ensure that we've spent some amount of time asleep 220 time.Sleep(100 * time.Millisecond) 221 222 newTime := time.Now().Local() 223 os.Chtimes(filepath.Join(dir, "test.fact"), newTime, newTime) 224 os.Chtimes(filepath.Join(dir, "main/main.go"), newTime, newTime) 225 os.Chtimes(filepath.Join(dir, "a/a.go"), newTime, newTime) 226 os.Chtimes(filepath.Join(dir, "a/b.go"), newTime, newTime) 227 os.Chtimes(filepath.Join(dir, "b/a.go"), newTime, newTime) 228 }, func(pkg *GoPackage) {}) 229} 230 231// TestRebuildAfterGoChange ensures that we rebuild after a content change 232// to a package's go file. 233func TestRebuildAfterGoChange(t *testing.T) { 234 testBuildAgain(t, true, true, func(config *Config, dir string, loadPkg loadPkgFunc) { 235 if err := ioutil.WriteFile(filepath.Join(dir, "a", "a.go"), []byte(go_a_a+"\n"), 0666); err != nil { 236 t.Fatal("Error writing a/a.go:", err) 237 } 238 }, func(pkg *GoPackage) { 239 if !pkg.directDeps[0].rebuilt { 240 t.Fatal("android/soong/a should have rebuilt") 241 } 242 if !pkg.directDeps[1].rebuilt { 243 t.Fatal("android/soong/b should have rebuilt") 244 } 245 }) 246} 247 248// TestRebuildAfterMainChange ensures that we don't rebuild any dependencies 249// if only the main package's go files are touched. 250func TestRebuildAfterMainChange(t *testing.T) { 251 testBuildAgain(t, true, true, func(config *Config, dir string, loadPkg loadPkgFunc) { 252 if err := ioutil.WriteFile(filepath.Join(dir, "main", "main.go"), []byte(go_main_main+"\n"), 0666); err != nil { 253 t.Fatal("Error writing main/main.go:", err) 254 } 255 }, func(pkg *GoPackage) { 256 if pkg.directDeps[0].rebuilt { 257 t.Fatal("android/soong/a should not have rebuilt") 258 } 259 if pkg.directDeps[1].rebuilt { 260 t.Fatal("android/soong/b should not have rebuilt") 261 } 262 }) 263} 264 265// TestRebuildAfterRemoveOut ensures that we rebuild if the output file is 266// missing, even if everything else doesn't need rebuilding. 267func TestRebuildAfterRemoveOut(t *testing.T) { 268 testBuildAgain(t, false, true, func(config *Config, dir string, loadPkg loadPkgFunc) { 269 if err := os.Remove(filepath.Join(dir, "out", "test")); err != nil { 270 t.Fatal("Failed to remove output:", err) 271 } 272 }, func(pkg *GoPackage) {}) 273} 274 275// TestRebuildAfterPartialBuild ensures that even if the build was interrupted 276// between the recompile and relink stages, we'll still relink when we run again. 277func TestRebuildAfterPartialBuild(t *testing.T) { 278 testBuildAgain(t, false, true, func(config *Config, dir string, loadPkg loadPkgFunc) { 279 if err := ioutil.WriteFile(filepath.Join(dir, "main", "main.go"), []byte(go_main_main+"\n"), 0666); err != nil { 280 t.Fatal("Error writing main/main.go:", err) 281 } 282 283 pkg := loadPkg() 284 285 if err := pkg.Compile(config, filepath.Join(dir, "out")); err != nil { 286 t.Fatal("Got error when compiling:", err) 287 } 288 if !pkg.rebuilt { 289 t.Fatal("Package should have recompiled, but was not recompiled.") 290 } 291 }, func(pkg *GoPackage) {}) 292} 293 294// BenchmarkInitialBuild computes how long a clean build takes (for tiny test 295// inputs). 296func BenchmarkInitialBuild(b *testing.B) { 297 for i := 0; i < b.N; i++ { 298 setupDir(b, func(config *Config, dir string, loadPkg loadPkgFunc) { 299 pkg := loadPkg() 300 if err := pkg.Compile(config, filepath.Join(dir, "out")); err != nil { 301 b.Fatal("Got error when compiling:", err) 302 } 303 304 if err := pkg.Link(config, filepath.Join(dir, "out", "test")); err != nil { 305 b.Fatal("Got error when linking:", err) 306 } 307 }) 308 } 309} 310 311// BenchmarkMinIncrementalBuild computes how long an incremental build that 312// doesn't actually need to build anything takes. 313func BenchmarkMinIncrementalBuild(b *testing.B) { 314 setupDir(b, func(config *Config, dir string, loadPkg loadPkgFunc) { 315 pkg := loadPkg() 316 317 if err := pkg.Compile(config, filepath.Join(dir, "out")); err != nil { 318 b.Fatal("Got error when compiling:", err) 319 } 320 321 if err := pkg.Link(config, filepath.Join(dir, "out", "test")); err != nil { 322 b.Fatal("Got error when linking:", err) 323 } 324 325 b.ResetTimer() 326 327 for i := 0; i < b.N; i++ { 328 pkg := loadPkg() 329 330 if err := pkg.Compile(config, filepath.Join(dir, "out")); err != nil { 331 b.Fatal("Got error when compiling:", err) 332 } 333 334 if err := pkg.Link(config, filepath.Join(dir, "out", "test")); err != nil { 335 b.Fatal("Got error when linking:", err) 336 } 337 338 if pkg.rebuilt { 339 b.Fatal("Should not have rebuilt anything") 340 } 341 } 342 }) 343} 344 345/////////////////////////////////////////////////////// 346// Templates used to create fake compilable packages // 347/////////////////////////////////////////////////////// 348 349const go_main_main = ` 350package main 351import ( 352 "fmt" 353 "android/soong/a" 354 "android/soong/b" 355) 356func main() { 357 fmt.Println(a.Stdout, b.Stdout) 358} 359` 360 361const go_a_a = ` 362package a 363import "os" 364var Stdout = os.Stdout 365` 366 367const go_a_b = ` 368package a 369` 370 371const go_b_a = ` 372package b 373import "android/soong/a" 374var Stdout = a.Stdout 375` 376 377type T interface { 378 Fatal(args ...interface{}) 379 Fatalf(format string, args ...interface{}) 380} 381 382type loadPkgFunc func() *GoPackage 383 384func setupDir(t T, test func(config *Config, dir string, loadPkg loadPkgFunc)) { 385 dir, err := ioutil.TempDir("", "test") 386 if err != nil { 387 t.Fatalf("Error creating temporary directory: %#v", err) 388 } 389 defer os.RemoveAll(dir) 390 391 writeFile := func(name, contents string) { 392 if err := ioutil.WriteFile(filepath.Join(dir, name), []byte(contents), 0666); err != nil { 393 t.Fatalf("Error writing %q: %#v", name, err) 394 } 395 } 396 mkdir := func(name string) { 397 if err := os.Mkdir(filepath.Join(dir, name), 0777); err != nil { 398 t.Fatalf("Error creating %q directory: %#v", name, err) 399 } 400 } 401 mkdir("main") 402 mkdir("a") 403 mkdir("b") 404 writeFile("main/main.go", go_main_main) 405 writeFile("a/a.go", go_a_a) 406 writeFile("a/b.go", go_a_b) 407 writeFile("b/a.go", go_b_a) 408 409 config := &Config{} 410 config.Map("android/soong", dir) 411 412 loadPkg := func() *GoPackage { 413 pkg := &GoPackage{ 414 Name: "main", 415 } 416 if err := pkg.FindDeps(config, filepath.Join(dir, "main")); err != nil { 417 t.Fatalf("Error finding deps: %v", err) 418 } 419 return pkg 420 } 421 422 test(config, dir, loadPkg) 423} 424