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  }