github.com/bazelbuild/bazel-watcher@v0.25.2/internal/e2e/ibazel.go (about) 1 package e2e 2 3 import ( 4 "bytes" 5 "fmt" 6 "io/ioutil" 7 "os" 8 "os/exec" 9 "path/filepath" 10 "regexp" 11 "strconv" 12 "strings" 13 "syscall" 14 "testing" 15 "time" 16 17 "github.com/bazelbuild/rules_go/go/tools/bazel_testing" 18 ) 19 20 // Maximum amount of time to wait before failing a test for not matching your expectations. 21 const ( 22 defaultDelay = 20 * time.Second 23 ) 24 25 type IBazelTester struct { 26 t *testing.T 27 ibazelLogFile string 28 29 cmd *exec.Cmd 30 stderrBuffer *Buffer 31 stderrOld string 32 stdoutBuffer *Buffer 33 stdoutOld string 34 ibazelErrOld string 35 } 36 37 func NewIBazelTester(t *testing.T) *IBazelTester { 38 f, err := ioutil.TempFile("", "ibazel_output.*.log") 39 if err != nil { 40 panic(fmt.Sprintf("Error ioutil.Tempfile: %v", err)) 41 } 42 43 return &IBazelTester{ 44 t: t, 45 ibazelLogFile: f.Name(), 46 } 47 } 48 49 func (i *IBazelTester) bazelPath() string { 50 i.t.Helper() 51 path, err := exec.LookPath("bazel") 52 if err != nil { 53 i.t.Fatalf("Unable to find bazel binary: %v", err) 54 } 55 return path 56 } 57 58 func (i *IBazelTester) Build(target string) { 59 i.t.Helper() 60 i.build(target, []string{}) 61 } 62 63 func (i *IBazelTester) Test(bazelArgs []string, targets ...string) { 64 i.t.Helper() 65 66 args := []string{"--bazel_path=" + i.bazelPath()} 67 args = append(args, 68 "--log_to_file="+i.ibazelLogFile, 69 "--graceful_termination_wait_duration=1s") 70 args = append(args, "test") 71 args = append(args, "--bazelrc=/dev/null") 72 args = append(args, targets...) 73 args = append(args, bazelArgs...) 74 i.cmd = exec.Command(ibazelPath, args...) 75 i.t.Logf("ibazel invoked as: %s", strings.Join(i.cmd.Args, " ")) 76 77 i.stdoutBuffer = &Buffer{} 78 i.cmd.Stdout = i.stdoutBuffer 79 80 i.stderrBuffer = &Buffer{} 81 i.cmd.Stderr = i.stderrBuffer 82 83 if err := i.cmd.Start(); err != nil { 84 i.t.Fatalf("Command: %s", i.cmd) 85 } 86 } 87 88 func (i *IBazelTester) Coverage(bazelArgs []string, targets ...string) { 89 i.t.Helper() 90 91 args := []string{"--bazel_path=" + i.bazelPath()} 92 args = append(args, 93 "--log_to_file="+i.ibazelLogFile, 94 "--graceful_termination_wait_duration=1s") 95 args = append(args, "coverage") 96 args = append(args, "--bazelrc=/dev/null") 97 args = append(args, targets...) 98 args = append(args, bazelArgs...) 99 i.cmd = exec.Command(ibazelPath, args...) 100 i.t.Logf("ibazel invoked as: %s", strings.Join(i.cmd.Args, " ")) 101 102 i.stdoutBuffer = &Buffer{} 103 i.cmd.Stdout = i.stdoutBuffer 104 105 i.stderrBuffer = &Buffer{} 106 i.cmd.Stderr = i.stderrBuffer 107 108 if err := i.cmd.Start(); err != nil { 109 i.t.Fatalf("Command: %s", i.cmd) 110 } 111 } 112 113 func (i *IBazelTester) Run(bazelArgs []string, target string) { 114 i.t.Helper() 115 i.run(target, bazelArgs, []string{ 116 "--log_to_file=" + i.ibazelLogFile, 117 "--graceful_termination_wait_duration=1s", 118 }) 119 } 120 121 func (i *IBazelTester) RunWithProfiler(target string, profiler string) { 122 i.t.Helper() 123 i.run(target, []string{}, []string{ 124 "--log_to_file=" + i.ibazelLogFile, 125 "--graceful_termination_wait_duration=1s", 126 "--profile_dev=" + profiler, 127 }) 128 } 129 130 func (i *IBazelTester) RunWithBazelFixCommands(target string) { 131 i.t.Helper() 132 i.run(target, []string{}, []string{ 133 "--log_to_file=" + i.ibazelLogFile, 134 "--graceful_termination_wait_duration=1s", 135 "--run_output=true", 136 "--run_output_interactive=false", 137 }) 138 } 139 140 func (i *IBazelTester) RunWithAdditionalArgs(target string, additionalArgs []string) { 141 i.t.Helper() 142 i.run(target, []string{}, additionalArgs) 143 } 144 145 func (i *IBazelTester) RunUnverifiedWithAdditionalArgs(target string, additionalArgs []string) { 146 i.t.Helper() 147 prebuild := false 148 i.runUnverified(target, []string{}, additionalArgs, prebuild) 149 } 150 151 func (i *IBazelTester) GetOutput() string { 152 i.t.Helper() 153 return i.stdoutBuffer.String() 154 } 155 156 func (i *IBazelTester) ExpectOutput(want string, delay ...time.Duration) { 157 i.t.Helper() 158 159 i.checkExit() 160 161 d := defaultDelay 162 if len(delay) == 1 { 163 d = delay[0] 164 } 165 i.Expect(want, i.GetOutput, &i.stdoutOld, d) 166 } 167 168 func (i *IBazelTester) ExpectNoOutput(delay ...time.Duration) { 169 i.t.Helper() 170 171 i.checkExit() 172 173 d := defaultDelay 174 if len(delay) == 1 { 175 d = delay[0] 176 } 177 178 // Flush the stdout before waiting for new content. 179 i.stdoutOld = i.GetOutput() 180 181 stopAt := time.Now().Add(d) 182 for time.Now().Before(stopAt) { 183 time.Sleep(5 * time.Millisecond) 184 185 stdout := i.GetOutput()[len(*&i.stdoutOld):] 186 if len(stdout) != 0 { 187 i.t.Errorf(`Expected no output, but found stdout "%s".`, stdout) 188 i.stdoutOld = i.GetOutput() 189 return 190 } 191 } 192 } 193 194 func (i *IBazelTester) ExpectError(want string, delay ...time.Duration) { 195 i.t.Helper() 196 197 i.checkExit() 198 199 d := defaultDelay 200 if len(delay) == 1 { 201 d = delay[0] 202 } 203 i.Expect(want, i.GetError, &i.stderrOld, d) 204 } 205 206 func (i *IBazelTester) ExpectNoError(delay ...time.Duration) { 207 i.t.Helper() 208 209 i.checkExit() 210 211 d := defaultDelay 212 if len(delay) == 1 { 213 d = delay[0] 214 } 215 216 // Flush the stdout before waiting for new content. 217 i.stderrOld = i.GetError() 218 219 stopAt := time.Now().Add(d) 220 for time.Now().Before(stopAt) { 221 time.Sleep(5 * time.Millisecond) 222 223 stderr := i.GetError()[len(*&i.stderrOld):] 224 if len(stderr) != 0 { 225 i.t.Errorf(`Expected no output err, but found stderr "%s".`, stderr) 226 i.stderrOld = i.GetError() 227 return 228 } 229 } 230 } 231 232 func (i *IBazelTester) ExpectIBazelError(want string, delay ...time.Duration) { 233 i.t.Helper() 234 235 i.checkExit() 236 237 d := defaultDelay 238 if len(delay) == 1 { 239 d = delay[0] 240 } 241 i.Expect(want, i.GetIBazelError, &i.ibazelErrOld, d) 242 } 243 244 func (i *IBazelTester) GetIBazelError() string { 245 i.t.Helper() 246 247 i.checkExit() 248 249 iBazelError, err := os.Open(i.ibazelLogFile) 250 if err != nil { 251 i.t.Errorf("Error os.Open(%q): %v", i.ibazelLogFile, err) 252 return "" 253 } 254 255 b, err := ioutil.ReadAll(iBazelError) 256 if err != nil { 257 i.t.Fatalf("Error ioutil.ReadAll(iBazelError): %v", err) 258 } 259 260 return string(b) 261 } 262 263 func (i *IBazelTester) ExpectFixCommands(want []string, delay ...time.Duration) { 264 i.t.Helper() 265 266 i.checkExit() 267 268 d := defaultDelay 269 if len(delay) == 1 { 270 d = delay[0] 271 } 272 273 logRegexp := regexp.MustCompile("Executing command: `([^`]+)`") 274 275 stopAt := time.Now().Add(d) 276 for time.Now().Before(stopAt) { 277 time.Sleep(5 * time.Millisecond) 278 279 if len(logRegexp.FindAllStringSubmatch(i.GetIBazelError(), -1)) >= len(want) { 280 break 281 } 282 } 283 284 matches := logRegexp.FindAllStringSubmatch(i.GetIBazelError(), -1) 285 if len(matches) != len(want) { 286 i.t.Errorf("Expected %v commands to be executed, but found %v.", len(want), len(matches)) 287 i.t.Errorf("Stderr: [%v]\niBazelStderr: [%v]", i.GetError(), i.GetIBazelError()) 288 } else { 289 var actual []string 290 for ind := range matches { 291 actual = append(actual, matches[ind][1]) 292 } 293 for ind, expected := range want { 294 if actual[ind] != expected { 295 i.t.Errorf("Expected the commands to have been executed in order:\nWanted [\n%s\n], got [\n%s\n]", 296 strings.Join(want, "\n"), strings.Join(actual, "\n")) 297 i.t.Errorf("Stderr: [%v]\niBazelStderr: [%v]", i.GetError(), i.GetIBazelError()) 298 } 299 } 300 } 301 } 302 303 func (i *IBazelTester) Expect(want string, stream func() string, history *string, delay time.Duration) { 304 i.t.Helper() 305 306 stopAt := time.Now().Add(delay) 307 for time.Now().Before(stopAt) { 308 time.Sleep(5 * time.Millisecond) 309 310 // Grab the output and strip output that was available last time we passed 311 // a test. 312 out := stream()[len(*history):] 313 if match, err := regexp.MatchString(want, out); match == true && err == nil { 314 // Save the current output value for the next iteration. 315 *history = stream() 316 return 317 } 318 } 319 320 if match, err := regexp.MatchString(want, stream()); match == false || err != nil { 321 i.t.Errorf("Expected iBazel output after %v to be:\nWanted [%v], got [%v]", delay, want, stream()) 322 i.t.Errorf("Stderr: [%v]\niBazelStderr: [%v]", i.GetError(), i.GetIBazelError()) 323 //i.t.Log(string(debug.Stack())) 324 325 // In order to prevent cascading errors where the first result failing to 326 // match ruins the error output for the rest of the runs, persist the old 327 // stdout. 328 *history = stream() 329 } 330 } 331 332 func (i *IBazelTester) GetError() string { 333 i.t.Helper() 334 return i.stderrBuffer.String() 335 } 336 337 func (i *IBazelTester) GetSubprocessPid() int64 { 338 i.t.Helper() 339 f, err := os.Open(filepath.Join(os.TempDir(), "ibazel_e2e_subprocess_launcher.pid")) 340 if err != nil { 341 panic(err) 342 } 343 344 rawPid, err := ioutil.ReadAll(f) 345 if err != nil { 346 panic(err) 347 } 348 349 pid, err := strconv.ParseInt(string(rawPid), 10, 32) 350 if err != nil { 351 panic(err) 352 } 353 return pid 354 } 355 356 func (i *IBazelTester) Kill() { 357 i.t.Helper() 358 if err := i.cmd.Process.Kill(); err != nil { 359 panic(err) 360 } 361 } 362 363 func (i *IBazelTester) Signal(signum os.Signal) { 364 i.t.Helper() 365 i.cmd.Process.Signal(signum) 366 } 367 368 func (i *IBazelTester) build(target string, additionalArgs []string) { 369 i.t.Helper() 370 args := []string{"--bazel_path=" + i.bazelPath()} 371 args = append(args, additionalArgs...) 372 args = append(args, "build") 373 args = append(args, target) 374 i.cmd = exec.Command(ibazelPath, args...) 375 376 i.stdoutBuffer = &Buffer{} 377 i.cmd.Stdout = i.stdoutBuffer 378 379 i.stderrBuffer = &Buffer{} 380 i.cmd.Stderr = i.stderrBuffer 381 382 if err := i.cmd.Start(); err != nil { 383 i.t.Fatalf("Command: %s\nError: %v", i.cmd, err) 384 } 385 } 386 387 func (i *IBazelTester) checkExit() { 388 if i.cmd != nil && i.cmd.ProcessState != nil && i.cmd.ProcessState.Exited() == true { 389 i.t.Errorf("ibazel is exited") 390 } 391 } 392 393 func (i *IBazelTester) run(target string, bazelArgs []string, additionalArgs []string) { 394 prebuild := true 395 i.runUnverified(target, bazelArgs, additionalArgs, prebuild) 396 } 397 398 func (i *IBazelTester) runUnverified(target string, bazelArgs []string, additionalArgs []string, prebuild bool) { 399 i.t.Helper() 400 401 args := []string{"--bazel_path=" + i.bazelPath()} 402 args = append(args, additionalArgs...) 403 args = append(args, "run") 404 args = append(args, "--bazelrc=/dev/null") 405 args = append(args, target) 406 args = append(args, bazelArgs...) 407 i.cmd = exec.Command(ibazelPath, args...) 408 i.t.Logf("ibazel invoked as: %s", strings.Join(i.cmd.Args, " ")) 409 410 checkArgs := []string{"build"} 411 checkArgs = append(checkArgs, target) 412 checkArgs = append(checkArgs, bazelArgs...) 413 cmd := bazel_testing.BazelCmd(checkArgs...) 414 415 var buildStdout, buildStderr bytes.Buffer 416 cmd.Stdout = &buildStdout 417 cmd.Stderr = &buildStderr 418 419 // Before doing anything crazy, let's build the target to make sure it works. 420 if prebuild { 421 if err := cmd.Run(); err != nil { 422 if exitErr, ok := err.(*exec.ExitError); ok { 423 status := exitErr.Sys().(syscall.WaitStatus) 424 i.t.Fatalf("Unable to build target. Error code: %d\nStdout:\n%s\nStderr:\n%s", status.ExitStatus(), buildStdout.String(), buildStderr.String()) 425 } 426 } 427 } 428 429 i.stdoutBuffer = &Buffer{} 430 i.cmd.Stdout = i.stdoutBuffer 431 432 i.stderrBuffer = &Buffer{} 433 i.cmd.Stderr = i.stderrBuffer 434 435 if err := i.cmd.Start(); err != nil { 436 i.t.Fatalf("Command: %s", i.cmd) 437 } 438 }