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