blob: 165749d26a0b2ca2a64c6b154bd101cdf5993ef4 [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 Nicolaou1381f8a2015-03-13 09:40:34 -070022 "v.io/v23"
Cosmos Nicolaoue3b19322015-06-18 16:05:08 -070023 "v.io/v23/context"
Cosmos Nicolaou1381f8a2015-03-13 09:40:34 -070024 "v.io/v23/security"
Cosmos Nicolaoue3b19322015-06-18 16:05:08 -070025
Todd Wang8123b5e2015-05-14 18:44:43 -070026 "v.io/x/ref"
Todd Wang88509682015-04-10 10:28:24 -070027 "v.io/x/ref/services/agent/agentlib"
Cosmos Nicolaou1381f8a2015-03-13 09:40:34 -070028 "v.io/x/ref/test"
29 "v.io/x/ref/test/modules"
Asim Shankar4a698282015-03-21 21:59:18 -070030 "v.io/x/ref/test/testutil"
Jiri Simsae63e07d2014-12-04 11:12:08 -080031)
32
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -080033// TB is an exact mirror of testing.TB. It is provided to allow for testing
34// of this package using a mock implementation. As per testing.TB, it is not
35// intended to be implemented outside of this package.
Cosmos Nicolaoud3b1cb12015-02-20 00:06:33 -080036type TB interface {
37 Error(args ...interface{})
38 Errorf(format string, args ...interface{})
39 Fail()
40 FailNow()
41 Failed() bool
42 Fatal(args ...interface{})
43 Fatalf(format string, args ...interface{})
44 Log(args ...interface{})
45 Logf(format string, args ...interface{})
46 Skip(args ...interface{})
47 SkipNow()
48 Skipf(format string, args ...interface{})
49 Skipped() bool
50}
James Ring855dfd42015-01-23 12:30:58 -080051
Cosmos Nicolaoucc5a4a82015-02-07 23:09:28 -080052// T represents an integration test environment.
Cosmos Nicolaou01007a02015-02-11 15:38:38 -080053type T struct {
Cosmos Nicolaoud3b1cb12015-02-20 00:06:33 -080054 // The embedded TB
55 TB
James Ring65795102014-12-17 13:01:03 -080056
Cosmos Nicolaoue3b19322015-06-18 16:05:08 -070057 ctx *context.T
58
Suharsh Sivakumar19fbf992015-01-23 11:02:27 -080059 // The function to shutdown the context used to create the environment.
Jiri Simsa6ac95222015-02-23 16:11:49 -080060 shutdown v23.Shutdown
Suharsh Sivakumar19fbf992015-01-23 11:02:27 -080061
James Ring65795102014-12-17 13:01:03 -080062 // The shell to use to start commands.
63 shell *modules.Shell
64
65 // The environment's root security principal.
66 principal security.Principal
67
Cosmos Nicolaou01007a02015-02-11 15:38:38 -080068 // Maps path to Binary.
69 builtBinaries map[string]*Binary
James Ring65795102014-12-17 13:01:03 -080070
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -080071 tempFiles []*os.File
72 tempDirs []string
73 binDir, cachedBinDir string
74 dirStack []string
Cosmos Nicolaoud21f6b12015-02-07 11:40:03 -080075
Cosmos Nicolaou01007a02015-02-11 15:38:38 -080076 invocations []*Invocation
James Ring65795102014-12-17 13:01:03 -080077}
78
Cosmos Nicolaoucc5a4a82015-02-07 23:09:28 -080079var errNotShutdown = errors.New("has not been shutdown")
80
Cosmos Nicolaou93dd88b2015-02-19 15:10:53 -080081// Caller returns a string of the form <filename>:<lineno> for the
82// caller specified by skip, where skip is as per runtime.Caller.
83func Caller(skip int) string {
84 _, file, line, _ := runtime.Caller(skip + 1)
Cosmos Nicolaou1fcb6a32015-02-17 07:46:02 -080085 return fmt.Sprintf("%s:%d", filepath.Base(file), line)
James Ring65795102014-12-17 13:01:03 -080086}
87
Matt Rosencrantz8df1ac32015-04-27 20:21:28 -070088// SkipInRegressionBefore skips the test if this is being run in a regression
89// test and some of the binaries are older than the specified date.
90// This is useful when we're breaking compatibility and we know it.
91// The parameter is a string formatted date YYYY-MM-DD.
92func (t *T) SkipInRegressionBefore(dateStr string) {
93 testStr := os.Getenv("V23_REGTEST_DATE")
94 if testStr == "" {
95 return
96 }
97 testDate, err := time.Parse("2006-01-02", testStr)
98 if err != nil {
99 t.Fatalf("%s: could not parse V23_REGTEST_DATE=%q: %v", Caller(1), testStr, err)
100 }
101 date, err := time.Parse("2006-01-02", dateStr)
102 if err != nil {
103 t.Fatalf("%s: could not parse date %q: %v", Caller(1), dateStr, err)
104 }
105 if testDate.Before(date) {
106 t.Skipf("Skipping regression test with binary from %s", testStr)
107 }
108}
109
Cosmos Nicolaoud3b1cb12015-02-20 00:06:33 -0800110// Run constructs a Binary for path and invokes Run on it.
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700111func (t *T) Run(path string, args ...string) string {
112 return t.BinaryFromPath(path).run(args...)
113}
114
115// Run constructs a Binary for path and invokes Run on it using
116// the specified StartOpts
117func (t *T) RunWithOpts(opts modules.StartOpts, path string, args ...string) string {
118 b := t.BinaryFromPath(path)
119 return b.WithStartOpts(opts).run(args...)
Cosmos Nicolaoud3b1cb12015-02-20 00:06:33 -0800120}
121
122// WaitFunc is the type of the functions to be used in conjunction
123// with WaitFor and WaitForAsync. It should return a value or an error
124// when it wants those functions to terminate, returning a nil value
125// and nil error will result in it being called again after the specified
126// delay time specified in the calls to WaitFor and WaitForAsync.
127type WaitFunc func() (interface{}, error)
128
129// WaitFor calls fn at least once with the specified delay value
130// between iterations until the first of the following is encountered:
131// 1. fn returns a non-nil value.
132// 2. fn returns an error value
133// 3. fn is executed at least once and the specified timeout is exceeded.
134//
135// WaitFor returns the non-nil value for the first case and calls e.Fatalf for
136// the other two cases.
137// WaitFor will always run fn at least once to completion and hence it will
138// hang if that first iteration of fn hangs. If this behaviour is not
139// appropriate, then WaitForAsync should be used.
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700140func (t *T) WaitFor(fn WaitFunc, delay, timeout time.Duration) interface{} {
Cosmos Nicolaoud3b1cb12015-02-20 00:06:33 -0800141 deadline := time.Now().Add(timeout)
142 for {
143 val, err := fn()
144 if val != nil {
145 return val
146 }
147 if err != nil {
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700148 t.Fatalf("%s: the WaitFunc returned an error: %v", Caller(1), err)
Cosmos Nicolaoud3b1cb12015-02-20 00:06:33 -0800149 }
150 if time.Now().After(deadline) {
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700151 t.Fatalf("%s: timed out after %s", Caller(1), timeout)
Cosmos Nicolaoud3b1cb12015-02-20 00:06:33 -0800152 }
153 time.Sleep(delay)
154 }
155}
156
157// WaitForAsync is like WaitFor except that it calls fn in a goroutine
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -0800158// and can timeout during the execution of fn.
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700159func (t *T) WaitForAsync(fn WaitFunc, delay, timeout time.Duration) interface{} {
Cosmos Nicolaoud3b1cb12015-02-20 00:06:33 -0800160 resultCh := make(chan interface{})
161 errCh := make(chan interface{})
162 go func() {
163 for {
164 val, err := fn()
165 if val != nil {
166 resultCh <- val
167 return
168 }
169 if err != nil {
170 errCh <- err
171 return
172 }
173 time.Sleep(delay)
174 }
175 }()
176 select {
177 case err := <-errCh:
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700178 t.Fatalf("%s: the WaitFunc returned error: %v", Caller(1), err)
Cosmos Nicolaoud3b1cb12015-02-20 00:06:33 -0800179 case result := <-resultCh:
180 return result
181 case <-time.After(timeout):
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700182 t.Fatalf("%s: timed out after %s", Caller(1), timeout)
Cosmos Nicolaoud3b1cb12015-02-20 00:06:33 -0800183 }
184 return nil
185}
186
187// Pushd pushes the current working directory to the stack of
188// directories, returning it as its result, and changes the working
189// directory to dir.
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700190func (t *T) Pushd(dir string) string {
Cosmos Nicolaoud3b1cb12015-02-20 00:06:33 -0800191 cwd, err := os.Getwd()
192 if err != nil {
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700193 t.Fatalf("%s: Getwd failed: %s", Caller(1), err)
Cosmos Nicolaoud3b1cb12015-02-20 00:06:33 -0800194 }
195 if err := os.Chdir(dir); err != nil {
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700196 t.Fatalf("%s: Chdir failed: %s", Caller(1), err)
Cosmos Nicolaoud3b1cb12015-02-20 00:06:33 -0800197 }
Cosmos Nicolaoue3b19322015-06-18 16:05:08 -0700198 t.ctx.VI(1).Infof("Pushd: %s -> %s", cwd, dir)
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700199 t.dirStack = append(t.dirStack, cwd)
Cosmos Nicolaoud3b1cb12015-02-20 00:06:33 -0800200 return cwd
201}
202
203// Popd pops the most recent entry from the directory stack and changes
204// the working directory to that directory. It returns the new working
205// directory as its result.
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700206func (t *T) Popd() string {
207 if len(t.dirStack) == 0 {
208 t.Fatalf("%s: directory stack empty", Caller(1))
Cosmos Nicolaoud3b1cb12015-02-20 00:06:33 -0800209 }
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700210 dir := t.dirStack[len(t.dirStack)-1]
211 t.dirStack = t.dirStack[:len(t.dirStack)-1]
Cosmos Nicolaoud3b1cb12015-02-20 00:06:33 -0800212 if err := os.Chdir(dir); err != nil {
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700213 t.Fatalf("%s: Chdir failed: %s", Caller(1), err)
Cosmos Nicolaoud3b1cb12015-02-20 00:06:33 -0800214 }
Cosmos Nicolaoue3b19322015-06-18 16:05:08 -0700215 t.ctx.VI(1).Infof("Popd: -> %s", dir)
Cosmos Nicolaoud3b1cb12015-02-20 00:06:33 -0800216 return dir
217}
218
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -0800219// Caller returns a string of the form <filename>:<lineno> for the
220// caller specified by skip, where skip is as per runtime.Caller.
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700221func (t *T) Caller(skip int) string {
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -0800222 return Caller(skip + 1)
223}
224
Cosmos Nicolaou01007a02015-02-11 15:38:38 -0800225// Principal returns the security principal of this environment.
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700226func (t *T) Principal() security.Principal {
227 return t.principal
James Ring65795102014-12-17 13:01:03 -0800228}
229
Cosmos Nicolaou01007a02015-02-11 15:38:38 -0800230// Cleanup cleans up the environment, deletes all its artifacts and
231// kills all subprocesses. It will kill subprocesses in LIFO order.
232// Cleanup checks to see if the test has failed and logs information
233// as to the state of the processes it was asked to invoke up to that
234// point and optionally, if the --v23.tests.shell-on-fail flag is set
235// then it will run a debug shell before cleaning up its state.
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700236func (t *T) Cleanup() {
237 if t.Failed() {
Cosmos Nicolaou1381f8a2015-03-13 09:40:34 -0700238 if test.IntegrationTestsDebugShellOnError {
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700239 t.DebugSystemShell()
Cosmos Nicolaoucc5a4a82015-02-07 23:09:28 -0800240 }
241 // Print out a summary of the invocations and their status.
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700242 for i, inv := range t.invocations {
Cosmos Nicolaoucc5a4a82015-02-07 23:09:28 -0800243 if inv.hasShutdown && inv.Exists() {
244 m := fmt.Sprintf("%d: %s has been shutdown but still exists: %v", i, inv.path, inv.shutdownErr)
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700245 t.Log(m)
Cosmos Nicolaoue3b19322015-06-18 16:05:08 -0700246 t.ctx.VI(1).Info(m)
247 t.ctx.VI(2).Infof("%d: %s %v", i, inv.path, inv.args)
Cosmos Nicolaoucc5a4a82015-02-07 23:09:28 -0800248 continue
249 }
Cosmos Nicolaou93dd88b2015-02-19 15:10:53 -0800250 if inv.shutdownErr != nil {
251 m := fmt.Sprintf("%d: %s: shutdown status: %v", i, inv.path, inv.shutdownErr)
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700252 t.Log(m)
Cosmos Nicolaoue3b19322015-06-18 16:05:08 -0700253 t.ctx.VI(1).Info(m)
254 t.ctx.VI(2).Infof("%d: %s %v", i, inv.path, inv.args)
Cosmos Nicolaou93dd88b2015-02-19 15:10:53 -0800255 }
Cosmos Nicolaoucc5a4a82015-02-07 23:09:28 -0800256 }
257 }
258
Cosmos Nicolaoue3b19322015-06-18 16:05:08 -0700259 t.ctx.VI(1).Infof("V23Test.Cleanup")
Cosmos Nicolaou82d00d82015-02-10 21:31:00 -0800260 // Shut down all processes in LIFO order before attempting to delete any
Cosmos Nicolaoud21f6b12015-02-07 11:40:03 -0800261 // files/directories to avoid potential 'file system busy' problems
262 // on non-unix systems.
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700263 for i := len(t.invocations); i > 0; i-- {
264 inv := t.invocations[i-1]
Cosmos Nicolaoucc5a4a82015-02-07 23:09:28 -0800265 if inv.hasShutdown {
Cosmos Nicolaoue3b19322015-06-18 16:05:08 -0700266 t.ctx.VI(1).Infof("V23Test.Cleanup: %q has been shutdown", inv.Path())
Cosmos Nicolaoucc5a4a82015-02-07 23:09:28 -0800267 continue
Cosmos Nicolaoud21f6b12015-02-07 11:40:03 -0800268 }
Cosmos Nicolaoue3b19322015-06-18 16:05:08 -0700269 t.ctx.VI(1).Infof("V23Test.Cleanup: Kill: %q", inv.Path())
Cosmos Nicolaoud21f6b12015-02-07 11:40:03 -0800270 err := inv.Kill(syscall.SIGTERM)
271 inv.Wait(os.Stdout, os.Stderr)
Cosmos Nicolaoue3b19322015-06-18 16:05:08 -0700272 t.ctx.VI(1).Infof("V23Test.Cleanup: Killed: %q: %v", inv.Path(), err)
Cosmos Nicolaoud21f6b12015-02-07 11:40:03 -0800273 }
Cosmos Nicolaoue3b19322015-06-18 16:05:08 -0700274 t.ctx.VI(1).Infof("V23Test.Cleanup: all invocations taken care of.")
Cosmos Nicolaoud21f6b12015-02-07 11:40:03 -0800275
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700276 if err := t.shell.Cleanup(os.Stdout, os.Stderr); err != nil {
277 t.Fatalf("WARNING: could not clean up shell (%v)", err)
Cosmos Nicolaoud21f6b12015-02-07 11:40:03 -0800278 }
279
Cosmos Nicolaoue3b19322015-06-18 16:05:08 -0700280 t.ctx.VI(1).Infof("V23Test.Cleanup: cleaning up binaries & files")
Cosmos Nicolaoucc5a4a82015-02-07 23:09:28 -0800281
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700282 for _, tempFile := range t.tempFiles {
Cosmos Nicolaoue3b19322015-06-18 16:05:08 -0700283 t.ctx.VI(1).Infof("V23Test.Cleanup: cleaning up %s", tempFile.Name())
James Ring65795102014-12-17 13:01:03 -0800284 if err := tempFile.Close(); err != nil {
Cosmos Nicolaoue3b19322015-06-18 16:05:08 -0700285 t.ctx.Errorf("WARNING: Close(%q) failed: %v", tempFile.Name(), err)
James Ring65795102014-12-17 13:01:03 -0800286 }
James Ring23b54862015-01-07 11:54:48 -0800287 if err := os.RemoveAll(tempFile.Name()); err != nil {
Cosmos Nicolaoue3b19322015-06-18 16:05:08 -0700288 t.ctx.Errorf("WARNING: RemoveAll(%q) failed: %v", tempFile.Name(), err)
James Ring23b54862015-01-07 11:54:48 -0800289 }
290 }
291
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700292 for _, tempDir := range t.tempDirs {
Cosmos Nicolaoue3b19322015-06-18 16:05:08 -0700293 t.ctx.VI(1).Infof("V23Test.Cleanup: cleaning up %s", tempDir)
294 t.ctx.Infof("V23Test.Cleanup: cleaning up %s", tempDir)
James Ring23b54862015-01-07 11:54:48 -0800295 if err := os.RemoveAll(tempDir); err != nil {
Cosmos Nicolaoue3b19322015-06-18 16:05:08 -0700296 t.ctx.Errorf("WARNING: RemoveAll(%q) failed: %v", tempDir, err)
James Ring65795102014-12-17 13:01:03 -0800297 }
298 }
299
Cosmos Nicolaoucc5a4a82015-02-07 23:09:28 -0800300 // shutdown the runtime
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700301 t.shutdown()
James Ring65795102014-12-17 13:01:03 -0800302}
303
Cosmos Nicolaou01007a02015-02-11 15:38:38 -0800304// GetVar returns the variable associated with the specified key
305// and an indication of whether it is defined or not.
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700306func (t *T) GetVar(key string) (string, bool) {
307 return t.shell.GetVar(key)
Cosmos Nicolaoud21f6b12015-02-07 11:40:03 -0800308}
309
Cosmos Nicolaou01007a02015-02-11 15:38:38 -0800310// SetVar sets the value to be associated with key.
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700311func (t *T) SetVar(key, value string) {
312 t.shell.SetVar(key, value)
Cosmos Nicolaoud21f6b12015-02-07 11:40:03 -0800313}
314
Cosmos Nicolaou01007a02015-02-11 15:38:38 -0800315// ClearVar removes the speficied variable from the Shell's environment
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700316func (t *T) ClearVar(key string) {
317 t.shell.ClearVar(key)
Cosmos Nicolaoud21f6b12015-02-07 11:40:03 -0800318}
319
Cosmos Nicolaou01007a02015-02-11 15:38:38 -0800320func writeStringOrDie(t *T, f *os.File, s string) {
James Ring0bad5672015-01-05 09:51:36 -0800321 if _, err := f.WriteString(s); err != nil {
James Ring0d525c22014-12-30 12:03:31 -0800322 t.Fatalf("Write() failed: %v", err)
323 }
324}
325
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700326// DebugSystemShell drops the user into a debug system shell (e.g. bash)
327// with any environment variables specified in env... (in VAR=VAL format)
328// available to it.
329// If there is no controlling TTY, DebugSystemShell will emit a warning message
330// and take no futher action. The DebugSystemShell also sets some environment
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -0800331// variables that relate to the running test:
332// - V23_TMP_DIR<#> contains the name of each temp directory created.
333// - V23_BIN_DIR contains the name of the directory containing binaries.
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700334func (t *T) DebugSystemShell(env ...string) {
James Ring65795102014-12-17 13:01:03 -0800335 // Get the current working directory.
336 cwd, err := os.Getwd()
337 if err != nil {
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700338 t.Fatalf("Getwd() failed: %v", err)
James Ring65795102014-12-17 13:01:03 -0800339 }
340
341 // Transfer stdin, stdout, and stderr to the new process
342 // and also set target directory for the shell to start in.
343 dev := "/dev/tty"
James Ring0d525c22014-12-30 12:03:31 -0800344 fd, err := syscall.Open(dev, syscall.O_RDWR, 0)
James Ring65795102014-12-17 13:01:03 -0800345 if err != nil {
Cosmos Nicolaoue3b19322015-06-18 16:05:08 -0700346 t.ctx.Errorf("WARNING: Open(%v) failed, was asked to create a debug shell but cannot: %v", dev, err)
James Ring65795102014-12-17 13:01:03 -0800347 return
348 }
Cosmos Nicolaou82d00d82015-02-10 21:31:00 -0800349
Ryan Brown61d69382015-02-25 11:13:39 -0800350 var agentFile *os.File
Asim Shankar34fb2492015-03-12 10:25:46 -0700351 if creds, err := t.shell.NewChildCredentials("debug"); err == nil {
Ryan Brown61d69382015-02-25 11:13:39 -0800352 if agentFile, err = creds.File(); err != nil {
Cosmos Nicolaoue3b19322015-06-18 16:05:08 -0700353 t.ctx.Errorf("WARNING: failed to obtain credentials for the debug shell: %v", err)
Ryan Brown61d69382015-02-25 11:13:39 -0800354 }
355 } else {
Cosmos Nicolaoue3b19322015-06-18 16:05:08 -0700356 t.ctx.Errorf("WARNING: failed to obtain credentials for the debug shell: %v", err)
Cosmos Nicolaou82d00d82015-02-10 21:31:00 -0800357 }
358
James Ring0d525c22014-12-30 12:03:31 -0800359 file := os.NewFile(uintptr(fd), dev)
James Ring65795102014-12-17 13:01:03 -0800360 attr := os.ProcAttr{
James Ring0d525c22014-12-30 12:03:31 -0800361 Files: []*os.File{file, file, file},
James Ring65795102014-12-17 13:01:03 -0800362 Dir: cwd,
363 }
Cosmos Nicolaou93dd88b2015-02-19 15:10:53 -0800364 // Set up agent for Child.
Cosmos Nicolaou82d00d82015-02-10 21:31:00 -0800365 attr.Files = append(attr.Files, agentFile)
Todd Wang8123b5e2015-05-14 18:44:43 -0700366 attr.Env = append(attr.Env, fmt.Sprintf("%s=%v", ref.EnvAgentEndpoint, agentlib.AgentEndpoint(len(attr.Files)-1)))
James Ring65795102014-12-17 13:01:03 -0800367
Cosmos Nicolaoud21f6b12015-02-07 11:40:03 -0800368 // Set up environment for Child.
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700369 for _, v := range t.shell.Env() {
Cosmos Nicolaou1fcb6a32015-02-17 07:46:02 -0800370 attr.Env = append(attr.Env, v)
Cosmos Nicolaoud21f6b12015-02-07 11:40:03 -0800371 }
Cosmos Nicolaoud21f6b12015-02-07 11:40:03 -0800372
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700373 for i, td := range t.tempDirs {
Cosmos Nicolaou93dd88b2015-02-19 15:10:53 -0800374 attr.Env = append(attr.Env, fmt.Sprintf("V23_TMP_DIR%d=%s", i, td))
375 }
376
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700377 if len(t.cachedBinDir) > 0 {
378 attr.Env = append(attr.Env, "V23_BIN_DIR="+t.BinDir())
Cosmos Nicolaou93dd88b2015-02-19 15:10:53 -0800379 }
380 attr.Env = append(attr.Env, env...)
381
James Ring65795102014-12-17 13:01:03 -0800382 // Start up a new shell.
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700383 writeStringOrDie(t, file, ">> Starting a new interactive shell\n")
384 writeStringOrDie(t, file, "Hit CTRL-D to resume the test\n")
385 if len(t.builtBinaries) > 0 {
386 writeStringOrDie(t, file, "Built binaries:\n")
387 for _, value := range t.builtBinaries {
388 writeStringOrDie(t, file, "\t"+value.Path()+"\n")
James Ring65795102014-12-17 13:01:03 -0800389 }
390 }
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700391 if len(t.cachedBinDir) > 0 {
392 writeStringOrDie(t, file, fmt.Sprintf("Binaries are cached in %q\n", t.cachedBinDir))
Cosmos Nicolaoua866f262015-02-10 14:56:06 -0800393 } else {
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700394 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 -0800395 }
James Ring65795102014-12-17 13:01:03 -0800396
397 shellPath := "/bin/sh"
James Ring2fdeb452015-02-02 21:18:22 -0800398 if shellPathFromEnv := os.Getenv("SHELL"); shellPathFromEnv != "" {
James Ringaa701492015-02-03 11:43:51 -0800399 shellPath = shellPathFromEnv
James Ring2fdeb452015-02-02 21:18:22 -0800400 }
James Ring65795102014-12-17 13:01:03 -0800401 proc, err := os.StartProcess(shellPath, []string{}, &attr)
402 if err != nil {
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700403 t.Fatalf("StartProcess(%q) failed: %v", shellPath, err)
James Ring65795102014-12-17 13:01:03 -0800404 }
405
406 // Wait until user exits the shell
407 state, err := proc.Wait()
408 if err != nil {
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700409 t.Fatalf("Wait(%v) failed: %v", shellPath, err)
James Ring65795102014-12-17 13:01:03 -0800410 }
411
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700412 writeStringOrDie(t, file, fmt.Sprintf("<< Exited shell: %s\n", state.String()))
James Ring65795102014-12-17 13:01:03 -0800413}
414
Asim Shankar6cc759d2015-03-14 03:31:44 -0700415// BinaryFromPath returns a new Binary that, when started, will execute the
416// executable or script at the given path. The binary is assumed to not
417// implement the exec protocol defined in v.io/x/ref/lib/exec.
Cosmos Nicolaou01007a02015-02-11 15:38:38 -0800418//
419// E.g. env.BinaryFromPath("/bin/bash").Start("-c", "echo hello world").Output() -> "hello world"
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700420func (t *T) BinaryFromPath(path string) *Binary {
Cosmos Nicolaou01007a02015-02-11 15:38:38 -0800421 return &Binary{
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700422 env: t,
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -0800423 envVars: nil,
424 path: path,
Todd Wang95873902015-05-22 14:21:30 -0700425 opts: t.shell.DefaultStartOpts().NoExecProgram(),
James Ring90d21dc2015-02-02 18:10:38 -0800426 }
427}
428
Todd Wang555097f2015-04-21 10:49:06 -0700429// BuildGoPkg expects a Go package path that identifies a "main" package, and
430// any build flags to pass to "go build", and returns a Binary representing the
431// newly built binary.
432//
433// The resulting binary is assumed to not use the exec protocol defined in
434// v.io/x/ref/lib/exec and in particular will not have access to the security
435// agent or any other shared file descriptors. Environment variables and
436// command line arguments are the only means of communicating with the
437// invocations of this binary.
438//
Cosmos Nicolaou52e9a222015-03-16 21:57:16 -0700439// Use this for non-Vanadium command-line tools and servers.
Todd Wang555097f2015-04-21 10:49:06 -0700440func (t *T) BuildGoPkg(pkg string, flags ...string) *Binary {
441 return t.buildPkg(pkg, flags...)
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700442}
443
Todd Wang555097f2015-04-21 10:49:06 -0700444// BuildV23 is like BuildGoPkg, but instead assumes that the resulting binary is
445// a Vanadium application and does implement the exec protocol defined in
446// v.io/x/ref/lib/exec. The invocations of this binary will have access to the
447// security agent configured for the parent process, to shared file descriptors,
448// the config shared by the exec package.
449//
Cosmos Nicolaou52e9a222015-03-16 21:57:16 -0700450// Use this for Vanadium servers. Note that some vanadium client only binaries,
Todd Wang555097f2015-04-21 10:49:06 -0700451// that do not call v23.Init and hence do not implement the exec protocol cannot
452// be used via BuildV23Pkg.
453func (t *T) BuildV23Pkg(pkg string, flags ...string) *Binary {
454 b := t.buildPkg(pkg, flags...)
Todd Wang95873902015-05-22 14:21:30 -0700455 b.opts = t.shell.DefaultStartOpts().ExternalProgram()
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700456 return b
457}
458
Todd Wang555097f2015-04-21 10:49:06 -0700459func (t *T) buildPkg(pkg string, flags ...string) *Binary {
Cosmos Nicolaou82d00d82015-02-10 21:31:00 -0800460 then := time.Now()
Cosmos Nicolaou93dd88b2015-02-19 15:10:53 -0800461 loc := Caller(1)
Todd Wang555097f2015-04-21 10:49:06 -0700462 cached, built_path, err := buildPkg(t, t.BinDir(), pkg, flags)
James Ring65795102014-12-17 13:01:03 -0800463 if err != nil {
Todd Wang555097f2015-04-21 10:49:06 -0700464 t.Fatalf("%s: buildPkg(%s, %v) failed: %v", loc, pkg, flags, err)
James Ring65795102014-12-17 13:01:03 -0800465 return nil
466 }
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -0800467 if _, err := os.Stat(built_path); err != nil {
Todd Wang555097f2015-04-21 10:49:06 -0700468 t.Fatalf("%s: buildPkg(%s, %v) failed to stat %q", loc, pkg, flags, built_path)
Cosmos Nicolaou82d00d82015-02-10 21:31:00 -0800469 }
470 taken := time.Now().Sub(then)
471 if cached {
Cosmos Nicolaoue3b19322015-06-18 16:05:08 -0700472 t.ctx.Infof("%s: using %s, from %s in %s.", loc, pkg, built_path, taken)
Cosmos Nicolaoua866f262015-02-10 14:56:06 -0800473 } else {
Cosmos Nicolaoue3b19322015-06-18 16:05:08 -0700474 t.ctx.Infof("%s: built %s, written to %s in %s.", loc, pkg, built_path, taken)
Cosmos Nicolaoua866f262015-02-10 14:56:06 -0800475 }
Cosmos Nicolaou01007a02015-02-11 15:38:38 -0800476 binary := &Binary{
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700477 env: t,
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -0800478 envVars: nil,
479 path: built_path,
Todd Wang95873902015-05-22 14:21:30 -0700480 opts: t.shell.DefaultStartOpts().NoExecProgram(),
James Ring65795102014-12-17 13:01:03 -0800481 }
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700482 t.builtBinaries[pkg] = binary
James Ring65795102014-12-17 13:01:03 -0800483 return binary
484}
485
Cosmos Nicolaou93dd88b2015-02-19 15:10:53 -0800486// NewTempFile creates a temporary file. Temporary files will be deleted
Cosmos Nicolaou01007a02015-02-11 15:38:38 -0800487// by Cleanup.
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700488func (t *T) NewTempFile() *os.File {
Cosmos Nicolaou93dd88b2015-02-19 15:10:53 -0800489 loc := Caller(1)
James Ring65795102014-12-17 13:01:03 -0800490 f, err := ioutil.TempFile("", "")
491 if err != nil {
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700492 t.Fatalf("%s: TempFile() failed: %v", loc, err)
James Ring65795102014-12-17 13:01:03 -0800493 }
Cosmos Nicolaoue3b19322015-06-18 16:05:08 -0700494 t.ctx.Infof("%s: created temporary file at %s", loc, f.Name())
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700495 t.tempFiles = append(t.tempFiles, f)
James Ring65795102014-12-17 13:01:03 -0800496 return f
497}
498
Cosmos Nicolaou93dd88b2015-02-19 15:10:53 -0800499// NewTempDir creates a temporary directory. Temporary directories and
Cosmos Nicolaou01007a02015-02-11 15:38:38 -0800500// their contents will be deleted by Cleanup.
Robert Kroeger02714b72015-04-14 18:02:38 -0700501func (t *T) NewTempDir(dir string) string {
Cosmos Nicolaou93dd88b2015-02-19 15:10:53 -0800502 loc := Caller(1)
Robert Kroeger02714b72015-04-14 18:02:38 -0700503 f, err := ioutil.TempDir(dir, "")
James Ring23b54862015-01-07 11:54:48 -0800504 if err != nil {
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700505 t.Fatalf("%s: TempDir() failed: %v", loc, err)
James Ring23b54862015-01-07 11:54:48 -0800506 }
Cosmos Nicolaoue3b19322015-06-18 16:05:08 -0700507 t.ctx.Infof("%s: created temporary directory at %s", loc, f)
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700508 t.tempDirs = append(t.tempDirs, f)
James Ring23b54862015-01-07 11:54:48 -0800509 return f
510}
511
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700512func (t *T) appendInvocation(inv *Invocation) {
513 t.invocations = append(t.invocations, inv)
Cosmos Nicolaoud21f6b12015-02-07 11:40:03 -0800514}
515
James Ringa71e49a2015-01-16 14:08:02 -0800516// Creates a new local testing environment. A local testing environment has a
Cosmos Nicolaoud21f6b12015-02-07 11:40:03 -0800517// a security principle available via Principal().
James Ringa71e49a2015-01-16 14:08:02 -0800518//
519// You should clean up the returned environment using the env.Cleanup() method.
520// A typical end-to-end test will begin like:
521//
522// func TestFoo(t *testing.T) {
Cosmos Nicolaou01007a02015-02-11 15:38:38 -0800523// env := integration.NewT(t)
James Ringa71e49a2015-01-16 14:08:02 -0800524// defer env.Cleanup()
525//
526// ...
527// }
Cosmos Nicolaoud3b1cb12015-02-20 00:06:33 -0800528func New(t TB) *T {
Jiri Simsa6ac95222015-02-23 16:11:49 -0800529 ctx, shutdown := v23.Init()
Suharsh Sivakumar19fbf992015-01-23 11:02:27 -0800530
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 Nicolaoue3b19322015-06-18 16:05:08 -0700537 ctx.Infof("created root principal: %v", principal)
538
Cosmos Nicolaou9e909842015-03-17 11:58:59 -0700539 shell, err := modules.NewShell(ctx, principal, testing.Verbose(), t)
James Ring65795102014-12-17 13:01:03 -0800540 if err != nil {
541 t.Fatalf("NewShell() failed: %v", err)
542 }
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700543 opts := modules.DefaultStartOpts()
544 opts.StartTimeout = time.Minute
545 opts.ShutdownTimeout = 5 * time.Minute
546 shell.SetDefaultStartOpts(opts)
James Ring65795102014-12-17 13:01:03 -0800547
Cosmos Nicolaou82d00d82015-02-10 21:31:00 -0800548 // The V23_BIN_DIR environment variable can be
549 // used to identify a directory that multiple integration
550 // tests can use to share binaries. Whoever sets this
551 // environment variable is responsible for cleaning up the
552 // directory it points to.
Cosmos Nicolaoua866f262015-02-10 14:56:06 -0800553 cachedBinDir := os.Getenv("V23_BIN_DIR")
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -0800554 e := &T{
Cosmos Nicolaoud3b1cb12015-02-20 00:06:33 -0800555 TB: t,
Cosmos Nicolaoue3b19322015-06-18 16:05:08 -0700556 ctx: ctx,
James Ring65795102014-12-17 13:01:03 -0800557 principal: principal,
Cosmos Nicolaou01007a02015-02-11 15:38:38 -0800558 builtBinaries: make(map[string]*Binary),
James Ring65795102014-12-17 13:01:03 -0800559 shell: shell,
James Ring65795102014-12-17 13:01:03 -0800560 tempFiles: []*os.File{},
James Ring23b54862015-01-07 11:54:48 -0800561 tempDirs: []string{},
Cosmos Nicolaoua866f262015-02-10 14:56:06 -0800562 cachedBinDir: cachedBinDir,
Suharsh Sivakumar19fbf992015-01-23 11:02:27 -0800563 shutdown: shutdown,
James Ring65795102014-12-17 13:01:03 -0800564 }
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -0800565 if len(e.cachedBinDir) == 0 {
Robert Kroeger02714b72015-04-14 18:02:38 -0700566 e.binDir = e.NewTempDir("")
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -0800567 }
568 return e
569}
570
Cosmos Nicolaou52e9a222015-03-16 21:57:16 -0700571// Shell returns the underlying modules.Shell used by v23tests.
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700572func (t *T) Shell() *modules.Shell {
573 return t.shell
574}
575
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -0800576// BinDir returns the directory that binarie files are stored in.
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700577func (t *T) BinDir() string {
578 if len(t.cachedBinDir) > 0 {
579 return t.cachedBinDir
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -0800580 }
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700581 return t.binDir
James Ring65795102014-12-17 13:01:03 -0800582}
583
Todd Wang555097f2015-04-21 10:49:06 -0700584// buildPkg returns a path to a directory that contains the built binary for
James Ringbd2216f2015-01-15 20:06:18 -0800585// the given packages and a function that should be invoked to clean up the
586// build artifacts. Note that the clients of this function should not modify
587// the contents of this directory directly and instead defer to the cleanup
588// function.
Todd Wang555097f2015-04-21 10:49:06 -0700589func buildPkg(t *T, binDir, pkg string, flags []string) (bool, string, error) {
James Ringbd2216f2015-01-15 20:06:18 -0800590 binFile := filepath.Join(binDir, path.Base(pkg))
Cosmos Nicolaoue3b19322015-06-18 16:05:08 -0700591 t.ctx.Infof("buildPkg: %v .. %v %v", binDir, pkg, flags)
James Ringbd2216f2015-01-15 20:06:18 -0800592 if _, err := os.Stat(binFile); err != nil {
593 if !os.IsNotExist(err) {
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -0800594 return false, "", err
James Ringbd2216f2015-01-15 20:06:18 -0800595 }
Cosmos Nicolaou47d55d92015-04-08 14:49:12 -0700596 baseName := path.Base(binFile)
597 tmpdir, err := ioutil.TempDir(binDir, baseName+"-")
598 if err != nil {
599 return false, "", err
600 }
601 defer os.RemoveAll(tmpdir)
602 uniqueBinFile := filepath.Join(tmpdir, baseName)
603
Todd Wang555097f2015-04-21 10:49:06 -0700604 buildArgs := []string{"go", "build", "-x", "-o", uniqueBinFile}
605 buildArgs = append(buildArgs, flags...)
606 buildArgs = append(buildArgs, pkg)
607 cmd := exec.Command("v23", buildArgs...)
Cosmos Nicolaou1fcb6a32015-02-17 07:46:02 -0800608 if output, err := cmd.CombinedOutput(); err != nil {
Cosmos Nicolaoue3b19322015-06-18 16:05:08 -0700609 t.ctx.VI(1).Infof("\n%v:\n%v\n", strings.Join(cmd.Args, " "), string(output))
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -0800610 return false, "", err
Jiri Simsae63e07d2014-12-04 11:12:08 -0800611 }
Cosmos Nicolaou47d55d92015-04-08 14:49:12 -0700612 if err := os.Rename(uniqueBinFile, binFile); err != nil {
613 // It seems that on some systems a rename may fail if another rename
614 // is in progress in the same directory. We back a random amount of time
615 // in the hope that a second attempt will succeed.
616 time.Sleep(time.Duration(rand.Int63n(1000)) * time.Millisecond)
617 if err := os.Rename(uniqueBinFile, binFile); err != nil {
618 return false, "", err
619 }
620 }
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -0800621 return false, binFile, nil
Jiri Simsae63e07d2014-12-04 11:12:08 -0800622 }
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -0800623 return true, binFile, nil
Jiri Simsae63e07d2014-12-04 11:12:08 -0800624}
625
Cosmos Nicolaou4a77c192015-02-08 15:29:18 -0800626// RunTest runs a single Vanadium 'v23 style' integration test.
Cosmos Nicolaou01007a02015-02-11 15:38:38 -0800627func RunTest(t *testing.T, fn func(i *T)) {
Cosmos Nicolaou1381f8a2015-03-13 09:40:34 -0700628 if !test.IntegrationTestsEnabled {
Cosmos Nicolaoud21f6b12015-02-07 11:40:03 -0800629 t.Skip()
Jiri Simsae63e07d2014-12-04 11:12:08 -0800630 }
Cosmos Nicolaoud21f6b12015-02-07 11:40:03 -0800631 i := New(t)
Cosmos Nicolaoucc5a4a82015-02-07 23:09:28 -0800632 // defer the Cleanup method so that it will be called even if
633 // t.Fatalf/FailNow etc are called and can print out useful information.
634 defer i.Cleanup()
Cosmos Nicolaoud21f6b12015-02-07 11:40:03 -0800635 fn(i)
Jiri Simsae63e07d2014-12-04 11:12:08 -0800636}
Cosmos Nicolaoud21f6b12015-02-07 11:40:03 -0800637
Asim Shankar59b8b692015-03-30 01:23:36 -0700638// RunRootMT builds and runs a root mount table instance. It populates the
Todd Wang8123b5e2015-05-14 18:44:43 -0700639// ref.EnvNamespacePrefix variable in the test environment so that all
Asim Shankar59b8b692015-03-30 01:23:36 -0700640// subsequent invocations will access this root mount table.
Cosmos Nicolaou01007a02015-02-11 15:38:38 -0800641func RunRootMT(i *T, args ...string) (*Binary, *Invocation) {
Cosmos Nicolaou42a17362015-03-10 16:40:18 -0700642 b := i.BuildV23Pkg("v.io/x/ref/services/mounttable/mounttabled")
Cosmos Nicolaoud3b1cb12015-02-20 00:06:33 -0800643 inv := b.start(1, args...)
Cosmos Nicolaou01007a02015-02-11 15:38:38 -0800644 name := inv.ExpectVar("NAME")
Todd Wang8123b5e2015-05-14 18:44:43 -0700645 inv.Environment().SetVar(ref.EnvNamespacePrefix, name)
Cosmos Nicolaoue3b19322015-06-18 16:05:08 -0700646 i.ctx.Infof("Running root mount table: %q", name)
Cosmos Nicolaou01007a02015-02-11 15:38:38 -0800647 return b, inv
Cosmos Nicolaoud21f6b12015-02-07 11:40:03 -0800648}
649
Cosmos Nicolaou93dd88b2015-02-19 15:10:53 -0800650// UseSharedBinDir ensures that a shared directory is used for binaries
Cosmos Nicolaou82d00d82015-02-10 21:31:00 -0800651// across multiple instances of the test environment. This is achieved
652// by setting the V23_BIN_DIR environment variable if it is not already
653// set in the test processes environment (as will typically be the case when
654// these tests are run from the v23 tool). It is intended to be called
655// from TestMain.
656func UseSharedBinDir() func() {
657 if v23BinDir := os.Getenv("V23_BIN_DIR"); len(v23BinDir) == 0 {
658 v23BinDir, err := ioutil.TempDir("", "bin-")
659 if err == nil {
Cosmos Nicolaou82d00d82015-02-10 21:31:00 -0800660 os.Setenv("V23_BIN_DIR", v23BinDir)
661 return func() { os.RemoveAll(v23BinDir) }
662 }
Cosmos Nicolaou82d00d82015-02-10 21:31:00 -0800663 }
664 return func() {}
665}