c1d878159c
Test: m checkbuild Change-Id: I5135e0daecd20abce9a66631f5f55230168613de
423 lines
12 KiB
Go
423 lines
12 KiB
Go
// Copyright 2017 Google Inc. All rights reserved.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package microfactory
|
|
|
|
import (
|
|
"flag"
|
|
"io/ioutil"
|
|
"os"
|
|
"path/filepath"
|
|
"reflect"
|
|
"runtime"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestSimplePackagePathMap(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
pkgMap := pkgPathMappingVar{&Config{}}
|
|
flags := flag.NewFlagSet("", flag.ContinueOnError)
|
|
flags.Var(&pkgMap, "m", "")
|
|
err := flags.Parse([]string{
|
|
"-m", "android/soong=build/soong/",
|
|
"-m", "github.com/google/blueprint/=build/blueprint",
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
compare := func(got, want interface{}) {
|
|
if !reflect.DeepEqual(got, want) {
|
|
t.Errorf("Unexpected values in .pkgs:\nwant: %v\n got: %v",
|
|
want, got)
|
|
}
|
|
}
|
|
|
|
wantPkgs := []string{"android/soong", "github.com/google/blueprint"}
|
|
compare(pkgMap.pkgs, wantPkgs)
|
|
compare(pkgMap.paths[wantPkgs[0]], "build/soong")
|
|
compare(pkgMap.paths[wantPkgs[1]], "build/blueprint")
|
|
|
|
got, ok, err := pkgMap.Path("android/soong/ui/test")
|
|
if err != nil {
|
|
t.Error("Unexpected error in pkgMap.Path(soong):", err)
|
|
} else if !ok {
|
|
t.Error("Expected a result from pkgMap.Path(soong)")
|
|
} else {
|
|
compare(got, "build/soong/ui/test")
|
|
}
|
|
|
|
got, ok, err = pkgMap.Path("github.com/google/blueprint")
|
|
if err != nil {
|
|
t.Error("Unexpected error in pkgMap.Path(blueprint):", err)
|
|
} else if !ok {
|
|
t.Error("Expected a result from pkgMap.Path(blueprint)")
|
|
} else {
|
|
compare(got, "build/blueprint")
|
|
}
|
|
}
|
|
|
|
func TestBadPackagePathMap(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
pkgMap := pkgPathMappingVar{&Config{}}
|
|
if _, _, err := pkgMap.Path("testing"); err == nil {
|
|
t.Error("Expected error if no maps are specified")
|
|
}
|
|
if err := pkgMap.Set(""); err == nil {
|
|
t.Error("Expected error with blank argument, but none returned")
|
|
}
|
|
if err := pkgMap.Set("a=a"); err != nil {
|
|
t.Errorf("Unexpected error: %v", err)
|
|
}
|
|
if err := pkgMap.Set("a=b"); err == nil {
|
|
t.Error("Expected error with duplicate package prefix, but none returned")
|
|
}
|
|
if _, ok, err := pkgMap.Path("testing"); err != nil {
|
|
t.Errorf("Unexpected error: %v", err)
|
|
} else if ok {
|
|
t.Error("Expected testing to be consider in the stdlib")
|
|
}
|
|
}
|
|
|
|
// TestSingleBuild ensures that just a basic build works.
|
|
func TestSingleBuild(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
setupDir(t, func(config *Config, dir string, loadPkg loadPkgFunc) {
|
|
// The output binary
|
|
out := filepath.Join(dir, "out", "test")
|
|
|
|
pkg := loadPkg()
|
|
|
|
if err := pkg.Compile(config, filepath.Join(dir, "out")); err != nil {
|
|
t.Fatal("Got error when compiling:", err)
|
|
}
|
|
|
|
if err := pkg.Link(config, out); err != nil {
|
|
t.Fatal("Got error when linking:", err)
|
|
}
|
|
|
|
if _, err := os.Stat(out); err != nil {
|
|
t.Error("Cannot stat output:", err)
|
|
}
|
|
})
|
|
}
|
|
|
|
// testBuildAgain triggers two builds, running the modify function in between
|
|
// each build. It verifies that the second build did or did not actually need
|
|
// to rebuild anything based on the shouldRebuild argument.
|
|
func testBuildAgain(t *testing.T,
|
|
shouldRecompile, shouldRelink bool,
|
|
modify func(config *Config, dir string, loadPkg loadPkgFunc),
|
|
after func(pkg *GoPackage)) {
|
|
|
|
t.Parallel()
|
|
|
|
setupDir(t, func(config *Config, dir string, loadPkg loadPkgFunc) {
|
|
// The output binary
|
|
out := filepath.Join(dir, "out", "test")
|
|
|
|
pkg := loadPkg()
|
|
|
|
if err := pkg.Compile(config, filepath.Join(dir, "out")); err != nil {
|
|
t.Fatal("Got error when compiling:", err)
|
|
}
|
|
|
|
if err := pkg.Link(config, out); err != nil {
|
|
t.Fatal("Got error when linking:", err)
|
|
}
|
|
|
|
var firstTime time.Time
|
|
if stat, err := os.Stat(out); err == nil {
|
|
firstTime = stat.ModTime()
|
|
} else {
|
|
t.Fatal("Failed to stat output file:", err)
|
|
}
|
|
|
|
// mtime on HFS+ (the filesystem on darwin) are stored with 1
|
|
// second granularity, so the timestamp checks will fail unless
|
|
// we wait at least a second. Sleeping 1.1s to be safe.
|
|
if runtime.GOOS == "darwin" {
|
|
time.Sleep(1100 * time.Millisecond)
|
|
}
|
|
|
|
modify(config, dir, loadPkg)
|
|
|
|
pkg = loadPkg()
|
|
|
|
if err := pkg.Compile(config, filepath.Join(dir, "out")); err != nil {
|
|
t.Fatal("Got error when compiling:", err)
|
|
}
|
|
if shouldRecompile {
|
|
if !pkg.rebuilt {
|
|
t.Fatal("Package should have recompiled, but was not recompiled.")
|
|
}
|
|
} else {
|
|
if pkg.rebuilt {
|
|
t.Fatal("Package should not have needed to be recompiled, but was recompiled.")
|
|
}
|
|
}
|
|
|
|
if err := pkg.Link(config, out); err != nil {
|
|
t.Fatal("Got error while linking:", err)
|
|
}
|
|
if shouldRelink {
|
|
if !pkg.rebuilt {
|
|
t.Error("Package should have relinked, but was not relinked.")
|
|
}
|
|
} else {
|
|
if pkg.rebuilt {
|
|
t.Error("Package should not have needed to be relinked, but was relinked.")
|
|
}
|
|
}
|
|
|
|
if stat, err := os.Stat(out); err == nil {
|
|
if shouldRelink {
|
|
if stat.ModTime() == firstTime {
|
|
t.Error("Output timestamp should be different, but both were", firstTime)
|
|
}
|
|
} else {
|
|
if stat.ModTime() != firstTime {
|
|
t.Error("Output timestamp should be the same.")
|
|
t.Error(" first:", firstTime)
|
|
t.Error("second:", stat.ModTime())
|
|
}
|
|
}
|
|
} else {
|
|
t.Fatal("Failed to stat output file:", err)
|
|
}
|
|
|
|
after(pkg)
|
|
})
|
|
}
|
|
|
|
// TestRebuildAfterNoChanges ensures that we don't rebuild if nothing
|
|
// changes
|
|
func TestRebuildAfterNoChanges(t *testing.T) {
|
|
testBuildAgain(t, false, false, func(config *Config, dir string, loadPkg loadPkgFunc) {}, func(pkg *GoPackage) {})
|
|
}
|
|
|
|
// TestRebuildAfterTimestamp ensures that we don't rebuild because
|
|
// timestamps of important files have changed. We should only rebuild if the
|
|
// content hashes are different.
|
|
func TestRebuildAfterTimestampChange(t *testing.T) {
|
|
testBuildAgain(t, false, false, func(config *Config, dir string, loadPkg loadPkgFunc) {
|
|
// Ensure that we've spent some amount of time asleep
|
|
time.Sleep(100 * time.Millisecond)
|
|
|
|
newTime := time.Now().Local()
|
|
os.Chtimes(filepath.Join(dir, "test.fact"), newTime, newTime)
|
|
os.Chtimes(filepath.Join(dir, "main/main.go"), newTime, newTime)
|
|
os.Chtimes(filepath.Join(dir, "a/a.go"), newTime, newTime)
|
|
os.Chtimes(filepath.Join(dir, "a/b.go"), newTime, newTime)
|
|
os.Chtimes(filepath.Join(dir, "b/a.go"), newTime, newTime)
|
|
}, func(pkg *GoPackage) {})
|
|
}
|
|
|
|
// TestRebuildAfterGoChange ensures that we rebuild after a content change
|
|
// to a package's go file.
|
|
func TestRebuildAfterGoChange(t *testing.T) {
|
|
testBuildAgain(t, true, true, func(config *Config, dir string, loadPkg loadPkgFunc) {
|
|
if err := ioutil.WriteFile(filepath.Join(dir, "a", "a.go"), []byte(go_a_a+"\n"), 0666); err != nil {
|
|
t.Fatal("Error writing a/a.go:", err)
|
|
}
|
|
}, func(pkg *GoPackage) {
|
|
if !pkg.directDeps[0].rebuilt {
|
|
t.Fatal("android/soong/a should have rebuilt")
|
|
}
|
|
if !pkg.directDeps[1].rebuilt {
|
|
t.Fatal("android/soong/b should have rebuilt")
|
|
}
|
|
})
|
|
}
|
|
|
|
// TestRebuildAfterMainChange ensures that we don't rebuild any dependencies
|
|
// if only the main package's go files are touched.
|
|
func TestRebuildAfterMainChange(t *testing.T) {
|
|
testBuildAgain(t, true, true, func(config *Config, dir string, loadPkg loadPkgFunc) {
|
|
if err := ioutil.WriteFile(filepath.Join(dir, "main", "main.go"), []byte(go_main_main+"\n"), 0666); err != nil {
|
|
t.Fatal("Error writing main/main.go:", err)
|
|
}
|
|
}, func(pkg *GoPackage) {
|
|
if pkg.directDeps[0].rebuilt {
|
|
t.Fatal("android/soong/a should not have rebuilt")
|
|
}
|
|
if pkg.directDeps[1].rebuilt {
|
|
t.Fatal("android/soong/b should not have rebuilt")
|
|
}
|
|
})
|
|
}
|
|
|
|
// TestRebuildAfterRemoveOut ensures that we rebuild if the output file is
|
|
// missing, even if everything else doesn't need rebuilding.
|
|
func TestRebuildAfterRemoveOut(t *testing.T) {
|
|
testBuildAgain(t, false, true, func(config *Config, dir string, loadPkg loadPkgFunc) {
|
|
if err := os.Remove(filepath.Join(dir, "out", "test")); err != nil {
|
|
t.Fatal("Failed to remove output:", err)
|
|
}
|
|
}, func(pkg *GoPackage) {})
|
|
}
|
|
|
|
// TestRebuildAfterPartialBuild ensures that even if the build was interrupted
|
|
// between the recompile and relink stages, we'll still relink when we run again.
|
|
func TestRebuildAfterPartialBuild(t *testing.T) {
|
|
testBuildAgain(t, false, true, func(config *Config, dir string, loadPkg loadPkgFunc) {
|
|
if err := ioutil.WriteFile(filepath.Join(dir, "main", "main.go"), []byte(go_main_main+"\n"), 0666); err != nil {
|
|
t.Fatal("Error writing main/main.go:", err)
|
|
}
|
|
|
|
pkg := loadPkg()
|
|
|
|
if err := pkg.Compile(config, filepath.Join(dir, "out")); err != nil {
|
|
t.Fatal("Got error when compiling:", err)
|
|
}
|
|
if !pkg.rebuilt {
|
|
t.Fatal("Package should have recompiled, but was not recompiled.")
|
|
}
|
|
}, func(pkg *GoPackage) {})
|
|
}
|
|
|
|
// BenchmarkInitialBuild computes how long a clean build takes (for tiny test
|
|
// inputs).
|
|
func BenchmarkInitialBuild(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
setupDir(b, func(config *Config, dir string, loadPkg loadPkgFunc) {
|
|
pkg := loadPkg()
|
|
if err := pkg.Compile(config, filepath.Join(dir, "out")); err != nil {
|
|
b.Fatal("Got error when compiling:", err)
|
|
}
|
|
|
|
if err := pkg.Link(config, filepath.Join(dir, "out", "test")); err != nil {
|
|
b.Fatal("Got error when linking:", err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// BenchmarkMinIncrementalBuild computes how long an incremental build that
|
|
// doesn't actually need to build anything takes.
|
|
func BenchmarkMinIncrementalBuild(b *testing.B) {
|
|
setupDir(b, func(config *Config, dir string, loadPkg loadPkgFunc) {
|
|
pkg := loadPkg()
|
|
|
|
if err := pkg.Compile(config, filepath.Join(dir, "out")); err != nil {
|
|
b.Fatal("Got error when compiling:", err)
|
|
}
|
|
|
|
if err := pkg.Link(config, filepath.Join(dir, "out", "test")); err != nil {
|
|
b.Fatal("Got error when linking:", err)
|
|
}
|
|
|
|
b.ResetTimer()
|
|
|
|
for i := 0; i < b.N; i++ {
|
|
pkg := loadPkg()
|
|
|
|
if err := pkg.Compile(config, filepath.Join(dir, "out")); err != nil {
|
|
b.Fatal("Got error when compiling:", err)
|
|
}
|
|
|
|
if err := pkg.Link(config, filepath.Join(dir, "out", "test")); err != nil {
|
|
b.Fatal("Got error when linking:", err)
|
|
}
|
|
|
|
if pkg.rebuilt {
|
|
b.Fatal("Should not have rebuilt anything")
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
///////////////////////////////////////////////////////
|
|
// Templates used to create fake compilable packages //
|
|
///////////////////////////////////////////////////////
|
|
|
|
const go_main_main = `
|
|
package main
|
|
import (
|
|
"fmt"
|
|
"android/soong/a"
|
|
"android/soong/b"
|
|
)
|
|
func main() {
|
|
fmt.Println(a.Stdout, b.Stdout)
|
|
}
|
|
`
|
|
|
|
const go_a_a = `
|
|
package a
|
|
import "os"
|
|
var Stdout = os.Stdout
|
|
`
|
|
|
|
const go_a_b = `
|
|
package a
|
|
`
|
|
|
|
const go_b_a = `
|
|
package b
|
|
import "android/soong/a"
|
|
var Stdout = a.Stdout
|
|
`
|
|
|
|
type T interface {
|
|
Fatal(args ...interface{})
|
|
Fatalf(format string, args ...interface{})
|
|
}
|
|
|
|
type loadPkgFunc func() *GoPackage
|
|
|
|
func setupDir(t T, test func(config *Config, dir string, loadPkg loadPkgFunc)) {
|
|
dir, err := ioutil.TempDir("", "test")
|
|
if err != nil {
|
|
t.Fatalf("Error creating temporary directory: %#v", err)
|
|
}
|
|
defer os.RemoveAll(dir)
|
|
|
|
writeFile := func(name, contents string) {
|
|
if err := ioutil.WriteFile(filepath.Join(dir, name), []byte(contents), 0666); err != nil {
|
|
t.Fatalf("Error writing %q: %#v", name, err)
|
|
}
|
|
}
|
|
mkdir := func(name string) {
|
|
if err := os.Mkdir(filepath.Join(dir, name), 0777); err != nil {
|
|
t.Fatalf("Error creating %q directory: %#v", name, err)
|
|
}
|
|
}
|
|
mkdir("main")
|
|
mkdir("a")
|
|
mkdir("b")
|
|
writeFile("main/main.go", go_main_main)
|
|
writeFile("a/a.go", go_a_a)
|
|
writeFile("a/b.go", go_a_b)
|
|
writeFile("b/a.go", go_b_a)
|
|
|
|
config := &Config{}
|
|
config.Map("android/soong", dir)
|
|
|
|
loadPkg := func() *GoPackage {
|
|
pkg := &GoPackage{
|
|
Name: "main",
|
|
}
|
|
if err := pkg.FindDeps(config, filepath.Join(dir, "main")); err != nil {
|
|
t.Fatalf("Error finding deps: %v", err)
|
|
}
|
|
return pkg
|
|
}
|
|
|
|
test(config, dir, loadPkg)
|
|
}
|