blob: 5c55cc0850c923d896daf7c1e7e6332f803c3feb [file] [log] [blame]
Jiri Simsad7616c92015-03-24 23:44:30 -07001// Copyright 2015 The Vanadium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
Cosmos Nicolaou4a77c192015-02-08 15:29:18 -08005package v23tests
Jiri Simsae63e07d2014-12-04 11:12:08 -08006
7import (
Cosmos Nicolaoucc5a4a82015-02-07 23:09:28 -08008 "errors"
Jiri Simsae63e07d2014-12-04 11:12:08 -08009 "fmt"
10 "io/ioutil"
Cosmos Nicolaou47d55d92015-04-08 14:49:12 -070011 "math/rand"
Jiri Simsae63e07d2014-12-04 11:12:08 -080012 "os"
13 "os/exec"
14 "path"
15 "path/filepath"
Cosmos Nicolaou1fcb6a32015-02-17 07:46:02 -080016 "runtime"
Jiri Simsae63e07d2014-12-04 11:12:08 -080017 "strings"
James Ring65795102014-12-17 13:01:03 -080018 "syscall"
Cosmos Nicolaou01007a02015-02-11 15:38:38 -080019 "testing"
Jiri Simsae63e07d2014-12-04 11:12:08 -080020 "time"
21
Cosmos Nicolaou47d55d92015-04-08 14:49:12 -070022 "v.io/x/lib/vlog"
23
Cosmos Nicolaou1381f8a2015-03-13 09:40:34 -070024 "v.io/v23"
25 "v.io/v23/security"
Cosmos Nicolaou47d55d92015-04-08 14:49:12 -070026
Asim Shankar59b8b692015-03-30 01:23:36 -070027 "v.io/x/ref/envvar"
Todd Wang88509682015-04-10 10:28:24 -070028 "v.io/x/ref/services/agent/agentlib"
Cosmos Nicolaou1381f8a2015-03-13 09:40:34 -070029 "v.io/x/ref/test"
30 "v.io/x/ref/test/modules"
Asim Shankar4a698282015-03-21 21:59:18 -070031 "v.io/x/ref/test/testutil"
Jiri Simsae63e07d2014-12-04 11:12:08 -080032)
33
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -080034// TB is an exact mirror of testing.TB. It is provided to allow for testing
35// of this package using a mock implementation. As per testing.TB, it is not
36// intended to be implemented outside of this package.
Cosmos Nicolaoud3b1cb12015-02-20 00:06:33 -080037type TB interface {
38 Error(args ...interface{})
39 Errorf(format string, args ...interface{})
40 Fail()
41 FailNow()
42 Failed() bool
43 Fatal(args ...interface{})
44 Fatalf(format string, args ...interface{})
45 Log(args ...interface{})
46 Logf(format string, args ...interface{})
47 Skip(args ...interface{})
48 SkipNow()
49 Skipf(format string, args ...interface{})
50 Skipped() bool
51}
James Ring855dfd42015-01-23 12:30:58 -080052
Cosmos Nicolaoucc5a4a82015-02-07 23:09:28 -080053// T represents an integration test environment.
Cosmos Nicolaou01007a02015-02-11 15:38:38 -080054type T struct {
Cosmos Nicolaoud3b1cb12015-02-20 00:06:33 -080055 // The embedded TB
56 TB
James Ring65795102014-12-17 13:01:03 -080057
Suharsh Sivakumar19fbf992015-01-23 11:02:27 -080058 // The function to shutdown the context used to create the environment.
Jiri Simsa6ac95222015-02-23 16:11:49 -080059 shutdown v23.Shutdown
Suharsh Sivakumar19fbf992015-01-23 11:02:27 -080060
James Ring65795102014-12-17 13:01:03 -080061 // The shell to use to start commands.
62 shell *modules.Shell
63
64 // The environment's root security principal.
65 principal security.Principal
66
Cosmos Nicolaou01007a02015-02-11 15:38:38 -080067 // Maps path to Binary.
68 builtBinaries map[string]*Binary
James Ring65795102014-12-17 13:01:03 -080069
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -080070 tempFiles []*os.File
71 tempDirs []string
72 binDir, cachedBinDir string
73 dirStack []string
Cosmos Nicolaoud21f6b12015-02-07 11:40:03 -080074
Cosmos Nicolaou01007a02015-02-11 15:38:38 -080075 invocations []*Invocation
James Ring65795102014-12-17 13:01:03 -080076}
77
Cosmos Nicolaoucc5a4a82015-02-07 23:09:28 -080078var errNotShutdown = errors.New("has not been shutdown")
79
Cosmos Nicolaou93dd88b2015-02-19 15:10:53 -080080// Caller returns a string of the form <filename>:<lineno> for the
81// caller specified by skip, where skip is as per runtime.Caller.
82func Caller(skip int) string {
83 _, file, line, _ := runtime.Caller(skip + 1)
Cosmos Nicolaou1fcb6a32015-02-17 07:46:02 -080084 return fmt.Sprintf("%s:%d", filepath.Base(file), line)
James Ring65795102014-12-17 13:01:03 -080085}
86
Matt Rosencrantz8df1ac32015-04-27 20:21:28 -070087// SkipInRegressionBefore skips the test if this is being run in a regression
88// test and some of the binaries are older than the specified date.
89// This is useful when we're breaking compatibility and we know it.
90// The parameter is a string formatted date YYYY-MM-DD.
91func (t *T) SkipInRegressionBefore(dateStr string) {
92 testStr := os.Getenv("V23_REGTEST_DATE")
93 if testStr == "" {
94 return
95 }
96 testDate, err := time.Parse("2006-01-02", testStr)
97 if err != nil {
98 t.Fatalf("%s: could not parse V23_REGTEST_DATE=%q: %v", Caller(1), testStr, err)
99 }
100 date, err := time.Parse("2006-01-02", dateStr)
101 if err != nil {
102 t.Fatalf("%s: could not parse date %q: %v", Caller(1), dateStr, err)
103 }
104 if testDate.Before(date) {
105 t.Skipf("Skipping regression test with binary from %s", testStr)
106 }
107}
108
Cosmos Nicolaoud3b1cb12015-02-20 00:06:33 -0800109// Run constructs a Binary for path and invokes Run on it.
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700110func (t *T) Run(path string, args ...string) string {
111 return t.BinaryFromPath(path).run(args...)
112}
113
114// Run constructs a Binary for path and invokes Run on it using
115// the specified StartOpts
116func (t *T) RunWithOpts(opts modules.StartOpts, path string, args ...string) string {
117 b := t.BinaryFromPath(path)
118 return b.WithStartOpts(opts).run(args...)
Cosmos Nicolaoud3b1cb12015-02-20 00:06:33 -0800119}
120
121// WaitFunc is the type of the functions to be used in conjunction
122// with WaitFor and WaitForAsync. It should return a value or an error
123// when it wants those functions to terminate, returning a nil value
124// and nil error will result in it being called again after the specified
125// delay time specified in the calls to WaitFor and WaitForAsync.
126type WaitFunc func() (interface{}, error)
127
128// WaitFor calls fn at least once with the specified delay value
129// between iterations until the first of the following is encountered:
130// 1. fn returns a non-nil value.
131// 2. fn returns an error value
132// 3. fn is executed at least once and the specified timeout is exceeded.
133//
134// WaitFor returns the non-nil value for the first case and calls e.Fatalf for
135// the other two cases.
136// WaitFor will always run fn at least once to completion and hence it will
137// hang if that first iteration of fn hangs. If this behaviour is not
138// appropriate, then WaitForAsync should be used.
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700139func (t *T) WaitFor(fn WaitFunc, delay, timeout time.Duration) interface{} {
Cosmos Nicolaoud3b1cb12015-02-20 00:06:33 -0800140 deadline := time.Now().Add(timeout)
141 for {
142 val, err := fn()
143 if val != nil {
144 return val
145 }
146 if err != nil {
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700147 t.Fatalf("%s: the WaitFunc returned an error: %v", Caller(1), err)
Cosmos Nicolaoud3b1cb12015-02-20 00:06:33 -0800148 }
149 if time.Now().After(deadline) {
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700150 t.Fatalf("%s: timed out after %s", Caller(1), timeout)
Cosmos Nicolaoud3b1cb12015-02-20 00:06:33 -0800151 }
152 time.Sleep(delay)
153 }
154}
155
156// WaitForAsync is like WaitFor except that it calls fn in a goroutine
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -0800157// and can timeout during the execution of fn.
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700158func (t *T) WaitForAsync(fn WaitFunc, delay, timeout time.Duration) interface{} {
Cosmos Nicolaoud3b1cb12015-02-20 00:06:33 -0800159 resultCh := make(chan interface{})
160 errCh := make(chan interface{})
161 go func() {
162 for {
163 val, err := fn()
164 if val != nil {
165 resultCh <- val
166 return
167 }
168 if err != nil {
169 errCh <- err
170 return
171 }
172 time.Sleep(delay)
173 }
174 }()
175 select {
176 case err := <-errCh:
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700177 t.Fatalf("%s: the WaitFunc returned error: %v", Caller(1), err)
Cosmos Nicolaoud3b1cb12015-02-20 00:06:33 -0800178 case result := <-resultCh:
179 return result
180 case <-time.After(timeout):
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700181 t.Fatalf("%s: timed out after %s", Caller(1), timeout)
Cosmos Nicolaoud3b1cb12015-02-20 00:06:33 -0800182 }
183 return nil
184}
185
186// Pushd pushes the current working directory to the stack of
187// directories, returning it as its result, and changes the working
188// directory to dir.
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700189func (t *T) Pushd(dir string) string {
Cosmos Nicolaoud3b1cb12015-02-20 00:06:33 -0800190 cwd, err := os.Getwd()
191 if err != nil {
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700192 t.Fatalf("%s: Getwd failed: %s", Caller(1), err)
Cosmos Nicolaoud3b1cb12015-02-20 00:06:33 -0800193 }
194 if err := os.Chdir(dir); err != nil {
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700195 t.Fatalf("%s: Chdir failed: %s", Caller(1), err)
Cosmos Nicolaoud3b1cb12015-02-20 00:06:33 -0800196 }
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -0800197 vlog.VI(1).Infof("Pushd: %s -> %s", cwd, dir)
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700198 t.dirStack = append(t.dirStack, cwd)
Cosmos Nicolaoud3b1cb12015-02-20 00:06:33 -0800199 return cwd
200}
201
202// Popd pops the most recent entry from the directory stack and changes
203// the working directory to that directory. It returns the new working
204// directory as its result.
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700205func (t *T) Popd() string {
206 if len(t.dirStack) == 0 {
207 t.Fatalf("%s: directory stack empty", Caller(1))
Cosmos Nicolaoud3b1cb12015-02-20 00:06:33 -0800208 }
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700209 dir := t.dirStack[len(t.dirStack)-1]
210 t.dirStack = t.dirStack[:len(t.dirStack)-1]
Cosmos Nicolaoud3b1cb12015-02-20 00:06:33 -0800211 if err := os.Chdir(dir); err != nil {
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700212 t.Fatalf("%s: Chdir failed: %s", Caller(1), err)
Cosmos Nicolaoud3b1cb12015-02-20 00:06:33 -0800213 }
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -0800214 vlog.VI(1).Infof("Popd: -> %s", dir)
Cosmos Nicolaoud3b1cb12015-02-20 00:06:33 -0800215 return dir
216}
217
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -0800218// Caller returns a string of the form <filename>:<lineno> for the
219// caller specified by skip, where skip is as per runtime.Caller.
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700220func (t *T) Caller(skip int) string {
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -0800221 return Caller(skip + 1)
222}
223
Cosmos Nicolaou01007a02015-02-11 15:38:38 -0800224// Principal returns the security principal of this environment.
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700225func (t *T) Principal() security.Principal {
226 return t.principal
James Ring65795102014-12-17 13:01:03 -0800227}
228
Cosmos Nicolaou01007a02015-02-11 15:38:38 -0800229// Cleanup cleans up the environment, deletes all its artifacts and
230// kills all subprocesses. It will kill subprocesses in LIFO order.
231// Cleanup checks to see if the test has failed and logs information
232// as to the state of the processes it was asked to invoke up to that
233// point and optionally, if the --v23.tests.shell-on-fail flag is set
234// then it will run a debug shell before cleaning up its state.
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700235func (t *T) Cleanup() {
236 if t.Failed() {
Cosmos Nicolaou1381f8a2015-03-13 09:40:34 -0700237 if test.IntegrationTestsDebugShellOnError {
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700238 t.DebugSystemShell()
Cosmos Nicolaoucc5a4a82015-02-07 23:09:28 -0800239 }
240 // Print out a summary of the invocations and their status.
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700241 for i, inv := range t.invocations {
Cosmos Nicolaoucc5a4a82015-02-07 23:09:28 -0800242 if inv.hasShutdown && inv.Exists() {
243 m := fmt.Sprintf("%d: %s has been shutdown but still exists: %v", i, inv.path, inv.shutdownErr)
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700244 t.Log(m)
Cosmos Nicolaoucc5a4a82015-02-07 23:09:28 -0800245 vlog.VI(1).Info(m)
246 vlog.VI(2).Infof("%d: %s %v", i, inv.path, inv.args)
247 continue
248 }
Cosmos Nicolaou93dd88b2015-02-19 15:10:53 -0800249 if inv.shutdownErr != nil {
250 m := fmt.Sprintf("%d: %s: shutdown status: %v", i, inv.path, inv.shutdownErr)
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700251 t.Log(m)
Cosmos Nicolaou93dd88b2015-02-19 15:10:53 -0800252 vlog.VI(1).Info(m)
253 vlog.VI(2).Infof("%d: %s %v", i, inv.path, inv.args)
254 }
Cosmos Nicolaoucc5a4a82015-02-07 23:09:28 -0800255 }
256 }
257
Cosmos Nicolaoud21f6b12015-02-07 11:40:03 -0800258 vlog.VI(1).Infof("V23Test.Cleanup")
Cosmos Nicolaou82d00d82015-02-10 21:31:00 -0800259 // Shut down all processes in LIFO order before attempting to delete any
Cosmos Nicolaoud21f6b12015-02-07 11:40:03 -0800260 // files/directories to avoid potential 'file system busy' problems
261 // on non-unix systems.
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700262 for i := len(t.invocations); i > 0; i-- {
263 inv := t.invocations[i-1]
Cosmos Nicolaoucc5a4a82015-02-07 23:09:28 -0800264 if inv.hasShutdown {
265 vlog.VI(1).Infof("V23Test.Cleanup: %q has been shutdown", inv.Path())
266 continue
Cosmos Nicolaoud21f6b12015-02-07 11:40:03 -0800267 }
Cosmos Nicolaoud21f6b12015-02-07 11:40:03 -0800268 vlog.VI(1).Infof("V23Test.Cleanup: Kill: %q", inv.Path())
269 err := inv.Kill(syscall.SIGTERM)
270 inv.Wait(os.Stdout, os.Stderr)
271 vlog.VI(1).Infof("V23Test.Cleanup: Killed: %q: %v", inv.Path(), err)
272 }
Cosmos Nicolaoucc5a4a82015-02-07 23:09:28 -0800273 vlog.VI(1).Infof("V23Test.Cleanup: all invocations taken care of.")
Cosmos Nicolaoud21f6b12015-02-07 11:40:03 -0800274
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700275 if err := t.shell.Cleanup(os.Stdout, os.Stderr); err != nil {
276 t.Fatalf("WARNING: could not clean up shell (%v)", err)
Cosmos Nicolaoud21f6b12015-02-07 11:40:03 -0800277 }
278
Cosmos Nicolaoucc5a4a82015-02-07 23:09:28 -0800279 vlog.VI(1).Infof("V23Test.Cleanup: cleaning up binaries & files")
280
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700281 for _, tempFile := range t.tempFiles {
Cosmos Nicolaoucc5a4a82015-02-07 23:09:28 -0800282 vlog.VI(1).Infof("V23Test.Cleanup: cleaning up %s", tempFile.Name())
James Ring65795102014-12-17 13:01:03 -0800283 if err := tempFile.Close(); err != nil {
Cosmos Nicolaoucc5a4a82015-02-07 23:09:28 -0800284 vlog.Errorf("WARNING: Close(%q) failed: %v", tempFile.Name(), err)
James Ring65795102014-12-17 13:01:03 -0800285 }
James Ring23b54862015-01-07 11:54:48 -0800286 if err := os.RemoveAll(tempFile.Name()); err != nil {
Cosmos Nicolaoucc5a4a82015-02-07 23:09:28 -0800287 vlog.Errorf("WARNING: RemoveAll(%q) failed: %v", tempFile.Name(), err)
James Ring23b54862015-01-07 11:54:48 -0800288 }
289 }
290
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700291 for _, tempDir := range t.tempDirs {
Cosmos Nicolaoucc5a4a82015-02-07 23:09:28 -0800292 vlog.VI(1).Infof("V23Test.Cleanup: cleaning up %s", tempDir)
Cosmos Nicolaou185c0c62015-04-13 21:22:43 -0700293 vlog.Infof("V23Test.Cleanup: cleaning up %s", tempDir)
James Ring23b54862015-01-07 11:54:48 -0800294 if err := os.RemoveAll(tempDir); err != nil {
Cosmos Nicolaoucc5a4a82015-02-07 23:09:28 -0800295 vlog.Errorf("WARNING: RemoveAll(%q) failed: %v", tempDir, err)
James Ring65795102014-12-17 13:01:03 -0800296 }
297 }
298
Cosmos Nicolaoucc5a4a82015-02-07 23:09:28 -0800299 // shutdown the runtime
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700300 t.shutdown()
James Ring65795102014-12-17 13:01:03 -0800301}
302
Cosmos Nicolaou01007a02015-02-11 15:38:38 -0800303// GetVar returns the variable associated with the specified key
304// and an indication of whether it is defined or not.
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700305func (t *T) GetVar(key string) (string, bool) {
306 return t.shell.GetVar(key)
Cosmos Nicolaoud21f6b12015-02-07 11:40:03 -0800307}
308
Cosmos Nicolaou01007a02015-02-11 15:38:38 -0800309// SetVar sets the value to be associated with key.
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700310func (t *T) SetVar(key, value string) {
311 t.shell.SetVar(key, value)
Cosmos Nicolaoud21f6b12015-02-07 11:40:03 -0800312}
313
Cosmos Nicolaou01007a02015-02-11 15:38:38 -0800314// ClearVar removes the speficied variable from the Shell's environment
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700315func (t *T) ClearVar(key string) {
316 t.shell.ClearVar(key)
Cosmos Nicolaoud21f6b12015-02-07 11:40:03 -0800317}
318
Cosmos Nicolaou01007a02015-02-11 15:38:38 -0800319func writeStringOrDie(t *T, f *os.File, s string) {
James Ring0bad5672015-01-05 09:51:36 -0800320 if _, err := f.WriteString(s); err != nil {
James Ring0d525c22014-12-30 12:03:31 -0800321 t.Fatalf("Write() failed: %v", err)
322 }
323}
324
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700325// DebugSystemShell drops the user into a debug system shell (e.g. bash)
326// with any environment variables specified in env... (in VAR=VAL format)
327// available to it.
328// If there is no controlling TTY, DebugSystemShell will emit a warning message
329// and take no futher action. The DebugSystemShell also sets some environment
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -0800330// variables that relate to the running test:
331// - V23_TMP_DIR<#> contains the name of each temp directory created.
332// - V23_BIN_DIR contains the name of the directory containing binaries.
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700333func (t *T) DebugSystemShell(env ...string) {
James Ring65795102014-12-17 13:01:03 -0800334 // Get the current working directory.
335 cwd, err := os.Getwd()
336 if err != nil {
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700337 t.Fatalf("Getwd() failed: %v", err)
James Ring65795102014-12-17 13:01:03 -0800338 }
339
340 // Transfer stdin, stdout, and stderr to the new process
341 // and also set target directory for the shell to start in.
342 dev := "/dev/tty"
James Ring0d525c22014-12-30 12:03:31 -0800343 fd, err := syscall.Open(dev, syscall.O_RDWR, 0)
James Ring65795102014-12-17 13:01:03 -0800344 if err != nil {
Cosmos Nicolaoucc5a4a82015-02-07 23:09:28 -0800345 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 -0800346 return
347 }
Cosmos Nicolaou82d00d82015-02-10 21:31:00 -0800348
Ryan Brown61d69382015-02-25 11:13:39 -0800349 var agentFile *os.File
Asim Shankar34fb2492015-03-12 10:25:46 -0700350 if creds, err := t.shell.NewChildCredentials("debug"); err == nil {
Ryan Brown61d69382015-02-25 11:13:39 -0800351 if agentFile, err = creds.File(); err != nil {
352 vlog.Errorf("WARNING: failed to obtain credentials for the debug shell: %v", err)
353 }
354 } else {
Cosmos Nicolaou82d00d82015-02-10 21:31:00 -0800355 vlog.Errorf("WARNING: failed to obtain credentials for the debug shell: %v", err)
356 }
357
James Ring0d525c22014-12-30 12:03:31 -0800358 file := os.NewFile(uintptr(fd), dev)
James Ring65795102014-12-17 13:01:03 -0800359 attr := os.ProcAttr{
James Ring0d525c22014-12-30 12:03:31 -0800360 Files: []*os.File{file, file, file},
James Ring65795102014-12-17 13:01:03 -0800361 Dir: cwd,
362 }
Cosmos Nicolaou93dd88b2015-02-19 15:10:53 -0800363 // Set up agent for Child.
Cosmos Nicolaou82d00d82015-02-10 21:31:00 -0800364 attr.Files = append(attr.Files, agentFile)
Ryan Brown7f950a82015-04-20 18:08:39 -0700365 attr.Env = append(attr.Env, fmt.Sprintf("%s=%d", envvar.AgentEndpoint, agentlib.AgentEndpoint(len(attr.Files)-1)))
James Ring65795102014-12-17 13:01:03 -0800366
Cosmos Nicolaoud21f6b12015-02-07 11:40:03 -0800367 // Set up environment for Child.
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700368 for _, v := range t.shell.Env() {
Cosmos Nicolaou1fcb6a32015-02-17 07:46:02 -0800369 attr.Env = append(attr.Env, v)
Cosmos Nicolaoud21f6b12015-02-07 11:40:03 -0800370 }
Cosmos Nicolaoud21f6b12015-02-07 11:40:03 -0800371
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700372 for i, td := range t.tempDirs {
Cosmos Nicolaou93dd88b2015-02-19 15:10:53 -0800373 attr.Env = append(attr.Env, fmt.Sprintf("V23_TMP_DIR%d=%s", i, td))
374 }
375
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700376 if len(t.cachedBinDir) > 0 {
377 attr.Env = append(attr.Env, "V23_BIN_DIR="+t.BinDir())
Cosmos Nicolaou93dd88b2015-02-19 15:10:53 -0800378 }
379 attr.Env = append(attr.Env, env...)
380
James Ring65795102014-12-17 13:01:03 -0800381 // Start up a new shell.
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700382 writeStringOrDie(t, file, ">> Starting a new interactive shell\n")
383 writeStringOrDie(t, file, "Hit CTRL-D to resume the test\n")
384 if len(t.builtBinaries) > 0 {
385 writeStringOrDie(t, file, "Built binaries:\n")
386 for _, value := range t.builtBinaries {
387 writeStringOrDie(t, file, "\t"+value.Path()+"\n")
James Ring65795102014-12-17 13:01:03 -0800388 }
389 }
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700390 if len(t.cachedBinDir) > 0 {
391 writeStringOrDie(t, file, fmt.Sprintf("Binaries are cached in %q\n", t.cachedBinDir))
Cosmos Nicolaoua866f262015-02-10 14:56:06 -0800392 } else {
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700393 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 -0800394 }
James Ring65795102014-12-17 13:01:03 -0800395
396 shellPath := "/bin/sh"
James Ring2fdeb452015-02-02 21:18:22 -0800397 if shellPathFromEnv := os.Getenv("SHELL"); shellPathFromEnv != "" {
James Ringaa701492015-02-03 11:43:51 -0800398 shellPath = shellPathFromEnv
James Ring2fdeb452015-02-02 21:18:22 -0800399 }
James Ring65795102014-12-17 13:01:03 -0800400 proc, err := os.StartProcess(shellPath, []string{}, &attr)
401 if err != nil {
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700402 t.Fatalf("StartProcess(%q) failed: %v", shellPath, err)
James Ring65795102014-12-17 13:01:03 -0800403 }
404
405 // Wait until user exits the shell
406 state, err := proc.Wait()
407 if err != nil {
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700408 t.Fatalf("Wait(%v) failed: %v", shellPath, err)
James Ring65795102014-12-17 13:01:03 -0800409 }
410
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700411 writeStringOrDie(t, file, fmt.Sprintf("<< Exited shell: %s\n", state.String()))
James Ring65795102014-12-17 13:01:03 -0800412}
413
Asim Shankar6cc759d2015-03-14 03:31:44 -0700414// BinaryFromPath returns a new Binary that, when started, will execute the
415// executable or script at the given path. The binary is assumed to not
416// implement the exec protocol defined in v.io/x/ref/lib/exec.
Cosmos Nicolaou01007a02015-02-11 15:38:38 -0800417//
418// E.g. env.BinaryFromPath("/bin/bash").Start("-c", "echo hello world").Output() -> "hello world"
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700419func (t *T) BinaryFromPath(path string) *Binary {
Cosmos Nicolaou01007a02015-02-11 15:38:38 -0800420 return &Binary{
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700421 env: t,
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -0800422 envVars: nil,
423 path: path,
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700424 opts: t.shell.DefaultStartOpts().NoExecCommand(),
James Ring90d21dc2015-02-02 18:10:38 -0800425 }
426}
427
Todd Wang555097f2015-04-21 10:49:06 -0700428// BuildGoPkg expects a Go package path that identifies a "main" package, and
429// any build flags to pass to "go build", and returns a Binary representing the
430// newly built binary.
431//
432// The resulting binary is assumed to not use the exec protocol defined in
433// v.io/x/ref/lib/exec and in particular will not have access to the security
434// agent or any other shared file descriptors. Environment variables and
435// command line arguments are the only means of communicating with the
436// invocations of this binary.
437//
Cosmos Nicolaou52e9a222015-03-16 21:57:16 -0700438// Use this for non-Vanadium command-line tools and servers.
Todd Wang555097f2015-04-21 10:49:06 -0700439func (t *T) BuildGoPkg(pkg string, flags ...string) *Binary {
440 return t.buildPkg(pkg, flags...)
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700441}
442
Todd Wang555097f2015-04-21 10:49:06 -0700443// BuildV23 is like BuildGoPkg, but instead assumes that the resulting binary is
444// a Vanadium application and does implement the exec protocol defined in
445// v.io/x/ref/lib/exec. The invocations of this binary will have access to the
446// security agent configured for the parent process, to shared file descriptors,
447// the config shared by the exec package.
448//
Cosmos Nicolaou52e9a222015-03-16 21:57:16 -0700449// Use this for Vanadium servers. Note that some vanadium client only binaries,
Todd Wang555097f2015-04-21 10:49:06 -0700450// that do not call v23.Init and hence do not implement the exec protocol cannot
451// be used via BuildV23Pkg.
452func (t *T) BuildV23Pkg(pkg string, flags ...string) *Binary {
453 b := t.buildPkg(pkg, flags...)
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700454 b.opts = t.shell.DefaultStartOpts().ExternalCommand()
455 return b
456}
457
Todd Wang555097f2015-04-21 10:49:06 -0700458func (t *T) buildPkg(pkg string, flags ...string) *Binary {
Cosmos Nicolaou82d00d82015-02-10 21:31:00 -0800459 then := time.Now()
Cosmos Nicolaou93dd88b2015-02-19 15:10:53 -0800460 loc := Caller(1)
Todd Wang555097f2015-04-21 10:49:06 -0700461 cached, built_path, err := buildPkg(t, t.BinDir(), pkg, flags)
James Ring65795102014-12-17 13:01:03 -0800462 if err != nil {
Todd Wang555097f2015-04-21 10:49:06 -0700463 t.Fatalf("%s: buildPkg(%s, %v) failed: %v", loc, pkg, flags, err)
James Ring65795102014-12-17 13:01:03 -0800464 return nil
465 }
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -0800466 if _, err := os.Stat(built_path); err != nil {
Todd Wang555097f2015-04-21 10:49:06 -0700467 t.Fatalf("%s: buildPkg(%s, %v) failed to stat %q", loc, pkg, flags, built_path)
Cosmos Nicolaou82d00d82015-02-10 21:31:00 -0800468 }
469 taken := time.Now().Sub(then)
470 if cached {
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -0800471 vlog.Infof("%s: using %s, from %s in %s.", loc, pkg, built_path, taken)
Cosmos Nicolaoua866f262015-02-10 14:56:06 -0800472 } else {
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -0800473 vlog.Infof("%s: built %s, written to %s in %s.", loc, pkg, built_path, taken)
Cosmos Nicolaoua866f262015-02-10 14:56:06 -0800474 }
Cosmos Nicolaou01007a02015-02-11 15:38:38 -0800475 binary := &Binary{
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700476 env: t,
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -0800477 envVars: nil,
478 path: built_path,
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700479 opts: t.shell.DefaultStartOpts().NoExecCommand(),
James Ring65795102014-12-17 13:01:03 -0800480 }
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700481 t.builtBinaries[pkg] = binary
James Ring65795102014-12-17 13:01:03 -0800482 return binary
483}
484
Cosmos Nicolaou93dd88b2015-02-19 15:10:53 -0800485// NewTempFile creates a temporary file. Temporary files will be deleted
Cosmos Nicolaou01007a02015-02-11 15:38:38 -0800486// by Cleanup.
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700487func (t *T) NewTempFile() *os.File {
Cosmos Nicolaou93dd88b2015-02-19 15:10:53 -0800488 loc := Caller(1)
James Ring65795102014-12-17 13:01:03 -0800489 f, err := ioutil.TempFile("", "")
490 if err != nil {
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700491 t.Fatalf("%s: TempFile() failed: %v", loc, err)
James Ring65795102014-12-17 13:01:03 -0800492 }
Cosmos Nicolaou1fcb6a32015-02-17 07:46:02 -0800493 vlog.Infof("%s: created temporary file at %s", loc, f.Name())
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700494 t.tempFiles = append(t.tempFiles, f)
James Ring65795102014-12-17 13:01:03 -0800495 return f
496}
497
Cosmos Nicolaou93dd88b2015-02-19 15:10:53 -0800498// NewTempDir creates a temporary directory. Temporary directories and
Cosmos Nicolaou01007a02015-02-11 15:38:38 -0800499// their contents will be deleted by Cleanup.
Robert Kroeger02714b72015-04-14 18:02:38 -0700500func (t *T) NewTempDir(dir string) string {
Cosmos Nicolaou93dd88b2015-02-19 15:10:53 -0800501 loc := Caller(1)
Robert Kroeger02714b72015-04-14 18:02:38 -0700502 f, err := ioutil.TempDir(dir, "")
James Ring23b54862015-01-07 11:54:48 -0800503 if err != nil {
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700504 t.Fatalf("%s: TempDir() failed: %v", loc, err)
James Ring23b54862015-01-07 11:54:48 -0800505 }
Cosmos Nicolaou1fcb6a32015-02-17 07:46:02 -0800506 vlog.Infof("%s: created temporary directory at %s", loc, f)
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700507 t.tempDirs = append(t.tempDirs, f)
James Ring23b54862015-01-07 11:54:48 -0800508 return f
509}
510
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700511func (t *T) appendInvocation(inv *Invocation) {
512 t.invocations = append(t.invocations, inv)
Cosmos Nicolaoud21f6b12015-02-07 11:40:03 -0800513}
514
James Ringa71e49a2015-01-16 14:08:02 -0800515// Creates a new local testing environment. A local testing environment has a
Cosmos Nicolaoud21f6b12015-02-07 11:40:03 -0800516// a security principle available via Principal().
James Ringa71e49a2015-01-16 14:08:02 -0800517//
518// You should clean up the returned environment using the env.Cleanup() method.
519// A typical end-to-end test will begin like:
520//
521// func TestFoo(t *testing.T) {
Cosmos Nicolaou01007a02015-02-11 15:38:38 -0800522// env := integration.NewT(t)
James Ringa71e49a2015-01-16 14:08:02 -0800523// defer env.Cleanup()
524//
525// ...
526// }
Cosmos Nicolaoud3b1cb12015-02-20 00:06:33 -0800527func New(t TB) *T {
Jiri Simsa6ac95222015-02-23 16:11:49 -0800528 ctx, shutdown := v23.Init()
Suharsh Sivakumar19fbf992015-01-23 11:02:27 -0800529
Cosmos Nicolaoucc5a4a82015-02-07 23:09:28 -0800530 vlog.Infof("creating root principal")
Asim Shankar4a698282015-03-21 21:59:18 -0700531 principal := testutil.NewPrincipal("root")
Todd Wangad492042015-04-17 15:58:40 -0700532 ctx, err := v23.WithPrincipal(ctx, principal)
Ryan Browna08a2212015-01-15 15:40:10 -0800533 if err != nil {
Suharsh Sivakumar19fbf992015-01-23 11:02:27 -0800534 t.Fatalf("failed to set principal: %v", err)
Ryan Browna08a2212015-01-15 15:40:10 -0800535 }
Suharsh Sivakumar19fbf992015-01-23 11:02:27 -0800536
Cosmos Nicolaou9e909842015-03-17 11:58:59 -0700537 shell, err := modules.NewShell(ctx, principal, testing.Verbose(), t)
James Ring65795102014-12-17 13:01:03 -0800538 if err != nil {
539 t.Fatalf("NewShell() failed: %v", err)
540 }
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700541 opts := modules.DefaultStartOpts()
542 opts.StartTimeout = time.Minute
543 opts.ShutdownTimeout = 5 * time.Minute
544 shell.SetDefaultStartOpts(opts)
James Ring65795102014-12-17 13:01:03 -0800545
Cosmos Nicolaou82d00d82015-02-10 21:31:00 -0800546 // The V23_BIN_DIR environment variable can be
547 // used to identify a directory that multiple integration
548 // tests can use to share binaries. Whoever sets this
549 // environment variable is responsible for cleaning up the
550 // directory it points to.
Cosmos Nicolaoua866f262015-02-10 14:56:06 -0800551 cachedBinDir := os.Getenv("V23_BIN_DIR")
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -0800552 e := &T{
Cosmos Nicolaoud3b1cb12015-02-20 00:06:33 -0800553 TB: t,
James Ring65795102014-12-17 13:01:03 -0800554 principal: principal,
Cosmos Nicolaou01007a02015-02-11 15:38:38 -0800555 builtBinaries: make(map[string]*Binary),
James Ring65795102014-12-17 13:01:03 -0800556 shell: shell,
James Ring65795102014-12-17 13:01:03 -0800557 tempFiles: []*os.File{},
James Ring23b54862015-01-07 11:54:48 -0800558 tempDirs: []string{},
Cosmos Nicolaoua866f262015-02-10 14:56:06 -0800559 cachedBinDir: cachedBinDir,
Suharsh Sivakumar19fbf992015-01-23 11:02:27 -0800560 shutdown: shutdown,
James Ring65795102014-12-17 13:01:03 -0800561 }
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -0800562 if len(e.cachedBinDir) == 0 {
Robert Kroeger02714b72015-04-14 18:02:38 -0700563 e.binDir = e.NewTempDir("")
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -0800564 }
565 return e
566}
567
Cosmos Nicolaou52e9a222015-03-16 21:57:16 -0700568// Shell returns the underlying modules.Shell used by v23tests.
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700569func (t *T) Shell() *modules.Shell {
570 return t.shell
571}
572
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -0800573// BinDir returns the directory that binarie files are stored in.
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700574func (t *T) BinDir() string {
575 if len(t.cachedBinDir) > 0 {
576 return t.cachedBinDir
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -0800577 }
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700578 return t.binDir
James Ring65795102014-12-17 13:01:03 -0800579}
580
Todd Wang555097f2015-04-21 10:49:06 -0700581// buildPkg returns a path to a directory that contains the built binary for
James Ringbd2216f2015-01-15 20:06:18 -0800582// the given packages and a function that should be invoked to clean up the
583// build artifacts. Note that the clients of this function should not modify
584// the contents of this directory directly and instead defer to the cleanup
585// function.
Todd Wang555097f2015-04-21 10:49:06 -0700586func buildPkg(t *T, binDir, pkg string, flags []string) (bool, string, error) {
James Ringbd2216f2015-01-15 20:06:18 -0800587 binFile := filepath.Join(binDir, path.Base(pkg))
Todd Wang555097f2015-04-21 10:49:06 -0700588 vlog.Infof("buildPkg: %v .. %v %v", binDir, pkg, flags)
James Ringbd2216f2015-01-15 20:06:18 -0800589 if _, err := os.Stat(binFile); err != nil {
590 if !os.IsNotExist(err) {
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -0800591 return false, "", err
James Ringbd2216f2015-01-15 20:06:18 -0800592 }
Cosmos Nicolaou47d55d92015-04-08 14:49:12 -0700593 baseName := path.Base(binFile)
594 tmpdir, err := ioutil.TempDir(binDir, baseName+"-")
595 if err != nil {
596 return false, "", err
597 }
598 defer os.RemoveAll(tmpdir)
599 uniqueBinFile := filepath.Join(tmpdir, baseName)
600
Todd Wang555097f2015-04-21 10:49:06 -0700601 buildArgs := []string{"go", "build", "-x", "-o", uniqueBinFile}
602 buildArgs = append(buildArgs, flags...)
603 buildArgs = append(buildArgs, pkg)
604 cmd := exec.Command("v23", buildArgs...)
Cosmos Nicolaou1fcb6a32015-02-17 07:46:02 -0800605 if output, err := cmd.CombinedOutput(); err != nil {
606 vlog.VI(1).Infof("\n%v:\n%v\n", strings.Join(cmd.Args, " "), string(output))
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -0800607 return false, "", err
Jiri Simsae63e07d2014-12-04 11:12:08 -0800608 }
Cosmos Nicolaou47d55d92015-04-08 14:49:12 -0700609 if err := os.Rename(uniqueBinFile, binFile); err != nil {
610 // It seems that on some systems a rename may fail if another rename
611 // is in progress in the same directory. We back a random amount of time
612 // in the hope that a second attempt will succeed.
613 time.Sleep(time.Duration(rand.Int63n(1000)) * time.Millisecond)
614 if err := os.Rename(uniqueBinFile, binFile); err != nil {
615 return false, "", err
616 }
617 }
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -0800618 return false, binFile, nil
Jiri Simsae63e07d2014-12-04 11:12:08 -0800619 }
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -0800620 return true, binFile, nil
Jiri Simsae63e07d2014-12-04 11:12:08 -0800621}
622
Cosmos Nicolaou4a77c192015-02-08 15:29:18 -0800623// RunTest runs a single Vanadium 'v23 style' integration test.
Cosmos Nicolaou01007a02015-02-11 15:38:38 -0800624func RunTest(t *testing.T, fn func(i *T)) {
Cosmos Nicolaou1381f8a2015-03-13 09:40:34 -0700625 if !test.IntegrationTestsEnabled {
Cosmos Nicolaoud21f6b12015-02-07 11:40:03 -0800626 t.Skip()
Jiri Simsae63e07d2014-12-04 11:12:08 -0800627 }
Cosmos Nicolaoud21f6b12015-02-07 11:40:03 -0800628 i := New(t)
Cosmos Nicolaoucc5a4a82015-02-07 23:09:28 -0800629 // defer the Cleanup method so that it will be called even if
630 // t.Fatalf/FailNow etc are called and can print out useful information.
631 defer i.Cleanup()
Cosmos Nicolaoud21f6b12015-02-07 11:40:03 -0800632 fn(i)
Jiri Simsae63e07d2014-12-04 11:12:08 -0800633}
Cosmos Nicolaoud21f6b12015-02-07 11:40:03 -0800634
Asim Shankar59b8b692015-03-30 01:23:36 -0700635// RunRootMT builds and runs a root mount table instance. It populates the
636// envvar.NamespacePrefix variable in the test environment so that all
637// subsequent invocations will access this root mount table.
Cosmos Nicolaou01007a02015-02-11 15:38:38 -0800638func RunRootMT(i *T, args ...string) (*Binary, *Invocation) {
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700639 b := i.BuildV23Pkg("v.io/x/ref/services/mounttable/mounttabled")
Cosmos Nicolaoud3b1cb12015-02-20 00:06:33 -0800640 inv := b.start(1, args...)
Cosmos Nicolaou01007a02015-02-11 15:38:38 -0800641 name := inv.ExpectVar("NAME")
Asim Shankar59b8b692015-03-30 01:23:36 -0700642 inv.Environment().SetVar(envvar.NamespacePrefix, name)
Cosmos Nicolaou82d00d82015-02-10 21:31:00 -0800643 vlog.Infof("Running root mount table: %q", name)
Cosmos Nicolaou01007a02015-02-11 15:38:38 -0800644 return b, inv
Cosmos Nicolaoud21f6b12015-02-07 11:40:03 -0800645}
646
Cosmos Nicolaou93dd88b2015-02-19 15:10:53 -0800647// UseSharedBinDir ensures that a shared directory is used for binaries
Cosmos Nicolaou82d00d82015-02-10 21:31:00 -0800648// across multiple instances of the test environment. This is achieved
649// by setting the V23_BIN_DIR environment variable if it is not already
650// set in the test processes environment (as will typically be the case when
651// these tests are run from the v23 tool). It is intended to be called
652// from TestMain.
653func UseSharedBinDir() func() {
654 if v23BinDir := os.Getenv("V23_BIN_DIR"); len(v23BinDir) == 0 {
655 v23BinDir, err := ioutil.TempDir("", "bin-")
656 if err == nil {
657 vlog.Infof("Setting V23_BIN_DIR to %q", v23BinDir)
658 os.Setenv("V23_BIN_DIR", v23BinDir)
659 return func() { os.RemoveAll(v23BinDir) }
660 }
661 } else {
662 vlog.Infof("Using V23_BIN_DIR %q", v23BinDir)
663 }
664 return func() {}
665}