blob: c56737be3f307c89400c0cb8d67fd292c2759efe [file] [log] [blame]
Cosmos Nicolaoucc5a4a82015-02-07 23:09:28 -08001// Package v23tests provides support for writing end-to-end style integration
2// tests. In particular, support is provided for building binaries, running
3// processes, making assertions about their output/state and ensuring that
4// no processes or files are left behind on exit. Since such tests are often
5// difficult to debug facilities are provided to help do so.
6//
7// The preferred usage of this integration test framework is via the v23
8// tool which generates supporting code. The primary reason for doing so is
9// to cleanly separate integration tests, which can be very expensive to run,
10// from normal unit tests which are intended to be fast and used constantly.
11// However, it still beneficial to be able to always compile the integration
12// test code with the normal test code, just not to run it. Similarly, it
13// is beneficial to share as much of the existing go test infrastructure as
14// possible, so the generated code uses a flag and a naming convention to
15// separate the tests. Integration tests may be run in addition to unit tests
16// by supplying the --v23.tests flag; the -run flag can be used
17// to avoid running unit tests by specifying a prefix of TestV23 since
18// the generate test functions always. Thus:
19//
20// v23 go test -v <pkgs> --v23.test // runs both unit and integration tests
21// v23 go test -v -run=TestV23 <pkgs> --v23.test // runs just integration tests
22//
23// The go generate mechanism is used to generate the test code, thus the
24// comment:
25//
26// //go:generate v23 integration generate
27//
28// will generate the files v23_test.go and internal_v23_test.go for the
29// package in which it occurs. Run v23 integration generate help for full
30// details and options. In short, any function in an external
31// (i.e. <pgk>_test) test package of the following form:
32//
Cosmos Nicolaou01007a02015-02-11 15:38:38 -080033// V23Test<x>(t *v23tests.T)
Cosmos Nicolaoucc5a4a82015-02-07 23:09:28 -080034//
35// will be invoked as integration test if the --v23.tests flag is used.
36//
37// The generated code makes use of the RunTest function, documented below.
38//
39// The test environment is implemented by an instance of the interface T.
40// It is constructed with an instance of another interface Test, which is
41// generally implemented by testing.T. Thus, the integration test environment
42// directly as follows:
James Ringa71e49a2015-01-16 14:08:02 -080043//
44// func TestFoo(t *testing.T) {
Cosmos Nicolaou4a77c192015-02-08 15:29:18 -080045// env := v23tests.New(t)
James Ringa71e49a2015-01-16 14:08:02 -080046// defer env.Cleanup()
47//
48// ...
49// }
50//
51// The methods in this API typically do not return error in the case of
52// failure. Instead, the current test will fail with an appropriate error
Cosmos Nicolaoucc5a4a82015-02-07 23:09:28 -080053// message. This avoids the need to handle errors inline the test itself.
James Ringa71e49a2015-01-16 14:08:02 -080054//
Cosmos Nicolaoucc5a4a82015-02-07 23:09:28 -080055// The test environment manages all built packages, subprocesses and a
56// set of environment variables that are passed to subprocesses.
57//
58// Debugging is supported as follows:
59// 1. The DebugShell method creates an interative shell at that point in
60// the tests execution that has access to all of the running processes
61// and environment of those processes. The developer can interact with
62// those processes to determine the state of the test.
63// 2. Calls to methods on Test (e.g. FailNow, Fatalf) that fail the test
64// cause the Cleanup method to print out the status of all invocations.
65// 3. Similarly, if the --v23.tests.shell-on-error flag is set then the
66// cleanup method will invoke a DebugShell on a test failure allowing
67// the developer to inspect the state of the test.
68// 4. The implementation of this package uses filenames that start with v23test
69// to allow for easy tracing with --vmodule=v23test*=2 for example.
70//
Cosmos Nicolaou4a77c192015-02-08 15:29:18 -080071package v23tests
Jiri Simsae63e07d2014-12-04 11:12:08 -080072
73import (
Cosmos Nicolaoucc5a4a82015-02-07 23:09:28 -080074 "errors"
Jiri Simsae63e07d2014-12-04 11:12:08 -080075 "fmt"
76 "io/ioutil"
77 "os"
78 "os/exec"
79 "path"
80 "path/filepath"
Cosmos Nicolaou1fcb6a32015-02-17 07:46:02 -080081 "runtime"
Jiri Simsae63e07d2014-12-04 11:12:08 -080082 "strings"
James Ring65795102014-12-17 13:01:03 -080083 "syscall"
Cosmos Nicolaou01007a02015-02-11 15:38:38 -080084 "testing"
Jiri Simsae63e07d2014-12-04 11:12:08 -080085 "time"
86
Jiri Simsa337af232015-02-27 14:36:46 -080087 "v.io/x/lib/vlog"
Cosmos Nicolaoud21f6b12015-02-07 11:40:03 -080088
Cosmos Nicolaou1381f8a2015-03-13 09:40:34 -070089 "v.io/v23"
90 "v.io/v23/security"
91
Jiri Simsaffceefa2015-02-28 11:03:34 -080092 "v.io/x/ref/security/agent"
Cosmos Nicolaou1381f8a2015-03-13 09:40:34 -070093 "v.io/x/ref/test"
94 "v.io/x/ref/test/modules"
95 tsecurity "v.io/x/ref/test/security"
Jiri Simsae63e07d2014-12-04 11:12:08 -080096)
97
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -080098// TB is an exact mirror of testing.TB. It is provided to allow for testing
99// of this package using a mock implementation. As per testing.TB, it is not
100// intended to be implemented outside of this package.
Cosmos Nicolaoud3b1cb12015-02-20 00:06:33 -0800101type TB interface {
102 Error(args ...interface{})
103 Errorf(format string, args ...interface{})
104 Fail()
105 FailNow()
106 Failed() bool
107 Fatal(args ...interface{})
108 Fatalf(format string, args ...interface{})
109 Log(args ...interface{})
110 Logf(format string, args ...interface{})
111 Skip(args ...interface{})
112 SkipNow()
113 Skipf(format string, args ...interface{})
114 Skipped() bool
115}
James Ring855dfd42015-01-23 12:30:58 -0800116
Cosmos Nicolaoucc5a4a82015-02-07 23:09:28 -0800117// T represents an integration test environment.
Cosmos Nicolaou01007a02015-02-11 15:38:38 -0800118type T struct {
Cosmos Nicolaoud3b1cb12015-02-20 00:06:33 -0800119 // The embedded TB
120 TB
James Ring65795102014-12-17 13:01:03 -0800121
Suharsh Sivakumar19fbf992015-01-23 11:02:27 -0800122 // The function to shutdown the context used to create the environment.
Jiri Simsa6ac95222015-02-23 16:11:49 -0800123 shutdown v23.Shutdown
Suharsh Sivakumar19fbf992015-01-23 11:02:27 -0800124
James Ring65795102014-12-17 13:01:03 -0800125 // The shell to use to start commands.
126 shell *modules.Shell
127
128 // The environment's root security principal.
129 principal security.Principal
130
Cosmos Nicolaou01007a02015-02-11 15:38:38 -0800131 // Maps path to Binary.
132 builtBinaries map[string]*Binary
James Ring65795102014-12-17 13:01:03 -0800133
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -0800134 tempFiles []*os.File
135 tempDirs []string
136 binDir, cachedBinDir string
137 dirStack []string
Cosmos Nicolaoud21f6b12015-02-07 11:40:03 -0800138
Cosmos Nicolaou01007a02015-02-11 15:38:38 -0800139 invocations []*Invocation
James Ring65795102014-12-17 13:01:03 -0800140}
141
Cosmos Nicolaoucc5a4a82015-02-07 23:09:28 -0800142var errNotShutdown = errors.New("has not been shutdown")
143
Cosmos Nicolaou93dd88b2015-02-19 15:10:53 -0800144// Caller returns a string of the form <filename>:<lineno> for the
145// caller specified by skip, where skip is as per runtime.Caller.
146func Caller(skip int) string {
147 _, file, line, _ := runtime.Caller(skip + 1)
Cosmos Nicolaou1fcb6a32015-02-17 07:46:02 -0800148 return fmt.Sprintf("%s:%d", filepath.Base(file), line)
James Ring65795102014-12-17 13:01:03 -0800149}
150
Cosmos Nicolaoud3b1cb12015-02-20 00:06:33 -0800151// Run constructs a Binary for path and invokes Run on it.
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700152func (t *T) Run(path string, args ...string) string {
153 return t.BinaryFromPath(path).run(args...)
154}
155
156// Run constructs a Binary for path and invokes Run on it using
157// the specified StartOpts
158func (t *T) RunWithOpts(opts modules.StartOpts, path string, args ...string) string {
159 b := t.BinaryFromPath(path)
160 return b.WithStartOpts(opts).run(args...)
Cosmos Nicolaoud3b1cb12015-02-20 00:06:33 -0800161}
162
163// WaitFunc is the type of the functions to be used in conjunction
164// with WaitFor and WaitForAsync. It should return a value or an error
165// when it wants those functions to terminate, returning a nil value
166// and nil error will result in it being called again after the specified
167// delay time specified in the calls to WaitFor and WaitForAsync.
168type WaitFunc func() (interface{}, error)
169
170// WaitFor calls fn at least once with the specified delay value
171// between iterations until the first of the following is encountered:
172// 1. fn returns a non-nil value.
173// 2. fn returns an error value
174// 3. fn is executed at least once and the specified timeout is exceeded.
175//
176// WaitFor returns the non-nil value for the first case and calls e.Fatalf for
177// the other two cases.
178// WaitFor will always run fn at least once to completion and hence it will
179// hang if that first iteration of fn hangs. If this behaviour is not
180// appropriate, then WaitForAsync should be used.
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700181func (t *T) WaitFor(fn WaitFunc, delay, timeout time.Duration) interface{} {
Cosmos Nicolaoud3b1cb12015-02-20 00:06:33 -0800182 deadline := time.Now().Add(timeout)
183 for {
184 val, err := fn()
185 if val != nil {
186 return val
187 }
188 if err != nil {
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700189 t.Fatalf("%s: the WaitFunc returned an error: %v", Caller(1), err)
Cosmos Nicolaoud3b1cb12015-02-20 00:06:33 -0800190 }
191 if time.Now().After(deadline) {
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700192 t.Fatalf("%s: timed out after %s", Caller(1), timeout)
Cosmos Nicolaoud3b1cb12015-02-20 00:06:33 -0800193 }
194 time.Sleep(delay)
195 }
196}
197
198// WaitForAsync is like WaitFor except that it calls fn in a goroutine
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -0800199// and can timeout during the execution of fn.
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700200func (t *T) WaitForAsync(fn WaitFunc, delay, timeout time.Duration) interface{} {
Cosmos Nicolaoud3b1cb12015-02-20 00:06:33 -0800201 resultCh := make(chan interface{})
202 errCh := make(chan interface{})
203 go func() {
204 for {
205 val, err := fn()
206 if val != nil {
207 resultCh <- val
208 return
209 }
210 if err != nil {
211 errCh <- err
212 return
213 }
214 time.Sleep(delay)
215 }
216 }()
217 select {
218 case err := <-errCh:
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700219 t.Fatalf("%s: the WaitFunc returned error: %v", Caller(1), err)
Cosmos Nicolaoud3b1cb12015-02-20 00:06:33 -0800220 case result := <-resultCh:
221 return result
222 case <-time.After(timeout):
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700223 t.Fatalf("%s: timed out after %s", Caller(1), timeout)
Cosmos Nicolaoud3b1cb12015-02-20 00:06:33 -0800224 }
225 return nil
226}
227
228// Pushd pushes the current working directory to the stack of
229// directories, returning it as its result, and changes the working
230// directory to dir.
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700231func (t *T) Pushd(dir string) string {
Cosmos Nicolaoud3b1cb12015-02-20 00:06:33 -0800232 cwd, err := os.Getwd()
233 if err != nil {
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700234 t.Fatalf("%s: Getwd failed: %s", Caller(1), err)
Cosmos Nicolaoud3b1cb12015-02-20 00:06:33 -0800235 }
236 if err := os.Chdir(dir); err != nil {
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700237 t.Fatalf("%s: Chdir failed: %s", Caller(1), err)
Cosmos Nicolaoud3b1cb12015-02-20 00:06:33 -0800238 }
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -0800239 vlog.VI(1).Infof("Pushd: %s -> %s", cwd, dir)
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700240 t.dirStack = append(t.dirStack, cwd)
Cosmos Nicolaoud3b1cb12015-02-20 00:06:33 -0800241 return cwd
242}
243
244// Popd pops the most recent entry from the directory stack and changes
245// the working directory to that directory. It returns the new working
246// directory as its result.
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700247func (t *T) Popd() string {
248 if len(t.dirStack) == 0 {
249 t.Fatalf("%s: directory stack empty", Caller(1))
Cosmos Nicolaoud3b1cb12015-02-20 00:06:33 -0800250 }
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700251 dir := t.dirStack[len(t.dirStack)-1]
252 t.dirStack = t.dirStack[:len(t.dirStack)-1]
Cosmos Nicolaoud3b1cb12015-02-20 00:06:33 -0800253 if err := os.Chdir(dir); err != nil {
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700254 t.Fatalf("%s: Chdir failed: %s", Caller(1), err)
Cosmos Nicolaoud3b1cb12015-02-20 00:06:33 -0800255 }
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -0800256 vlog.VI(1).Infof("Popd: -> %s", dir)
Cosmos Nicolaoud3b1cb12015-02-20 00:06:33 -0800257 return dir
258}
259
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -0800260// Caller returns a string of the form <filename>:<lineno> for the
261// caller specified by skip, where skip is as per runtime.Caller.
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700262func (t *T) Caller(skip int) string {
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -0800263 return Caller(skip + 1)
264}
265
Cosmos Nicolaou01007a02015-02-11 15:38:38 -0800266// Principal returns the security principal of this environment.
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700267func (t *T) Principal() security.Principal {
268 return t.principal
James Ring65795102014-12-17 13:01:03 -0800269}
270
Cosmos Nicolaou01007a02015-02-11 15:38:38 -0800271// Cleanup cleans up the environment, deletes all its artifacts and
272// kills all subprocesses. It will kill subprocesses in LIFO order.
273// Cleanup checks to see if the test has failed and logs information
274// as to the state of the processes it was asked to invoke up to that
275// point and optionally, if the --v23.tests.shell-on-fail flag is set
276// then it will run a debug shell before cleaning up its state.
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700277func (t *T) Cleanup() {
278 if t.Failed() {
Cosmos Nicolaou1381f8a2015-03-13 09:40:34 -0700279 if test.IntegrationTestsDebugShellOnError {
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700280 t.DebugSystemShell()
Cosmos Nicolaoucc5a4a82015-02-07 23:09:28 -0800281 }
282 // Print out a summary of the invocations and their status.
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700283 for i, inv := range t.invocations {
Cosmos Nicolaoucc5a4a82015-02-07 23:09:28 -0800284 if inv.hasShutdown && inv.Exists() {
285 m := fmt.Sprintf("%d: %s has been shutdown but still exists: %v", i, inv.path, inv.shutdownErr)
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700286 t.Log(m)
Cosmos Nicolaoucc5a4a82015-02-07 23:09:28 -0800287 vlog.VI(1).Info(m)
288 vlog.VI(2).Infof("%d: %s %v", i, inv.path, inv.args)
289 continue
290 }
Cosmos Nicolaou93dd88b2015-02-19 15:10:53 -0800291 if inv.shutdownErr != nil {
292 m := fmt.Sprintf("%d: %s: shutdown status: %v", i, inv.path, inv.shutdownErr)
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700293 t.Log(m)
Cosmos Nicolaou93dd88b2015-02-19 15:10:53 -0800294 vlog.VI(1).Info(m)
295 vlog.VI(2).Infof("%d: %s %v", i, inv.path, inv.args)
296 }
Cosmos Nicolaoucc5a4a82015-02-07 23:09:28 -0800297 }
298 }
299
Cosmos Nicolaoud21f6b12015-02-07 11:40:03 -0800300 vlog.VI(1).Infof("V23Test.Cleanup")
Cosmos Nicolaou82d00d82015-02-10 21:31:00 -0800301 // Shut down all processes in LIFO order before attempting to delete any
Cosmos Nicolaoud21f6b12015-02-07 11:40:03 -0800302 // files/directories to avoid potential 'file system busy' problems
303 // on non-unix systems.
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700304 for i := len(t.invocations); i > 0; i-- {
305 inv := t.invocations[i-1]
Cosmos Nicolaoucc5a4a82015-02-07 23:09:28 -0800306 if inv.hasShutdown {
307 vlog.VI(1).Infof("V23Test.Cleanup: %q has been shutdown", inv.Path())
308 continue
Cosmos Nicolaoud21f6b12015-02-07 11:40:03 -0800309 }
Cosmos Nicolaoud21f6b12015-02-07 11:40:03 -0800310 vlog.VI(1).Infof("V23Test.Cleanup: Kill: %q", inv.Path())
311 err := inv.Kill(syscall.SIGTERM)
312 inv.Wait(os.Stdout, os.Stderr)
313 vlog.VI(1).Infof("V23Test.Cleanup: Killed: %q: %v", inv.Path(), err)
314 }
Cosmos Nicolaoucc5a4a82015-02-07 23:09:28 -0800315 vlog.VI(1).Infof("V23Test.Cleanup: all invocations taken care of.")
Cosmos Nicolaoud21f6b12015-02-07 11:40:03 -0800316
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700317 if err := t.shell.Cleanup(os.Stdout, os.Stderr); err != nil {
318 t.Fatalf("WARNING: could not clean up shell (%v)", err)
Cosmos Nicolaoud21f6b12015-02-07 11:40:03 -0800319 }
320
Cosmos Nicolaoucc5a4a82015-02-07 23:09:28 -0800321 vlog.VI(1).Infof("V23Test.Cleanup: cleaning up binaries & files")
322
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700323 for _, tempFile := range t.tempFiles {
Cosmos Nicolaoucc5a4a82015-02-07 23:09:28 -0800324 vlog.VI(1).Infof("V23Test.Cleanup: cleaning up %s", tempFile.Name())
James Ring65795102014-12-17 13:01:03 -0800325 if err := tempFile.Close(); err != nil {
Cosmos Nicolaoucc5a4a82015-02-07 23:09:28 -0800326 vlog.Errorf("WARNING: Close(%q) failed: %v", tempFile.Name(), err)
James Ring65795102014-12-17 13:01:03 -0800327 }
James Ring23b54862015-01-07 11:54:48 -0800328 if err := os.RemoveAll(tempFile.Name()); err != nil {
Cosmos Nicolaoucc5a4a82015-02-07 23:09:28 -0800329 vlog.Errorf("WARNING: RemoveAll(%q) failed: %v", tempFile.Name(), err)
James Ring23b54862015-01-07 11:54:48 -0800330 }
331 }
332
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700333 for _, tempDir := range t.tempDirs {
Cosmos Nicolaoucc5a4a82015-02-07 23:09:28 -0800334 vlog.VI(1).Infof("V23Test.Cleanup: cleaning up %s", tempDir)
James Ring23b54862015-01-07 11:54:48 -0800335 if err := os.RemoveAll(tempDir); err != nil {
Cosmos Nicolaoucc5a4a82015-02-07 23:09:28 -0800336 vlog.Errorf("WARNING: RemoveAll(%q) failed: %v", tempDir, err)
James Ring65795102014-12-17 13:01:03 -0800337 }
338 }
339
Cosmos Nicolaoucc5a4a82015-02-07 23:09:28 -0800340 // shutdown the runtime
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700341 t.shutdown()
James Ring65795102014-12-17 13:01:03 -0800342}
343
Cosmos Nicolaou01007a02015-02-11 15:38:38 -0800344// GetVar returns the variable associated with the specified key
345// and an indication of whether it is defined or not.
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700346func (t *T) GetVar(key string) (string, bool) {
347 return t.shell.GetVar(key)
Cosmos Nicolaoud21f6b12015-02-07 11:40:03 -0800348}
349
Cosmos Nicolaou01007a02015-02-11 15:38:38 -0800350// SetVar sets the value to be associated with key.
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700351func (t *T) SetVar(key, value string) {
352 t.shell.SetVar(key, value)
Cosmos Nicolaoud21f6b12015-02-07 11:40:03 -0800353}
354
Cosmos Nicolaou01007a02015-02-11 15:38:38 -0800355// ClearVar removes the speficied variable from the Shell's environment
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700356func (t *T) ClearVar(key string) {
357 t.shell.ClearVar(key)
Cosmos Nicolaoud21f6b12015-02-07 11:40:03 -0800358}
359
Cosmos Nicolaou01007a02015-02-11 15:38:38 -0800360func writeStringOrDie(t *T, f *os.File, s string) {
James Ring0bad5672015-01-05 09:51:36 -0800361 if _, err := f.WriteString(s); err != nil {
James Ring0d525c22014-12-30 12:03:31 -0800362 t.Fatalf("Write() failed: %v", err)
363 }
364}
365
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700366// DebugSystemShell drops the user into a debug system shell (e.g. bash)
367// with any environment variables specified in env... (in VAR=VAL format)
368// available to it.
369// If there is no controlling TTY, DebugSystemShell will emit a warning message
370// and take no futher action. The DebugSystemShell also sets some environment
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -0800371// variables that relate to the running test:
372// - V23_TMP_DIR<#> contains the name of each temp directory created.
373// - V23_BIN_DIR contains the name of the directory containing binaries.
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700374func (t *T) DebugSystemShell(env ...string) {
James Ring65795102014-12-17 13:01:03 -0800375 // Get the current working directory.
376 cwd, err := os.Getwd()
377 if err != nil {
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700378 t.Fatalf("Getwd() failed: %v", err)
James Ring65795102014-12-17 13:01:03 -0800379 }
380
381 // Transfer stdin, stdout, and stderr to the new process
382 // and also set target directory for the shell to start in.
383 dev := "/dev/tty"
James Ring0d525c22014-12-30 12:03:31 -0800384 fd, err := syscall.Open(dev, syscall.O_RDWR, 0)
James Ring65795102014-12-17 13:01:03 -0800385 if err != nil {
Cosmos Nicolaoucc5a4a82015-02-07 23:09:28 -0800386 vlog.Errorf("WARNING: Open(%v) failed, was asked to create a debug shell but cannot: %v", dev, err)
James Ring65795102014-12-17 13:01:03 -0800387 return
388 }
Cosmos Nicolaou82d00d82015-02-10 21:31:00 -0800389
Ryan Brown61d69382015-02-25 11:13:39 -0800390 var agentFile *os.File
Asim Shankar34fb2492015-03-12 10:25:46 -0700391 if creds, err := t.shell.NewChildCredentials("debug"); err == nil {
Ryan Brown61d69382015-02-25 11:13:39 -0800392 if agentFile, err = creds.File(); err != nil {
393 vlog.Errorf("WARNING: failed to obtain credentials for the debug shell: %v", err)
394 }
395 } else {
Cosmos Nicolaou82d00d82015-02-10 21:31:00 -0800396 vlog.Errorf("WARNING: failed to obtain credentials for the debug shell: %v", err)
397 }
398
James Ring0d525c22014-12-30 12:03:31 -0800399 file := os.NewFile(uintptr(fd), dev)
James Ring65795102014-12-17 13:01:03 -0800400 attr := os.ProcAttr{
James Ring0d525c22014-12-30 12:03:31 -0800401 Files: []*os.File{file, file, file},
James Ring65795102014-12-17 13:01:03 -0800402 Dir: cwd,
403 }
Cosmos Nicolaou93dd88b2015-02-19 15:10:53 -0800404 // Set up agent for Child.
Cosmos Nicolaou82d00d82015-02-10 21:31:00 -0800405 attr.Files = append(attr.Files, agentFile)
406 attr.Env = append(attr.Env, fmt.Sprintf("%s=%d", agent.FdVarName, len(attr.Files)-1))
James Ring65795102014-12-17 13:01:03 -0800407
Cosmos Nicolaoud21f6b12015-02-07 11:40:03 -0800408 // Set up environment for Child.
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700409 for _, v := range t.shell.Env() {
Cosmos Nicolaou1fcb6a32015-02-17 07:46:02 -0800410 attr.Env = append(attr.Env, v)
Cosmos Nicolaoud21f6b12015-02-07 11:40:03 -0800411 }
Cosmos Nicolaoud21f6b12015-02-07 11:40:03 -0800412
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700413 for i, td := range t.tempDirs {
Cosmos Nicolaou93dd88b2015-02-19 15:10:53 -0800414 attr.Env = append(attr.Env, fmt.Sprintf("V23_TMP_DIR%d=%s", i, td))
415 }
416
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700417 if len(t.cachedBinDir) > 0 {
418 attr.Env = append(attr.Env, "V23_BIN_DIR="+t.BinDir())
Cosmos Nicolaou93dd88b2015-02-19 15:10:53 -0800419 }
420 attr.Env = append(attr.Env, env...)
421
James Ring65795102014-12-17 13:01:03 -0800422 // Start up a new shell.
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700423 writeStringOrDie(t, file, ">> Starting a new interactive shell\n")
424 writeStringOrDie(t, file, "Hit CTRL-D to resume the test\n")
425 if len(t.builtBinaries) > 0 {
426 writeStringOrDie(t, file, "Built binaries:\n")
427 for _, value := range t.builtBinaries {
428 writeStringOrDie(t, file, "\t"+value.Path()+"\n")
James Ring65795102014-12-17 13:01:03 -0800429 }
430 }
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700431 if len(t.cachedBinDir) > 0 {
432 writeStringOrDie(t, file, fmt.Sprintf("Binaries are cached in %q\n", t.cachedBinDir))
Cosmos Nicolaoua866f262015-02-10 14:56:06 -0800433 } else {
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700434 writeStringOrDie(t, file, fmt.Sprintf("Caching of binaries was not enabled, being written to %q\n", t.binDir))
Cosmos Nicolaoua866f262015-02-10 14:56:06 -0800435 }
James Ring65795102014-12-17 13:01:03 -0800436
437 shellPath := "/bin/sh"
James Ring2fdeb452015-02-02 21:18:22 -0800438 if shellPathFromEnv := os.Getenv("SHELL"); shellPathFromEnv != "" {
James Ringaa701492015-02-03 11:43:51 -0800439 shellPath = shellPathFromEnv
James Ring2fdeb452015-02-02 21:18:22 -0800440 }
James Ring65795102014-12-17 13:01:03 -0800441 proc, err := os.StartProcess(shellPath, []string{}, &attr)
442 if err != nil {
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700443 t.Fatalf("StartProcess(%q) failed: %v", shellPath, err)
James Ring65795102014-12-17 13:01:03 -0800444 }
445
446 // Wait until user exits the shell
447 state, err := proc.Wait()
448 if err != nil {
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700449 t.Fatalf("Wait(%v) failed: %v", shellPath, err)
James Ring65795102014-12-17 13:01:03 -0800450 }
451
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700452 writeStringOrDie(t, file, fmt.Sprintf("<< Exited shell: %s\n", state.String()))
James Ring65795102014-12-17 13:01:03 -0800453}
454
Asim Shankar6cc759d2015-03-14 03:31:44 -0700455// BinaryFromPath returns a new Binary that, when started, will execute the
456// executable or script at the given path. The binary is assumed to not
457// implement the exec protocol defined in v.io/x/ref/lib/exec.
Cosmos Nicolaou01007a02015-02-11 15:38:38 -0800458//
459// E.g. env.BinaryFromPath("/bin/bash").Start("-c", "echo hello world").Output() -> "hello world"
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700460func (t *T) BinaryFromPath(path string) *Binary {
Cosmos Nicolaou01007a02015-02-11 15:38:38 -0800461 return &Binary{
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700462 env: t,
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -0800463 envVars: nil,
464 path: path,
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700465 opts: t.shell.DefaultStartOpts().NoExecCommand(),
James Ring90d21dc2015-02-02 18:10:38 -0800466 }
467}
468
Asim Shankar6cc759d2015-03-14 03:31:44 -0700469// BuildGoPkg expects a Go package path that identifies a "main" package and
470// returns a Binary representing the newly built binary. This binary does not
471// use the exec protocol defined in v.io/x/ref/lib/exec. Use this for
472// non-Vanadium command-line tools and servers.
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700473func (t *T) BuildGoPkg(pkg string) *Binary {
474 return t.buildPkg(pkg)
475}
476
477// BuildV23 is like BuildGoPkg, but instead assumes that the resulting
478// binary is a Vanadium application and does implement the exec protocol
479// defined in v.io/x/ref/lib/exec. Use this for Vanadium servers.
480func (t *T) BuildV23Pkg(pkg string) *Binary {
481 b := t.buildPkg(pkg)
482 b.opts = t.shell.DefaultStartOpts().ExternalCommand()
483 return b
484}
485
486func (t *T) buildPkg(pkg string) *Binary {
Cosmos Nicolaou82d00d82015-02-10 21:31:00 -0800487 then := time.Now()
Cosmos Nicolaou93dd88b2015-02-19 15:10:53 -0800488 loc := Caller(1)
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700489 cached, built_path, err := buildPkg(t.BinDir(), pkg)
James Ring65795102014-12-17 13:01:03 -0800490 if err != nil {
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700491 t.Fatalf("%s: buildPkg(%s) failed: %v", loc, pkg, err)
James Ring65795102014-12-17 13:01:03 -0800492 return nil
493 }
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -0800494 if _, err := os.Stat(built_path); err != nil {
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700495 t.Fatalf("%s: buildPkg(%s) failed to stat %q", loc, pkg, built_path)
Cosmos Nicolaou82d00d82015-02-10 21:31:00 -0800496 }
497 taken := time.Now().Sub(then)
498 if cached {
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -0800499 vlog.Infof("%s: using %s, from %s in %s.", loc, pkg, built_path, taken)
Cosmos Nicolaoua866f262015-02-10 14:56:06 -0800500 } else {
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -0800501 vlog.Infof("%s: built %s, written to %s in %s.", loc, pkg, built_path, taken)
Cosmos Nicolaoua866f262015-02-10 14:56:06 -0800502 }
Cosmos Nicolaou01007a02015-02-11 15:38:38 -0800503 binary := &Binary{
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700504 env: t,
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -0800505 envVars: nil,
506 path: built_path,
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700507 opts: t.shell.DefaultStartOpts().NoExecCommand(),
James Ring65795102014-12-17 13:01:03 -0800508 }
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700509 t.builtBinaries[pkg] = binary
James Ring65795102014-12-17 13:01:03 -0800510 return binary
511}
512
Cosmos Nicolaou93dd88b2015-02-19 15:10:53 -0800513// NewTempFile creates a temporary file. Temporary files will be deleted
Cosmos Nicolaou01007a02015-02-11 15:38:38 -0800514// by Cleanup.
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700515func (t *T) NewTempFile() *os.File {
Cosmos Nicolaou93dd88b2015-02-19 15:10:53 -0800516 loc := Caller(1)
James Ring65795102014-12-17 13:01:03 -0800517 f, err := ioutil.TempFile("", "")
518 if err != nil {
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700519 t.Fatalf("%s: TempFile() failed: %v", loc, err)
James Ring65795102014-12-17 13:01:03 -0800520 }
Cosmos Nicolaou1fcb6a32015-02-17 07:46:02 -0800521 vlog.Infof("%s: created temporary file at %s", loc, f.Name())
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700522 t.tempFiles = append(t.tempFiles, f)
James Ring65795102014-12-17 13:01:03 -0800523 return f
524}
525
Cosmos Nicolaou93dd88b2015-02-19 15:10:53 -0800526// NewTempDir creates a temporary directory. Temporary directories and
Cosmos Nicolaou01007a02015-02-11 15:38:38 -0800527// their contents will be deleted by Cleanup.
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700528func (t *T) NewTempDir() string {
Cosmos Nicolaou93dd88b2015-02-19 15:10:53 -0800529 loc := Caller(1)
James Ring23b54862015-01-07 11:54:48 -0800530 f, err := ioutil.TempDir("", "")
531 if err != nil {
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700532 t.Fatalf("%s: TempDir() failed: %v", loc, err)
James Ring23b54862015-01-07 11:54:48 -0800533 }
Cosmos Nicolaou1fcb6a32015-02-17 07:46:02 -0800534 vlog.Infof("%s: created temporary directory at %s", loc, f)
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700535 t.tempDirs = append(t.tempDirs, f)
James Ring23b54862015-01-07 11:54:48 -0800536 return f
537}
538
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700539func (t *T) appendInvocation(inv *Invocation) {
540 t.invocations = append(t.invocations, inv)
Cosmos Nicolaoud21f6b12015-02-07 11:40:03 -0800541}
542
James Ringa71e49a2015-01-16 14:08:02 -0800543// Creates a new local testing environment. A local testing environment has a
Cosmos Nicolaoud21f6b12015-02-07 11:40:03 -0800544// a security principle available via Principal().
James Ringa71e49a2015-01-16 14:08:02 -0800545//
546// You should clean up the returned environment using the env.Cleanup() method.
547// A typical end-to-end test will begin like:
548//
549// func TestFoo(t *testing.T) {
Cosmos Nicolaou01007a02015-02-11 15:38:38 -0800550// env := integration.NewT(t)
James Ringa71e49a2015-01-16 14:08:02 -0800551// defer env.Cleanup()
552//
553// ...
554// }
Cosmos Nicolaoud3b1cb12015-02-20 00:06:33 -0800555func New(t TB) *T {
Jiri Simsa6ac95222015-02-23 16:11:49 -0800556 ctx, shutdown := v23.Init()
Suharsh Sivakumar19fbf992015-01-23 11:02:27 -0800557
Cosmos Nicolaoucc5a4a82015-02-07 23:09:28 -0800558 vlog.Infof("creating root principal")
James Ring65795102014-12-17 13:01:03 -0800559 principal := tsecurity.NewPrincipal("root")
Jiri Simsa6ac95222015-02-23 16:11:49 -0800560 ctx, err := v23.SetPrincipal(ctx, principal)
Ryan Browna08a2212015-01-15 15:40:10 -0800561 if err != nil {
Suharsh Sivakumar19fbf992015-01-23 11:02:27 -0800562 t.Fatalf("failed to set principal: %v", err)
Ryan Browna08a2212015-01-15 15:40:10 -0800563 }
Suharsh Sivakumar19fbf992015-01-23 11:02:27 -0800564
565 shell, err := modules.NewShell(ctx, principal)
James Ring65795102014-12-17 13:01:03 -0800566 if err != nil {
567 t.Fatalf("NewShell() failed: %v", err)
568 }
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700569 opts := modules.DefaultStartOpts()
570 opts.StartTimeout = time.Minute
571 opts.ShutdownTimeout = 5 * time.Minute
572 shell.SetDefaultStartOpts(opts)
James Ring65795102014-12-17 13:01:03 -0800573
Cosmos Nicolaou82d00d82015-02-10 21:31:00 -0800574 // The V23_BIN_DIR environment variable can be
575 // used to identify a directory that multiple integration
576 // tests can use to share binaries. Whoever sets this
577 // environment variable is responsible for cleaning up the
578 // directory it points to.
Cosmos Nicolaoua866f262015-02-10 14:56:06 -0800579 cachedBinDir := os.Getenv("V23_BIN_DIR")
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -0800580 e := &T{
Cosmos Nicolaoud3b1cb12015-02-20 00:06:33 -0800581 TB: t,
James Ring65795102014-12-17 13:01:03 -0800582 principal: principal,
Cosmos Nicolaou01007a02015-02-11 15:38:38 -0800583 builtBinaries: make(map[string]*Binary),
James Ring65795102014-12-17 13:01:03 -0800584 shell: shell,
James Ring65795102014-12-17 13:01:03 -0800585 tempFiles: []*os.File{},
James Ring23b54862015-01-07 11:54:48 -0800586 tempDirs: []string{},
Cosmos Nicolaoua866f262015-02-10 14:56:06 -0800587 cachedBinDir: cachedBinDir,
Suharsh Sivakumar19fbf992015-01-23 11:02:27 -0800588 shutdown: shutdown,
James Ring65795102014-12-17 13:01:03 -0800589 }
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -0800590 if len(e.cachedBinDir) == 0 {
591 e.binDir = e.NewTempDir()
592 }
593 return e
594}
595
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700596func (t *T) Shell() *modules.Shell {
597 return t.shell
598}
599
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -0800600// BinDir returns the directory that binarie files are stored in.
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700601func (t *T) BinDir() string {
602 if len(t.cachedBinDir) > 0 {
603 return t.cachedBinDir
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -0800604 }
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700605 return t.binDir
James Ring65795102014-12-17 13:01:03 -0800606}
607
James Ringbd2216f2015-01-15 20:06:18 -0800608// BuildPkg returns a path to a directory that contains the built binary for
609// the given packages and a function that should be invoked to clean up the
610// build artifacts. Note that the clients of this function should not modify
611// the contents of this directory directly and instead defer to the cleanup
612// function.
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -0800613func buildPkg(binDir, pkg string) (bool, string, error) {
James Ringbd2216f2015-01-15 20:06:18 -0800614 binFile := filepath.Join(binDir, path.Base(pkg))
615 if _, err := os.Stat(binFile); err != nil {
616 if !os.IsNotExist(err) {
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -0800617 return false, "", err
James Ringbd2216f2015-01-15 20:06:18 -0800618 }
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -0800619 cmd := exec.Command("v23", "go", "build", "-o", binFile, pkg)
Cosmos Nicolaou1fcb6a32015-02-17 07:46:02 -0800620 if output, err := cmd.CombinedOutput(); err != nil {
621 vlog.VI(1).Infof("\n%v:\n%v\n", strings.Join(cmd.Args, " "), string(output))
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -0800622 return false, "", err
Jiri Simsae63e07d2014-12-04 11:12:08 -0800623 }
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -0800624 return false, binFile, nil
Jiri Simsae63e07d2014-12-04 11:12:08 -0800625 }
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -0800626 return true, binFile, nil
Jiri Simsae63e07d2014-12-04 11:12:08 -0800627}
628
Cosmos Nicolaou4a77c192015-02-08 15:29:18 -0800629// RunTest runs a single Vanadium 'v23 style' integration test.
Cosmos Nicolaou01007a02015-02-11 15:38:38 -0800630func RunTest(t *testing.T, fn func(i *T)) {
Cosmos Nicolaou1381f8a2015-03-13 09:40:34 -0700631 if !test.IntegrationTestsEnabled {
Cosmos Nicolaoud21f6b12015-02-07 11:40:03 -0800632 t.Skip()
Jiri Simsae63e07d2014-12-04 11:12:08 -0800633 }
Cosmos Nicolaoud21f6b12015-02-07 11:40:03 -0800634 i := New(t)
Cosmos Nicolaoucc5a4a82015-02-07 23:09:28 -0800635 // defer the Cleanup method so that it will be called even if
636 // t.Fatalf/FailNow etc are called and can print out useful information.
637 defer i.Cleanup()
Cosmos Nicolaoud21f6b12015-02-07 11:40:03 -0800638 fn(i)
Jiri Simsae63e07d2014-12-04 11:12:08 -0800639}
Cosmos Nicolaoud21f6b12015-02-07 11:40:03 -0800640
Cosmos Nicolaou4a77c192015-02-08 15:29:18 -0800641// RunRootMT builds and runs a root mount table instance. It populates
642// the NAMESPACE_ROOT variable in the test environment so that all subsequent
643// invocations will access this root mount table.
Cosmos Nicolaou01007a02015-02-11 15:38:38 -0800644func RunRootMT(i *T, args ...string) (*Binary, *Invocation) {
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700645 b := i.BuildV23Pkg("v.io/x/ref/services/mounttable/mounttabled")
Cosmos Nicolaoud3b1cb12015-02-20 00:06:33 -0800646 inv := b.start(1, args...)
Cosmos Nicolaou01007a02015-02-11 15:38:38 -0800647 name := inv.ExpectVar("NAME")
648 inv.Environment().SetVar("NAMESPACE_ROOT", name)
Cosmos Nicolaou82d00d82015-02-10 21:31:00 -0800649 vlog.Infof("Running root mount table: %q", name)
Cosmos Nicolaou01007a02015-02-11 15:38:38 -0800650 return b, inv
Cosmos Nicolaoud21f6b12015-02-07 11:40:03 -0800651}
652
Cosmos Nicolaou93dd88b2015-02-19 15:10:53 -0800653// UseSharedBinDir ensures that a shared directory is used for binaries
Cosmos Nicolaou82d00d82015-02-10 21:31:00 -0800654// across multiple instances of the test environment. This is achieved
655// by setting the V23_BIN_DIR environment variable if it is not already
656// set in the test processes environment (as will typically be the case when
657// these tests are run from the v23 tool). It is intended to be called
658// from TestMain.
659func UseSharedBinDir() func() {
660 if v23BinDir := os.Getenv("V23_BIN_DIR"); len(v23BinDir) == 0 {
661 v23BinDir, err := ioutil.TempDir("", "bin-")
662 if err == nil {
663 vlog.Infof("Setting V23_BIN_DIR to %q", v23BinDir)
664 os.Setenv("V23_BIN_DIR", v23BinDir)
665 return func() { os.RemoveAll(v23BinDir) }
666 }
667 } else {
668 vlog.Infof("Using V23_BIN_DIR %q", v23BinDir)
669 }
670 return func() {}
671}