blob: 296a8446d18a05a688daa874992c59819ea80588 [file] [log] [blame]
Dan Willemsen0043c0e2016-09-18 20:27:41 -07001// 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 main
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 var pkgMap pkgPathMapping
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 var pkgMap pkgPathMapping
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.Error("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.Error("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(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(filepath.Join(dir, "out"), ""); err != nil {
107 t.Fatalf("Got error when compiling:", err)
108 }
109
110 if err := pkg.Link(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(dir string, loadPkg loadPkgFunc),
126 after func(pkg *GoPackage)) {
127
128 t.Parallel()
129
130 setupDir(t, func(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(filepath.Join(dir, "out"), ""); err != nil {
137 t.Fatal("Got error when compiling:", err)
138 }
139
140 if err := pkg.Link(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(dir, loadPkg)
159
160 pkg = loadPkg()
161
162 if err := pkg.Compile(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(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(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(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(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.deps[0].rebuilt {
240 t.Fatal("android/soong/a should have rebuilt")
241 }
242 if !pkg.deps[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(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.deps[0].rebuilt {
257 t.Fatal("android/soong/a should not have rebuilt")
258 }
259 if pkg.deps[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(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(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(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(dir string, loadPkg loadPkgFunc) {
299 pkg := loadPkg()
300 if err := pkg.Compile(filepath.Join(dir, "out"), ""); err != nil {
301 b.Fatal("Got error when compiling:", err)
302 }
303
304 if err := pkg.Link(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(dir string, loadPkg loadPkgFunc) {
315 pkg := loadPkg()
316
317 if err := pkg.Compile(filepath.Join(dir, "out"), ""); err != nil {
318 b.Fatal("Got error when compiling:", err)
319 }
320
321 if err := pkg.Link(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(filepath.Join(dir, "out"), ""); err != nil {
331 b.Fatal("Got error when compiling:", err)
332 }
333
334 if err := pkg.Link(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(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 loadPkg := func() *GoPackage {
410 pkg := &GoPackage{
411 Name: "main",
412 }
413 pkgMap := &pkgPathMapping{}
414 pkgMap.Set("android/soong=" + dir)
415 if err := pkg.FindDeps(filepath.Join(dir, "main"), pkgMap); err != nil {
416 t.Fatalf("Error finding deps: %v", err)
417 }
418 return pkg
419 }
420
421 test(dir, loadPkg)
422}