github.com/windmilleng/tilt@v0.13.6/integration/fixture_test.go (about)

     1  // +build integration
     2  
     3  package integration
     4  
     5  import (
     6  	"context"
     7  	"fmt"
     8  	"io"
     9  	"io/ioutil"
    10  	"os"
    11  	"os/exec"
    12  	"path/filepath"
    13  	"runtime"
    14  	"strings"
    15  	"testing"
    16  	"time"
    17  
    18  	"github.com/windmilleng/tilt/internal/testutils/bufsync"
    19  )
    20  
    21  var packageDir string
    22  var installed bool
    23  
    24  const namespaceFlag = "-n=tilt-integration"
    25  
    26  func init() {
    27  	_, file, _, ok := runtime.Caller(0)
    28  	if !ok {
    29  		panic(fmt.Errorf("Could not locate path to Tilt integration tests"))
    30  	}
    31  
    32  	packageDir = filepath.Dir(file)
    33  }
    34  
    35  type fixture struct {
    36  	t             *testing.T
    37  	ctx           context.Context
    38  	cancel        func()
    39  	dir           string
    40  	logs          *bufsync.ThreadSafeBuffer
    41  	originalFiles map[string]string
    42  	tilt          *TiltDriver
    43  	activeTiltUp  *TiltUpResponse
    44  	tearingDown   bool
    45  	tiltArgs      []string
    46  }
    47  
    48  func newFixture(t *testing.T, dir string) *fixture {
    49  	dir = filepath.Join(packageDir, dir)
    50  	err := os.Chdir(dir)
    51  	if err != nil {
    52  		t.Fatal(err)
    53  	}
    54  
    55  	client := NewTiltDriver()
    56  	client.Environ["TILT_DISABLE_ANALYTICS"] = "true"
    57  
    58  	ctx, cancel := context.WithCancel(context.Background())
    59  	f := &fixture{
    60  		t:             t,
    61  		ctx:           ctx,
    62  		cancel:        cancel,
    63  		dir:           dir,
    64  		logs:          bufsync.NewThreadSafeBuffer(),
    65  		originalFiles: make(map[string]string),
    66  		tilt:          client,
    67  	}
    68  
    69  	if !installed {
    70  		// Install tilt on the first test run.
    71  		f.installTilt()
    72  		installed = true
    73  	}
    74  
    75  	return f
    76  }
    77  
    78  func (f *fixture) testDirPath(s string) string {
    79  	return filepath.Join(f.dir, s)
    80  }
    81  
    82  func (f *fixture) installTilt() {
    83  	cmd := exec.CommandContext(f.ctx, "go", "install", "-mod", "vendor", "github.com/windmilleng/tilt/cmd/tilt")
    84  	f.runOrFail(cmd, "Building tilt")
    85  }
    86  
    87  func (f *fixture) runOrFail(cmd *exec.Cmd, msg string) {
    88  	// Use Output() instead of Run() because that captures Stderr in the ExitError.
    89  	_, err := cmd.Output()
    90  	if err == nil {
    91  		return
    92  	}
    93  
    94  	exitErr, isExitErr := err.(*exec.ExitError)
    95  	if isExitErr {
    96  		f.t.Fatalf("%s\nError: %v\nStderr:\n%s\n", msg, err, string(exitErr.Stderr))
    97  		return
    98  	}
    99  	f.t.Fatalf("%s. Error: %v", msg, err)
   100  }
   101  
   102  func (f *fixture) DumpLogs() {
   103  	_, _ = os.Stdout.Write([]byte(f.logs.String()))
   104  }
   105  
   106  func (f *fixture) WaitUntil(ctx context.Context, msg string, fun func() (string, error), expectedContents string) {
   107  	for {
   108  		actualContents, err := fun()
   109  		if err == nil && strings.Contains(actualContents, expectedContents) {
   110  			return
   111  		}
   112  
   113  		select {
   114  		case <-f.activeTiltDone():
   115  			f.t.Fatalf("Tilt died while waiting: %v", f.activeTiltErr())
   116  		case <-ctx.Done():
   117  			f.t.Fatalf("Timed out waiting for expected result (%s)\n"+
   118  				"Expected: %s\n"+
   119  				"Actual: %s\n"+
   120  				"Current error: %v\n",
   121  				msg, expectedContents, actualContents, err)
   122  		case <-time.After(200 * time.Millisecond):
   123  		}
   124  	}
   125  }
   126  
   127  func (f *fixture) activeTiltDone() <-chan struct{} {
   128  	if f.activeTiltUp != nil {
   129  		return f.activeTiltUp.Done()
   130  	}
   131  	neverDone := make(chan struct{})
   132  	return neverDone
   133  }
   134  
   135  func (f *fixture) activeTiltErr() error {
   136  	if f.activeTiltUp != nil {
   137  		return f.activeTiltUp.Err()
   138  	}
   139  	return nil
   140  }
   141  
   142  func (f *fixture) LogWriter() io.Writer {
   143  	return io.MultiWriter(f.logs, os.Stdout)
   144  }
   145  
   146  func (f *fixture) TiltUp(name string) {
   147  	args := []string{"--watch=false"}
   148  	if name != "" {
   149  		args = append(args, name)
   150  	}
   151  	response, err := f.tilt.Up(args, f.LogWriter())
   152  	if err != nil {
   153  		f.t.Fatalf("TiltUp %s: %v", name, err)
   154  	}
   155  	select {
   156  	case <-response.Done():
   157  		err := response.Err()
   158  		if err != nil {
   159  			f.t.Fatalf("TiltUp %s: %v", name, err)
   160  		}
   161  	case <-f.ctx.Done():
   162  		err := f.ctx.Err()
   163  		if err != nil {
   164  			f.t.Fatalf("TiltUp %s: %v", name, err)
   165  		}
   166  	}
   167  }
   168  
   169  func (f *fixture) TiltWatch() {
   170  	response, err := f.tilt.Up(f.tiltArgs, f.LogWriter())
   171  	if err != nil {
   172  		f.t.Fatalf("TiltWatch: %v", err)
   173  	}
   174  	f.activeTiltUp = response
   175  }
   176  
   177  func (f *fixture) TiltWatchExec() {
   178  	response, err := f.tilt.Up(append([]string{"--update-mode=exec"}, f.tiltArgs...), f.LogWriter())
   179  	if err != nil {
   180  		f.t.Fatalf("TiltWatchExec: %v", err)
   181  	}
   182  	f.activeTiltUp = response
   183  }
   184  
   185  func (f *fixture) ReplaceContents(fileBaseName, original, replacement string) {
   186  	file := f.testDirPath(fileBaseName)
   187  	contentsBytes, err := ioutil.ReadFile(file)
   188  	if err != nil {
   189  		f.t.Fatal(err)
   190  	}
   191  
   192  	contents := string(contentsBytes)
   193  	_, hasStoredContents := f.originalFiles[file]
   194  	if !hasStoredContents {
   195  		f.originalFiles[file] = contents
   196  	}
   197  
   198  	newContents := strings.Replace(contents, original, replacement, -1)
   199  	if newContents == contents {
   200  		f.t.Fatalf("Could not find contents %q to replace in file %s: %s", original, fileBaseName, contents)
   201  	}
   202  
   203  	err = ioutil.WriteFile(file, []byte(newContents), os.FileMode(0777))
   204  	if err != nil {
   205  		f.t.Fatal(err)
   206  	}
   207  }
   208  
   209  func (f *fixture) StartTearDown() {
   210  	if f.tearingDown {
   211  		return
   212  	}
   213  
   214  	isTiltStillUp := f.activeTiltUp != nil && f.activeTiltUp.Err() == nil
   215  	if f.t.Failed() && isTiltStillUp {
   216  		fmt.Printf("Test failed, dumping engine state\n----\n")
   217  		err := f.tilt.DumpEngine(os.Stdout)
   218  		if err != nil {
   219  			fmt.Printf("Error: %v", err)
   220  		}
   221  		fmt.Printf("\n----\n")
   222  
   223  		err = f.activeTiltUp.KillAndDumpThreads()
   224  		if err != nil {
   225  			fmt.Printf("error killing tilt: %v\n", err)
   226  		}
   227  	}
   228  
   229  	f.cancel()
   230  	f.ctx = context.Background()
   231  	f.tearingDown = true
   232  }
   233  
   234  func (f *fixture) KillProcs() {
   235  	if f.activeTiltUp != nil {
   236  		err := f.activeTiltUp.Kill()
   237  		if err != nil {
   238  			fmt.Printf("error killing tilt: %v\n", err)
   239  		}
   240  	}
   241  }
   242  
   243  func (f *fixture) TearDown() {
   244  	f.StartTearDown()
   245  
   246  	f.KillProcs()
   247  
   248  	// This is a hack.
   249  	//
   250  	// Deleting a namespace is slow. Doing it on every test case makes
   251  	// the tests more accurate. We believe that in this particular case,
   252  	// the trade-off of speed over accuracy is worthwhile, so
   253  	// we add this hack so that we can `tilt down` without deleting
   254  	// the namespace.
   255  	//
   256  	// Each Tiltfile reads this environment variable, and skips loading the namespace
   257  	// into Tilt, so that Tilt doesn't delete it.
   258  	//
   259  	// If users want to do the same thing in practice, it might be worth
   260  	// adding better in-product hooks (e.g., `tilt down --preserve-namespace`),
   261  	// or more scriptability in the Tiltfile.
   262  	f.tilt.Environ["SKIP_NAMESPACE"] = "true"
   263  
   264  	err := f.tilt.Down(os.Stdout)
   265  	if err != nil {
   266  		f.t.Errorf("Running tilt down: %v", err)
   267  	}
   268  
   269  	for k, v := range f.originalFiles {
   270  		_ = ioutil.WriteFile(k, []byte(v), os.FileMode(0777))
   271  	}
   272  }