// Copyright 2019 The ChromiumOS Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package main import ( "errors" "fmt" "io" "path/filepath" "strings" "testing" ) func TestCallBisectDriver(t *testing.T) { withBisectTestContext(t, func(ctx *testContext) { ctx.env = []string{ "BISECT_STAGE=someBisectStage", "BISECT_DIR=someBisectDir", } cmd := mustCallBisectDriver(ctx, callCompiler(ctx, ctx.cfg, ctx.newCommand(gccX86_64, mainCc))) if err := verifyPath(cmd, "bisect_driver"); err != nil { t.Error(err) } if err := verifyArgOrder(cmd, "someBisectStage", "someBisectDir", filepath.Join(ctx.tempDir, gccX86_64+".real"), "--sysroot=.*", mainCc); err != nil { t.Error(err) } }) } func TestCallBisectDriverWithParamsFile(t *testing.T) { withBisectTestContext(t, func(ctx *testContext) { ctx.env = []string{ "BISECT_STAGE=someBisectStage", "BISECT_DIR=someBisectDir", } paramsFile1 := filepath.Join(ctx.tempDir, "params1") ctx.writeFile(paramsFile1, "a\n#comment\n@params2") paramsFile2 := filepath.Join(ctx.tempDir, "params2") ctx.writeFile(paramsFile2, "b\n"+mainCc) cmd := mustCallBisectDriver(ctx, callCompiler(ctx, ctx.cfg, ctx.newCommand(gccX86_64, "@"+paramsFile1))) if err := verifyArgOrder(cmd, "a", "b", mainCc); err != nil { t.Error(err) } }) } func TestCallBisectDriverWithCCache(t *testing.T) { withBisectTestContext(t, func(ctx *testContext) { ctx.cfg.useCCache = true cmd := ctx.must(callCompiler(ctx, ctx.cfg, ctx.newCommand(gccX86_64, mainCc))) if err := verifyPath(cmd, "/usr/bin/env"); err != nil { t.Error(err) } if err := verifyArgOrder(cmd, "python3", "/usr/bin/ccache"); err != nil { t.Error(err) } if err := verifyEnvUpdate(cmd, "CCACHE_DIR=.*"); err != nil { t.Error(err) } }) } func TestDefaultBisectDirCros(t *testing.T) { withBisectTestContext(t, func(ctx *testContext) { ctx.env = []string{ "BISECT_STAGE=someBisectStage", } cmd := mustCallBisectDriver(ctx, callCompiler(ctx, ctx.cfg, ctx.newCommand(gccX86_64, mainCc))) if err := verifyArgOrder(cmd, "someBisectStage", "/tmp/sysroot_bisect"); err != nil { t.Error(err) } }) } func TestDefaultBisectDirAndroid(t *testing.T) { withBisectTestContext(t, func(ctx *testContext) { ctx.env = []string{ "BISECT_STAGE=someBisectStage", "HOME=/somehome", } ctx.cfg.isAndroidWrapper = true cmd := mustCallBisectDriver(ctx, callCompiler(ctx, ctx.cfg, ctx.newCommand(clangAndroid, mainCc))) if err := verifyArgOrder(cmd, "someBisectStage", filepath.Join("/somehome", "ANDROID_BISECT")); err != nil { t.Error(err) } }) } func TestForwardStdOutAndStdErrAndExitCodeFromBisect(t *testing.T) { withBisectTestContext(t, func(ctx *testContext) { ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error { fmt.Fprint(stdout, "somemessage") fmt.Fprint(stderr, "someerror") return newExitCodeError(23) } exitCode := callCompiler(ctx, ctx.cfg, ctx.newCommand(gccX86_64, mainCc)) if exitCode != 23 { t.Errorf("unexpected exit code. Got: %d", exitCode) } if ctx.stdoutString() != "somemessage" { t.Errorf("stdout was not forwarded. Got: %s", ctx.stdoutString()) } if ctx.stderrString() != "someerror" { t.Errorf("stderr was not forwarded. Got: %s", ctx.stderrString()) } }) } func TestForwardGeneralErrorFromBisect(t *testing.T) { withBisectTestContext(t, func(ctx *testContext) { ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error { return errors.New("someerror") } stderr := ctx.mustFail(callCompiler(ctx, ctx.cfg, ctx.newCommand(gccX86_64, mainCc))) if err := verifyInternalError(stderr); err != nil { t.Fatal(err) } if !strings.Contains(stderr, "someerror") { t.Errorf("unexpected error. Got: %s", stderr) } }) } func withBisectTestContext(t *testing.T, work func(ctx *testContext)) { withTestContext(t, func(ctx *testContext) { ctx.env = []string{"BISECT_STAGE=xyz"} // We execute the python script but replace the call to the bisect_driver with // a mock that logs the data. ctx.cmdMock = func(cmd *command, stdin io.Reader, stdout io.Writer, stderr io.Writer) error { if err := verifyPath(cmd, "/usr/bin/env"); err != nil { return err } if cmd.Args[0] != "python3" { return fmt.Errorf("expected a call to python. Got: %s", cmd.Args[0]) } if cmd.Args[1] != "-c" { return fmt.Errorf("expected an inline python script. Got: %s", cmd.Args) } script := cmd.Args[2] mock := ` class BisectDriver: def __init__(self): self.VALID_MODES = ['POPULATE_GOOD', 'POPULATE_BAD', 'TRIAGE'] def bisect_driver(self, bisect_stage, bisect_dir, execargs): print('command bisect_driver') print('arg %s' % bisect_stage) print('arg %s' % bisect_dir) for arg in execargs: print('arg %s' % arg) bisect_driver = BisectDriver() ` script = mock + script script = strings.Replace(script, "import bisect_driver", "", -1) cmdCopy := *cmd cmdCopy.Args = append(append(cmd.Args[:2], script), cmd.Args[3:]...) // Evaluate the python script, but replace the call to the bisect_driver // with a log statement so that we can assert it. return runCmd(ctx, &cmdCopy, nil, stdout, stderr) } work(ctx) }) } func mustCallBisectDriver(ctx *testContext, exitCode int) *command { ctx.must(exitCode) cmd := &command{} for _, line := range strings.Split(ctx.stdoutString(), "\n") { if prefix := "command "; strings.HasPrefix(line, prefix) { cmd.Path = line[len(prefix):] } else if prefix := "arg "; strings.HasPrefix(line, prefix) { cmd.Args = append(cmd.Args, line[len(prefix):]) } } return cmd }