1// Copyright 2019 The Chromium OS Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5package main
6
7import (
8	"errors"
9	"fmt"
10	"io"
11	"path/filepath"
12	"strings"
13	"testing"
14)
15
16func TestCallBisectDriver(t *testing.T) {
17	withBisectTestContext(t, func(ctx *testContext) {
18		ctx.env = []string{
19			"BISECT_STAGE=someBisectStage",
20			"BISECT_DIR=someBisectDir",
21		}
22		cmd := mustCallBisectDriver(ctx, callCompiler(ctx, ctx.cfg, ctx.newCommand(gccX86_64, mainCc)))
23		if err := verifyPath(cmd, "bisect_driver"); err != nil {
24			t.Error(err)
25		}
26		if err := verifyArgOrder(cmd,
27			"someBisectStage", "someBisectDir", filepath.Join(ctx.tempDir, gccX86_64+".real"), "--sysroot=.*", mainCc); err != nil {
28			t.Error(err)
29		}
30	})
31}
32
33func TestCallBisectDriverWithParamsFile(t *testing.T) {
34	withBisectTestContext(t, func(ctx *testContext) {
35		ctx.env = []string{
36			"BISECT_STAGE=someBisectStage",
37			"BISECT_DIR=someBisectDir",
38		}
39		paramsFile1 := filepath.Join(ctx.tempDir, "params1")
40		ctx.writeFile(paramsFile1, "a\n#comment\n@params2")
41		paramsFile2 := filepath.Join(ctx.tempDir, "params2")
42		ctx.writeFile(paramsFile2, "b\n"+mainCc)
43
44		cmd := mustCallBisectDriver(ctx, callCompiler(ctx, ctx.cfg, ctx.newCommand(gccX86_64, "@"+paramsFile1)))
45		if err := verifyArgOrder(cmd,
46			"a", "b", mainCc); err != nil {
47			t.Error(err)
48		}
49	})
50}
51
52func TestCallBisectDriverWithCCache(t *testing.T) {
53	withBisectTestContext(t, func(ctx *testContext) {
54		ctx.cfg.useCCache = true
55		cmd := ctx.must(callCompiler(ctx, ctx.cfg, ctx.newCommand(gccX86_64, mainCc)))
56		if err := verifyPath(cmd, "/usr/bin/env"); err != nil {
57			t.Error(err)
58		}
59		if err := verifyArgOrder(cmd, "python3", "/usr/bin/ccache"); err != nil {
60			t.Error(err)
61		}
62		if err := verifyEnvUpdate(cmd, "CCACHE_DIR=.*"); err != nil {
63			t.Error(err)
64		}
65	})
66}
67
68func TestDefaultBisectDirCros(t *testing.T) {
69	withBisectTestContext(t, func(ctx *testContext) {
70		ctx.env = []string{
71			"BISECT_STAGE=someBisectStage",
72		}
73		cmd := mustCallBisectDriver(ctx, callCompiler(ctx, ctx.cfg, ctx.newCommand(gccX86_64, mainCc)))
74		if err := verifyArgOrder(cmd,
75			"someBisectStage", "/tmp/sysroot_bisect"); err != nil {
76			t.Error(err)
77		}
78	})
79}
80
81func TestDefaultBisectDirAndroid(t *testing.T) {
82	withBisectTestContext(t, func(ctx *testContext) {
83		ctx.env = []string{
84			"BISECT_STAGE=someBisectStage",
85			"HOME=/somehome",
86		}
87		ctx.cfg.isAndroidWrapper = true
88		cmd := mustCallBisectDriver(ctx, callCompiler(ctx, ctx.cfg, ctx.newCommand(clangAndroid, mainCc)))
89		if err := verifyArgOrder(cmd,
90			"someBisectStage", filepath.Join("/somehome", "ANDROID_BISECT")); err != nil {
91			t.Error(err)
92		}
93	})
94}
95
96func TestForwardStdOutAndStdErrAndExitCodeFromBisect(t *testing.T) {
97	withBisectTestContext(t, func(ctx *testContext) {
98		ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error {
99			fmt.Fprint(stdout, "somemessage")
100			fmt.Fprint(stderr, "someerror")
101			return newExitCodeError(23)
102		}
103		exitCode := callCompiler(ctx, ctx.cfg, ctx.newCommand(gccX86_64, mainCc))
104		if exitCode != 23 {
105			t.Errorf("unexpected exit code. Got: %d", exitCode)
106		}
107		if ctx.stdoutString() != "somemessage" {
108			t.Errorf("stdout was not forwarded. Got: %s", ctx.stdoutString())
109		}
110		if ctx.stderrString() != "someerror" {
111			t.Errorf("stderr was not forwarded. Got: %s", ctx.stderrString())
112		}
113	})
114}
115
116func TestForwardGeneralErrorFromBisect(t *testing.T) {
117	withBisectTestContext(t, func(ctx *testContext) {
118		ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error {
119			return errors.New("someerror")
120		}
121		stderr := ctx.mustFail(callCompiler(ctx, ctx.cfg,
122			ctx.newCommand(gccX86_64, mainCc)))
123		if err := verifyInternalError(stderr); err != nil {
124			t.Fatal(err)
125		}
126		if !strings.Contains(stderr, "someerror") {
127			t.Errorf("unexpected error. Got: %s", stderr)
128		}
129	})
130}
131
132func withBisectTestContext(t *testing.T, work func(ctx *testContext)) {
133	withTestContext(t, func(ctx *testContext) {
134		ctx.env = []string{"BISECT_STAGE=xyz"}
135		// We execute the python script but replace the call to the bisect_driver with
136		// a mock that logs the data.
137		ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error {
138			if err := verifyPath(cmd, "/usr/bin/env"); err != nil {
139				return err
140			}
141			if cmd.Args[0] != "python3" {
142				return fmt.Errorf("expected a call to python. Got: %s", cmd.Args[0])
143			}
144			if cmd.Args[1] != "-c" {
145				return fmt.Errorf("expected an inline python script. Got: %s", cmd.Args)
146			}
147			script := cmd.Args[2]
148			mock := `
149class BisectDriver:
150	def __init__(self):
151		self.VALID_MODES = ['POPULATE_GOOD', 'POPULATE_BAD', 'TRIAGE']
152	def bisect_driver(self, bisect_stage, bisect_dir, execargs):
153		print('command bisect_driver')
154		print('arg %s' % bisect_stage)
155		print('arg %s' % bisect_dir)
156		for arg in execargs:
157			print('arg %s' % arg)
158
159bisect_driver = BisectDriver()
160`
161			script = mock + script
162			script = strings.Replace(script, "import bisect_driver", "", -1)
163			cmdCopy := *cmd
164			cmdCopy.Args = append(append(cmd.Args[:2], script), cmd.Args[3:]...)
165			// Evaluate the python script, but replace the call to the bisect_driver
166			// with a log statement so that we can assert it.
167			return runCmd(ctx, &cmdCopy, nil, stdout, stderr)
168		}
169		work(ctx)
170	})
171}
172
173func mustCallBisectDriver(ctx *testContext, exitCode int) *command {
174	ctx.must(exitCode)
175	cmd := &command{}
176	for _, line := range strings.Split(ctx.stdoutString(), "\n") {
177		if prefix := "command "; strings.HasPrefix(line, prefix) {
178			cmd.Path = line[len(prefix):]
179		} else if prefix := "arg "; strings.HasPrefix(line, prefix) {
180			cmd.Args = append(cmd.Args, line[len(prefix):])
181		}
182	}
183	return cmd
184}
185