blob: 2b77e43c4239072fe4bd07e560f6834a2663d3eb [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 (
James Ring65795102014-12-17 13:01:03 -080074 "bytes"
Cosmos Nicolaoud21f6b12015-02-07 11:40:03 -080075 "container/list"
Cosmos Nicolaoucc5a4a82015-02-07 23:09:28 -080076 "errors"
Jiri Simsae63e07d2014-12-04 11:12:08 -080077 "fmt"
James Ring65795102014-12-17 13:01:03 -080078 "io"
Jiri Simsae63e07d2014-12-04 11:12:08 -080079 "io/ioutil"
80 "os"
81 "os/exec"
82 "path"
83 "path/filepath"
Cosmos Nicolaou1fcb6a32015-02-17 07:46:02 -080084 "runtime"
Jiri Simsae63e07d2014-12-04 11:12:08 -080085 "strings"
James Ring65795102014-12-17 13:01:03 -080086 "syscall"
Cosmos Nicolaou01007a02015-02-11 15:38:38 -080087 "testing"
Jiri Simsae63e07d2014-12-04 11:12:08 -080088 "time"
89
Suharsh Sivakumar19fbf992015-01-23 11:02:27 -080090 "v.io/core/veyron2"
Jiri Simsa764efb72014-12-25 20:57:03 -080091 "v.io/core/veyron2/security"
Cosmos Nicolaoud21f6b12015-02-07 11:40:03 -080092 "v.io/core/veyron2/vlog"
93
94 "v.io/core/veyron/lib/expect"
95 "v.io/core/veyron/lib/modules"
96 "v.io/core/veyron/lib/testutil"
97 tsecurity "v.io/core/veyron/lib/testutil/security"
Cosmos Nicolaou82d00d82015-02-10 21:31:00 -080098 "v.io/core/veyron/security/agent"
Jiri Simsae63e07d2014-12-04 11:12:08 -080099)
100
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -0800101// TB is an exact mirror of testing.TB. It is provided to allow for testing
102// of this package using a mock implementation. As per testing.TB, it is not
103// intended to be implemented outside of this package.
Cosmos Nicolaoud3b1cb12015-02-20 00:06:33 -0800104type TB interface {
105 Error(args ...interface{})
106 Errorf(format string, args ...interface{})
107 Fail()
108 FailNow()
109 Failed() bool
110 Fatal(args ...interface{})
111 Fatalf(format string, args ...interface{})
112 Log(args ...interface{})
113 Logf(format string, args ...interface{})
114 Skip(args ...interface{})
115 SkipNow()
116 Skipf(format string, args ...interface{})
117 Skipped() bool
118}
James Ring855dfd42015-01-23 12:30:58 -0800119
Cosmos Nicolaoucc5a4a82015-02-07 23:09:28 -0800120// T represents an integration test environment.
Cosmos Nicolaou01007a02015-02-11 15:38:38 -0800121type T struct {
Cosmos Nicolaoud3b1cb12015-02-20 00:06:33 -0800122 // The embedded TB
123 TB
James Ring65795102014-12-17 13:01:03 -0800124
Suharsh Sivakumar19fbf992015-01-23 11:02:27 -0800125 // The function to shutdown the context used to create the environment.
126 shutdown veyron2.Shutdown
127
James Ring65795102014-12-17 13:01:03 -0800128 // The shell to use to start commands.
129 shell *modules.Shell
130
131 // The environment's root security principal.
132 principal security.Principal
133
Cosmos Nicolaou01007a02015-02-11 15:38:38 -0800134 // Maps path to Binary.
135 builtBinaries map[string]*Binary
James Ring65795102014-12-17 13:01:03 -0800136
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -0800137 tempFiles []*os.File
138 tempDirs []string
139 binDir, cachedBinDir string
140 dirStack []string
Cosmos Nicolaoud21f6b12015-02-07 11:40:03 -0800141
Cosmos Nicolaou01007a02015-02-11 15:38:38 -0800142 invocations []*Invocation
James Ring65795102014-12-17 13:01:03 -0800143}
144
Cosmos Nicolaou01007a02015-02-11 15:38:38 -0800145// Binary represents an executable program that will be executed during a
146// test. A binary may be invoked multiple times by calling Start, each call
147// will return a new Invocation.
148//
149// Binary instances are typically obtained from a T by calling BuildGoPkg
150// (for Vanadium and other Go binaries) or BinaryFromPath (to start binaries
151// that are already present on the system).
152type Binary struct {
James Ring65795102014-12-17 13:01:03 -0800153 // The environment to which this binary belongs.
Cosmos Nicolaou01007a02015-02-11 15:38:38 -0800154 env *T
James Ring65795102014-12-17 13:01:03 -0800155
156 // The path to the binary.
157 path string
158
James Ring7edf1ca2015-01-08 12:32:29 -0800159 // Environment variables that will be used when creating invocations
160 // via Start.
161 envVars []string
162
James Ring79a9ceb2015-02-09 13:25:54 -0800163 // The reader who is supplying the bytes we're going to send to our stdin.
164 inputReader io.Reader
James Ring65795102014-12-17 13:01:03 -0800165}
166
Cosmos Nicolaou01007a02015-02-11 15:38:38 -0800167// Invocation represents a single invocation of a Binary.
168//
169// Any bytes written by the invocation to its standard error may be recovered
170// using the Wait or WaitOrDie functions.
171//
172// For example:
173// bin := env.BinaryFromPath("/bin/bash")
174// inv := bin.Start("-c", "echo hello world 1>&2")
175// var stderr bytes.Buffer
176// inv.WaitOrDie(nil, &stderr)
177// // stderr.Bytes() now contains 'hello world\n'
178type Invocation struct {
179 *expect.Session
Cosmos Nicolaoucc5a4a82015-02-07 23:09:28 -0800180
James Ring65795102014-12-17 13:01:03 -0800181 // The environment to which this invocation belongs.
Cosmos Nicolaou01007a02015-02-11 15:38:38 -0800182 env *T
James Ring65795102014-12-17 13:01:03 -0800183
184 // The handle to the process that was run when this invocation was started.
Cosmos Nicolaoud21f6b12015-02-07 11:40:03 -0800185 handle modules.Handle
186
187 // The element representing this invocation in the list of
188 // invocations stored in the environment
189 el *list.Element
190
Cosmos Nicolaoud21f6b12015-02-07 11:40:03 -0800191 // The path of the binary used for this invocation.
192 path string
Cosmos Nicolaoucc5a4a82015-02-07 23:09:28 -0800193
194 // The args the binary was started with
195 args []string
196
197 // True if the process has been shutdown
198 hasShutdown bool
199
200 // The error, if any, as determined when the invocation was
201 // shutdown. It must be set to a default initial value of
202 // errNotShutdown rather than nil to allow us to distinguish between
203 // a successful shutdown or an error.
204 shutdownErr error
James Ring65795102014-12-17 13:01:03 -0800205}
206
Cosmos Nicolaoucc5a4a82015-02-07 23:09:28 -0800207var errNotShutdown = errors.New("has not been shutdown")
208
Cosmos Nicolaou01007a02015-02-11 15:38:38 -0800209// Stdin returns this invocations Stdin stream.
210func (i *Invocation) Stdin() io.Writer {
Cosmos Nicolaoud21f6b12015-02-07 11:40:03 -0800211 return i.handle.Stdin()
James Ring65795102014-12-17 13:01:03 -0800212}
213
Cosmos Nicolaou01007a02015-02-11 15:38:38 -0800214// CloseStdin closes the write-side of the pipe to the invocation's
215// standard input.
216func (i *Invocation) CloseStdin() {
James Ring79a9ceb2015-02-09 13:25:54 -0800217 i.handle.CloseStdin()
218}
219
Cosmos Nicolaou01007a02015-02-11 15:38:38 -0800220// Stdout returns this invocations Stdout stream.
221func (i *Invocation) Stdout() io.Reader {
Cosmos Nicolaoud21f6b12015-02-07 11:40:03 -0800222 return i.handle.Stdout()
223}
224
Cosmos Nicolaou01007a02015-02-11 15:38:38 -0800225// Path returns the path to the binary that was used for this invocation.
226func (i *Invocation) Path() string {
Cosmos Nicolaoud21f6b12015-02-07 11:40:03 -0800227 return i.path
228}
229
Cosmos Nicolaou01007a02015-02-11 15:38:38 -0800230// Exists returns true if the invocation still exists.
231func (i *Invocation) Exists() bool {
Cosmos Nicolaoud21f6b12015-02-07 11:40:03 -0800232 return syscall.Kill(i.handle.Pid(), 0) == nil
James Ring65795102014-12-17 13:01:03 -0800233}
234
Cosmos Nicolaou01007a02015-02-11 15:38:38 -0800235// Sends the given signal to this invocation. It is up to the test
236// author to decide whether failure to deliver the signal is fatal to
237// the test.
238func (i *Invocation) Kill(sig syscall.Signal) error {
Cosmos Nicolaoud21f6b12015-02-07 11:40:03 -0800239 pid := i.handle.Pid()
Cosmos Nicolaoucc5a4a82015-02-07 23:09:28 -0800240 vlog.VI(1).Infof("sending signal %v to PID %d", sig, pid)
Cosmos Nicolaoud21f6b12015-02-07 11:40:03 -0800241 return syscall.Kill(pid, sig)
James Ring72b14bd2014-12-29 14:26:54 -0800242}
243
Cosmos Nicolaou93dd88b2015-02-19 15:10:53 -0800244// Caller returns a string of the form <filename>:<lineno> for the
245// caller specified by skip, where skip is as per runtime.Caller.
246func Caller(skip int) string {
247 _, file, line, _ := runtime.Caller(skip + 1)
Cosmos Nicolaou1fcb6a32015-02-17 07:46:02 -0800248 return fmt.Sprintf("%s:%d", filepath.Base(file), line)
James Ring65795102014-12-17 13:01:03 -0800249}
250
Cosmos Nicolaou01007a02015-02-11 15:38:38 -0800251// Output reads the invocation's stdout until EOF and then returns what
252// was read as a string.
253func (i *Invocation) Output() string {
Cosmos Nicolaou1fcb6a32015-02-17 07:46:02 -0800254 buf := bytes.Buffer{}
255 _, err := buf.ReadFrom(i.Stdout())
256 if err != nil {
Cosmos Nicolaou93dd88b2015-02-19 15:10:53 -0800257 i.env.Fatalf("%s: ReadFrom() failed: %v", Caller(1), err)
Cosmos Nicolaou1fcb6a32015-02-17 07:46:02 -0800258 }
259 return buf.String()
James Ring65795102014-12-17 13:01:03 -0800260}
261
Cosmos Nicolaou01007a02015-02-11 15:38:38 -0800262// Wait waits for this invocation to finish. If either stdout or stderr
263// is non-nil, any remaining unread output from those sources will be
264// written to the corresponding writer. The returned error represents
265// the exit status of the underlying command.
266func (i *Invocation) Wait(stdout, stderr io.Writer) error {
Cosmos Nicolaoucc5a4a82015-02-07 23:09:28 -0800267 err := i.handle.Shutdown(stdout, stderr)
268 i.hasShutdown = true
269 i.shutdownErr = err
270 return err
James Ringe78c7da2015-01-06 15:59:37 -0800271}
272
Cosmos Nicolaou01007a02015-02-11 15:38:38 -0800273// Wait waits for this invocation to finish. If either stdout or stderr
274// is non-nil, any remaining unread output from those sources will be
275// written to the corresponding writer. If the underlying command
276// exited with anything but success (exit status 0), this function will
277// cause the current test to fail.
278func (i *Invocation) WaitOrDie(stdout, stderr io.Writer) {
James Ringe78c7da2015-01-06 15:59:37 -0800279 if err := i.Wait(stdout, stderr); err != nil {
Cosmos Nicolaou93dd88b2015-02-19 15:10:53 -0800280 i.env.Fatalf("%s: FATAL: Wait() for pid %d failed: %v", Caller(1), i.handle.Pid(), err)
James Ring65795102014-12-17 13:01:03 -0800281 }
282}
283
Cosmos Nicolaou01007a02015-02-11 15:38:38 -0800284// Environment returns the instance of the test environment that this
285// invocation was from.
286func (i *Invocation) Environment() *T {
Cosmos Nicolaoud21f6b12015-02-07 11:40:03 -0800287 return i.env
288}
289
Cosmos Nicolaou01007a02015-02-11 15:38:38 -0800290func (b *Binary) cleanup() {
James Ring65795102014-12-17 13:01:03 -0800291 binaryDir := path.Dir(b.path)
Cosmos Nicolaoucc5a4a82015-02-07 23:09:28 -0800292 vlog.Infof("cleaning up %s", binaryDir)
James Ring65795102014-12-17 13:01:03 -0800293 if err := os.RemoveAll(binaryDir); err != nil {
Cosmos Nicolaoucc5a4a82015-02-07 23:09:28 -0800294 vlog.Infof("WARNING: RemoveAll(%s) failed (%v)", binaryDir, err)
James Ring65795102014-12-17 13:01:03 -0800295 }
296}
297
Cosmos Nicolaou01007a02015-02-11 15:38:38 -0800298// Path returns the path to the binary.
299func (b *Binary) Path() string {
James Ring65795102014-12-17 13:01:03 -0800300 return b.path
301}
302
Cosmos Nicolaou01007a02015-02-11 15:38:38 -0800303// Start starts the given binary with the given arguments.
304func (b *Binary) Start(args ...string) *Invocation {
Cosmos Nicolaoud3b1cb12015-02-20 00:06:33 -0800305 return b.start(1, args...)
306}
307
308func (b *Binary) start(skip int, args ...string) *Invocation {
309 vlog.Infof("%s: starting %s %s", Caller(skip+1), b.Path(), strings.Join(args, " "))
Cosmos Nicolaoud61e5a82015-02-22 16:32:48 -0800310 handle, err := b.env.shell.StartExternalCommand(b.inputReader, b.envVars, append([]string{b.Path()}, args...)...)
James Ring65795102014-12-17 13:01:03 -0800311 if err != nil {
Cosmos Nicolaou93dd88b2015-02-19 15:10:53 -0800312 // TODO(cnicolaou): calling Fatalf etc from a goroutine often leads
313 // to deadlock. Need to make sure that we handle this here. Maybe
314 // it's best to just return an error? Or provide a StartWithError
315 // call for use from goroutines.
Cosmos Nicolaoud3b1cb12015-02-20 00:06:33 -0800316 b.env.Fatalf("%s: StartExternalCommand(%v, %v) failed: %v", Caller(skip+1), b.Path(), strings.Join(args, ", "), err)
James Ring65795102014-12-17 13:01:03 -0800317 }
Cosmos Nicolaoucc5a4a82015-02-07 23:09:28 -0800318 vlog.Infof("started PID %d\n", handle.Pid())
Cosmos Nicolaou01007a02015-02-11 15:38:38 -0800319 inv := &Invocation{
Cosmos Nicolaoucc5a4a82015-02-07 23:09:28 -0800320 env: b.env,
321 handle: handle,
322 path: b.path,
323 args: args,
324 shutdownErr: errNotShutdown,
325 Session: expect.NewSession(b.env, handle.Stdout(), 5*time.Minute),
James Ring65795102014-12-17 13:01:03 -0800326 }
Cosmos Nicolaoucc5a4a82015-02-07 23:09:28 -0800327 b.env.appendInvocation(inv)
Cosmos Nicolaoud21f6b12015-02-07 11:40:03 -0800328 return inv
James Ring65795102014-12-17 13:01:03 -0800329}
330
Cosmos Nicolaoud3b1cb12015-02-20 00:06:33 -0800331func (b *Binary) run(args ...string) string {
332 inv := b.start(2, args...)
333 var stdout, stderr bytes.Buffer
334 err := inv.Wait(&stdout, &stderr)
335 if err != nil {
336 a := strings.Join(args, ", ")
337 b.env.Fatalf("%s: Run(%s): failed: %v: \n%s\n", Caller(2), a, err, stderr.String())
338 }
339 return strings.TrimRight(stdout.String(), "\n")
340}
341
342// Run runs the binary with the specified arguments to completion. On
343// success it returns the contents of stdout, on failure it terminates the
344// test with an error message containing the error and the contents of
345// stderr.
346func (b *Binary) Run(args ...string) string {
347 return b.run(args...)
348}
349
350// Run constructs a Binary for path and invokes Run on it.
351func (e *T) Run(path string, args ...string) string {
352 return e.BinaryFromPath(path).run(args...)
353}
354
355// WaitFunc is the type of the functions to be used in conjunction
356// with WaitFor and WaitForAsync. It should return a value or an error
357// when it wants those functions to terminate, returning a nil value
358// and nil error will result in it being called again after the specified
359// delay time specified in the calls to WaitFor and WaitForAsync.
360type WaitFunc func() (interface{}, error)
361
362// WaitFor calls fn at least once with the specified delay value
363// between iterations until the first of the following is encountered:
364// 1. fn returns a non-nil value.
365// 2. fn returns an error value
366// 3. fn is executed at least once and the specified timeout is exceeded.
367//
368// WaitFor returns the non-nil value for the first case and calls e.Fatalf for
369// the other two cases.
370// WaitFor will always run fn at least once to completion and hence it will
371// hang if that first iteration of fn hangs. If this behaviour is not
372// appropriate, then WaitForAsync should be used.
373func (e *T) WaitFor(fn WaitFunc, delay, timeout time.Duration) interface{} {
374 deadline := time.Now().Add(timeout)
375 for {
376 val, err := fn()
377 if val != nil {
378 return val
379 }
380 if err != nil {
381 e.Fatalf("%s: the WaitFunc returned an error: %v", Caller(1), err)
382 }
383 if time.Now().After(deadline) {
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -0800384 e.Fatalf("%s: timed out after %s", Caller(1), timeout)
Cosmos Nicolaoud3b1cb12015-02-20 00:06:33 -0800385 }
386 time.Sleep(delay)
387 }
388}
389
390// WaitForAsync is like WaitFor except that it calls fn in a goroutine
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -0800391// and can timeout during the execution of fn.
Cosmos Nicolaoud3b1cb12015-02-20 00:06:33 -0800392func (e *T) WaitForAsync(fn WaitFunc, delay, timeout time.Duration) interface{} {
393 resultCh := make(chan interface{})
394 errCh := make(chan interface{})
395 go func() {
396 for {
397 val, err := fn()
398 if val != nil {
399 resultCh <- val
400 return
401 }
402 if err != nil {
403 errCh <- err
404 return
405 }
406 time.Sleep(delay)
407 }
408 }()
409 select {
410 case err := <-errCh:
411 e.Fatalf("%s: the WaitFunc returned error: %v", Caller(1), err)
412 case result := <-resultCh:
413 return result
414 case <-time.After(timeout):
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -0800415 e.Fatalf("%s: timed out after %s", Caller(1), timeout)
Cosmos Nicolaoud3b1cb12015-02-20 00:06:33 -0800416 }
417 return nil
418}
419
420// Pushd pushes the current working directory to the stack of
421// directories, returning it as its result, and changes the working
422// directory to dir.
423func (e *T) Pushd(dir string) string {
424 cwd, err := os.Getwd()
425 if err != nil {
426 e.Fatalf("%s: Getwd failed: %s", Caller(1), err)
427 }
428 if err := os.Chdir(dir); err != nil {
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -0800429 e.Fatalf("%s: Chdir failed: %s", Caller(1), err)
Cosmos Nicolaoud3b1cb12015-02-20 00:06:33 -0800430 }
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -0800431 vlog.VI(1).Infof("Pushd: %s -> %s", cwd, dir)
Cosmos Nicolaoud3b1cb12015-02-20 00:06:33 -0800432 e.dirStack = append(e.dirStack, cwd)
433 return cwd
434}
435
436// Popd pops the most recent entry from the directory stack and changes
437// the working directory to that directory. It returns the new working
438// directory as its result.
439func (e *T) Popd() string {
440 if len(e.dirStack) == 0 {
441 e.Fatalf("%s: directory stack empty", Caller(1))
442 }
443 dir := e.dirStack[len(e.dirStack)-1]
444 e.dirStack = e.dirStack[:len(e.dirStack)-1]
445 if err := os.Chdir(dir); err != nil {
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -0800446 e.Fatalf("%s: Chdir failed: %s", Caller(1), err)
Cosmos Nicolaoud3b1cb12015-02-20 00:06:33 -0800447 }
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -0800448 vlog.VI(1).Infof("Popd: -> %s", dir)
Cosmos Nicolaoud3b1cb12015-02-20 00:06:33 -0800449 return dir
450}
451
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -0800452// Caller returns a string of the form <filename>:<lineno> for the
453// caller specified by skip, where skip is as per runtime.Caller.
454func (e *T) Caller(skip int) string {
455 return Caller(skip + 1)
456}
457
Cosmos Nicolaou01007a02015-02-11 15:38:38 -0800458// WithStdin returns a copy of this binary that, when Start is called,
459// will read its input from the given reader. Once the reader returns
460// EOF, the returned invocation's standard input will be closed (see
461// Invocation.CloseStdin).
462func (b *Binary) WithStdin(r io.Reader) *Binary {
James Ring79a9ceb2015-02-09 13:25:54 -0800463 newBin := *b
464 newBin.inputReader = r
465 return &newBin
466}
467
Cosmos Nicolaou01007a02015-02-11 15:38:38 -0800468// Returns a copy of this binary that, when Start is called, will use
469// the given environment variables. Each environment variable should be
470// in "key=value" form. For example:
471//
472// bin.WithEnv("EXAMPLE_ENV=/tmp/something").Start(...)
473func (b *Binary) WithEnv(env ...string) *Binary {
James Ring7edf1ca2015-01-08 12:32:29 -0800474 newBin := *b
475 newBin.envVars = env
476 return &newBin
477}
478
Cosmos Nicolaou01007a02015-02-11 15:38:38 -0800479// Principal returns the security principal of this environment.
480func (e *T) Principal() security.Principal {
James Ring65795102014-12-17 13:01:03 -0800481 return e.principal
482}
483
Cosmos Nicolaou01007a02015-02-11 15:38:38 -0800484// Cleanup cleans up the environment, deletes all its artifacts and
485// kills all subprocesses. It will kill subprocesses in LIFO order.
486// Cleanup checks to see if the test has failed and logs information
487// as to the state of the processes it was asked to invoke up to that
488// point and optionally, if the --v23.tests.shell-on-fail flag is set
489// then it will run a debug shell before cleaning up its state.
490func (e *T) Cleanup() {
Cosmos Nicolaoucc5a4a82015-02-07 23:09:28 -0800491 if e.Failed() {
492 if testutil.IntegrationTestsDebugShellOnError {
493 e.DebugShell()
494 }
495 // Print out a summary of the invocations and their status.
496 for i, inv := range e.invocations {
497 if inv.hasShutdown && inv.Exists() {
498 m := fmt.Sprintf("%d: %s has been shutdown but still exists: %v", i, inv.path, inv.shutdownErr)
499 e.Log(m)
500 vlog.VI(1).Info(m)
501 vlog.VI(2).Infof("%d: %s %v", i, inv.path, inv.args)
502 continue
503 }
Cosmos Nicolaou93dd88b2015-02-19 15:10:53 -0800504 if inv.shutdownErr != nil {
505 m := fmt.Sprintf("%d: %s: shutdown status: %v", i, inv.path, inv.shutdownErr)
506 e.Log(m)
507 vlog.VI(1).Info(m)
508 vlog.VI(2).Infof("%d: %s %v", i, inv.path, inv.args)
509 }
Cosmos Nicolaoucc5a4a82015-02-07 23:09:28 -0800510 }
511 }
512
Cosmos Nicolaoud21f6b12015-02-07 11:40:03 -0800513 vlog.VI(1).Infof("V23Test.Cleanup")
Cosmos Nicolaou82d00d82015-02-10 21:31:00 -0800514 // Shut down all processes in LIFO order before attempting to delete any
Cosmos Nicolaoud21f6b12015-02-07 11:40:03 -0800515 // files/directories to avoid potential 'file system busy' problems
516 // on non-unix systems.
Cosmos Nicolaou82d00d82015-02-10 21:31:00 -0800517 for i := len(e.invocations); i > 0; i-- {
518 inv := e.invocations[i-1]
Cosmos Nicolaoucc5a4a82015-02-07 23:09:28 -0800519 if inv.hasShutdown {
520 vlog.VI(1).Infof("V23Test.Cleanup: %q has been shutdown", inv.Path())
521 continue
Cosmos Nicolaoud21f6b12015-02-07 11:40:03 -0800522 }
Cosmos Nicolaoud21f6b12015-02-07 11:40:03 -0800523 vlog.VI(1).Infof("V23Test.Cleanup: Kill: %q", inv.Path())
524 err := inv.Kill(syscall.SIGTERM)
525 inv.Wait(os.Stdout, os.Stderr)
526 vlog.VI(1).Infof("V23Test.Cleanup: Killed: %q: %v", inv.Path(), err)
527 }
Cosmos Nicolaoucc5a4a82015-02-07 23:09:28 -0800528 vlog.VI(1).Infof("V23Test.Cleanup: all invocations taken care of.")
Cosmos Nicolaoud21f6b12015-02-07 11:40:03 -0800529
530 if err := e.shell.Cleanup(os.Stdout, os.Stderr); err != nil {
531 e.Fatalf("WARNING: could not clean up shell (%v)", err)
532 }
533
Cosmos Nicolaoucc5a4a82015-02-07 23:09:28 -0800534 vlog.VI(1).Infof("V23Test.Cleanup: cleaning up binaries & files")
535
James Ring65795102014-12-17 13:01:03 -0800536 for _, tempFile := range e.tempFiles {
Cosmos Nicolaoucc5a4a82015-02-07 23:09:28 -0800537 vlog.VI(1).Infof("V23Test.Cleanup: cleaning up %s", tempFile.Name())
James Ring65795102014-12-17 13:01:03 -0800538 if err := tempFile.Close(); err != nil {
Cosmos Nicolaoucc5a4a82015-02-07 23:09:28 -0800539 vlog.Errorf("WARNING: Close(%q) failed: %v", tempFile.Name(), err)
James Ring65795102014-12-17 13:01:03 -0800540 }
James Ring23b54862015-01-07 11:54:48 -0800541 if err := os.RemoveAll(tempFile.Name()); err != nil {
Cosmos Nicolaoucc5a4a82015-02-07 23:09:28 -0800542 vlog.Errorf("WARNING: RemoveAll(%q) failed: %v", tempFile.Name(), err)
James Ring23b54862015-01-07 11:54:48 -0800543 }
544 }
545
546 for _, tempDir := range e.tempDirs {
Cosmos Nicolaoucc5a4a82015-02-07 23:09:28 -0800547 vlog.VI(1).Infof("V23Test.Cleanup: cleaning up %s", tempDir)
James Ring23b54862015-01-07 11:54:48 -0800548 if err := os.RemoveAll(tempDir); err != nil {
Cosmos Nicolaoucc5a4a82015-02-07 23:09:28 -0800549 vlog.Errorf("WARNING: RemoveAll(%q) failed: %v", tempDir, err)
James Ring65795102014-12-17 13:01:03 -0800550 }
551 }
552
Cosmos Nicolaoucc5a4a82015-02-07 23:09:28 -0800553 // shutdown the runtime
Cosmos Nicolaou731c6922015-02-05 17:05:20 -0800554 e.shutdown()
James Ring65795102014-12-17 13:01:03 -0800555}
556
Cosmos Nicolaou01007a02015-02-11 15:38:38 -0800557// GetVar returns the variable associated with the specified key
558// and an indication of whether it is defined or not.
559func (e *T) GetVar(key string) (string, bool) {
Cosmos Nicolaoud21f6b12015-02-07 11:40:03 -0800560 return e.shell.GetVar(key)
561}
562
Cosmos Nicolaou01007a02015-02-11 15:38:38 -0800563// SetVar sets the value to be associated with key.
564func (e *T) SetVar(key, value string) {
Cosmos Nicolaoud21f6b12015-02-07 11:40:03 -0800565 e.shell.SetVar(key, value)
566}
567
Cosmos Nicolaou01007a02015-02-11 15:38:38 -0800568// ClearVar removes the speficied variable from the Shell's environment
569func (e *T) ClearVar(key string) {
Cosmos Nicolaoud21f6b12015-02-07 11:40:03 -0800570 e.shell.ClearVar(key)
571}
572
Cosmos Nicolaou01007a02015-02-11 15:38:38 -0800573func writeStringOrDie(t *T, f *os.File, s string) {
James Ring0bad5672015-01-05 09:51:36 -0800574 if _, err := f.WriteString(s); err != nil {
James Ring0d525c22014-12-30 12:03:31 -0800575 t.Fatalf("Write() failed: %v", err)
576 }
577}
578
Cosmos Nicolaou93dd88b2015-02-19 15:10:53 -0800579// DebugShell drops the user into a debug shell with any environment
580// variables specified in env... (in VAR=VAL format) available to it.
581// If there is no controlling TTY, DebugShell will emit a warning message
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -0800582// and take no futher action. The DebugShell also sets some environment
583// variables that relate to the running test:
584// - V23_TMP_DIR<#> contains the name of each temp directory created.
585// - V23_BIN_DIR contains the name of the directory containing binaries.
Cosmos Nicolaou93dd88b2015-02-19 15:10:53 -0800586func (e *T) DebugShell(env ...string) {
James Ring65795102014-12-17 13:01:03 -0800587 // Get the current working directory.
588 cwd, err := os.Getwd()
589 if err != nil {
Cosmos Nicolaou234642b2015-02-04 18:30:52 -0800590 e.Fatalf("Getwd() failed: %v", err)
James Ring65795102014-12-17 13:01:03 -0800591 }
592
593 // Transfer stdin, stdout, and stderr to the new process
594 // and also set target directory for the shell to start in.
595 dev := "/dev/tty"
James Ring0d525c22014-12-30 12:03:31 -0800596 fd, err := syscall.Open(dev, syscall.O_RDWR, 0)
James Ring65795102014-12-17 13:01:03 -0800597 if err != nil {
Cosmos Nicolaoucc5a4a82015-02-07 23:09:28 -0800598 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 -0800599 return
600 }
Cosmos Nicolaou82d00d82015-02-10 21:31:00 -0800601
602 agentFile, err := e.shell.NewChildCredentials()
603 if err != nil {
604 vlog.Errorf("WARNING: failed to obtain credentials for the debug shell: %v", err)
605 }
606
James Ring0d525c22014-12-30 12:03:31 -0800607 file := os.NewFile(uintptr(fd), dev)
James Ring65795102014-12-17 13:01:03 -0800608 attr := os.ProcAttr{
James Ring0d525c22014-12-30 12:03:31 -0800609 Files: []*os.File{file, file, file},
James Ring65795102014-12-17 13:01:03 -0800610 Dir: cwd,
611 }
Cosmos Nicolaou93dd88b2015-02-19 15:10:53 -0800612 // Set up agent for Child.
Cosmos Nicolaou82d00d82015-02-10 21:31:00 -0800613 attr.Files = append(attr.Files, agentFile)
614 attr.Env = append(attr.Env, fmt.Sprintf("%s=%d", agent.FdVarName, len(attr.Files)-1))
James Ring65795102014-12-17 13:01:03 -0800615
Cosmos Nicolaoud21f6b12015-02-07 11:40:03 -0800616 // Set up environment for Child.
Cosmos Nicolaou1fcb6a32015-02-17 07:46:02 -0800617 for _, v := range e.shell.Env() {
618 attr.Env = append(attr.Env, v)
Cosmos Nicolaoud21f6b12015-02-07 11:40:03 -0800619 }
Cosmos Nicolaoud21f6b12015-02-07 11:40:03 -0800620
Cosmos Nicolaou93dd88b2015-02-19 15:10:53 -0800621 for i, td := range e.tempDirs {
622 attr.Env = append(attr.Env, fmt.Sprintf("V23_TMP_DIR%d=%s", i, td))
623 }
624
625 if len(e.cachedBinDir) > 0 {
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -0800626 attr.Env = append(attr.Env, "V23_BIN_DIR="+e.BinDir())
Cosmos Nicolaou93dd88b2015-02-19 15:10:53 -0800627 }
628 attr.Env = append(attr.Env, env...)
629
James Ring65795102014-12-17 13:01:03 -0800630 // Start up a new shell.
Cosmos Nicolaou234642b2015-02-04 18:30:52 -0800631 writeStringOrDie(e, file, ">> Starting a new interactive shell\n")
632 writeStringOrDie(e, file, "Hit CTRL-D to resume the test\n")
James Ring65795102014-12-17 13:01:03 -0800633 if len(e.builtBinaries) > 0 {
Cosmos Nicolaou234642b2015-02-04 18:30:52 -0800634 writeStringOrDie(e, file, "Built binaries:\n")
James Ring65795102014-12-17 13:01:03 -0800635 for _, value := range e.builtBinaries {
Cosmos Nicolaou234642b2015-02-04 18:30:52 -0800636 writeStringOrDie(e, file, "\t"+value.Path()+"\n")
James Ring65795102014-12-17 13:01:03 -0800637 }
638 }
Cosmos Nicolaoua866f262015-02-10 14:56:06 -0800639 if len(e.cachedBinDir) > 0 {
Cosmos Nicolaou93dd88b2015-02-19 15:10:53 -0800640 writeStringOrDie(e, file, fmt.Sprintf("Binaries are cached in %q\n", e.cachedBinDir))
Cosmos Nicolaoua866f262015-02-10 14:56:06 -0800641 } else {
Cosmos Nicolaou980a8702015-02-20 23:33:46 -0800642 writeStringOrDie(e, file, fmt.Sprintf("Caching of binaries was not enabled, being written to %q\n", e.binDir))
Cosmos Nicolaoua866f262015-02-10 14:56:06 -0800643 }
James Ring65795102014-12-17 13:01:03 -0800644
645 shellPath := "/bin/sh"
James Ring2fdeb452015-02-02 21:18:22 -0800646 if shellPathFromEnv := os.Getenv("SHELL"); shellPathFromEnv != "" {
James Ringaa701492015-02-03 11:43:51 -0800647 shellPath = shellPathFromEnv
James Ring2fdeb452015-02-02 21:18:22 -0800648 }
James Ring65795102014-12-17 13:01:03 -0800649 proc, err := os.StartProcess(shellPath, []string{}, &attr)
650 if err != nil {
Cosmos Nicolaou234642b2015-02-04 18:30:52 -0800651 e.Fatalf("StartProcess(%q) failed: %v", shellPath, err)
James Ring65795102014-12-17 13:01:03 -0800652 }
653
654 // Wait until user exits the shell
655 state, err := proc.Wait()
656 if err != nil {
Cosmos Nicolaou234642b2015-02-04 18:30:52 -0800657 e.Fatalf("Wait(%v) failed: %v", shellPath, err)
James Ring65795102014-12-17 13:01:03 -0800658 }
659
Cosmos Nicolaou234642b2015-02-04 18:30:52 -0800660 writeStringOrDie(e, file, fmt.Sprintf("<< Exited shell: %s\n", state.String()))
James Ring65795102014-12-17 13:01:03 -0800661}
662
Cosmos Nicolaou01007a02015-02-11 15:38:38 -0800663// BinaryFromPath returns a new Binary that, when started, will
664// execute the executable or script at the given path.
665//
666// E.g. env.BinaryFromPath("/bin/bash").Start("-c", "echo hello world").Output() -> "hello world"
667func (e *T) BinaryFromPath(path string) *Binary {
668 return &Binary{
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -0800669 env: e,
670 envVars: nil,
671 path: path,
James Ring90d21dc2015-02-02 18:10:38 -0800672 }
673}
674
Cosmos Nicolaou01007a02015-02-11 15:38:38 -0800675// BuildGoPkg expects a Go package path that identifies a "main"
676// package and returns a Binary representing the newly built
677// binary.
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -0800678func (e *T) BuildGoPkg(pkg string) *Binary {
Cosmos Nicolaou82d00d82015-02-10 21:31:00 -0800679 then := time.Now()
Cosmos Nicolaou93dd88b2015-02-19 15:10:53 -0800680 loc := Caller(1)
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -0800681 cached, built_path, err := buildPkg(e.BinDir(), pkg)
James Ring65795102014-12-17 13:01:03 -0800682 if err != nil {
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -0800683 e.Fatalf("%s: buildPkg(%s) failed: %v", loc, pkg, err)
James Ring65795102014-12-17 13:01:03 -0800684 return nil
685 }
Cosmos Nicolaoua866f262015-02-10 14:56:06 -0800686
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -0800687 if _, err := os.Stat(built_path); err != nil {
688 e.Fatalf("%s: buildPkg(%s) failed to stat %q", loc, pkg, built_path)
Cosmos Nicolaou82d00d82015-02-10 21:31:00 -0800689 }
690 taken := time.Now().Sub(then)
691 if cached {
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -0800692 vlog.Infof("%s: using %s, from %s in %s.", loc, pkg, built_path, taken)
Cosmos Nicolaoua866f262015-02-10 14:56:06 -0800693 } else {
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -0800694 vlog.Infof("%s: built %s, written to %s in %s.", loc, pkg, built_path, taken)
Cosmos Nicolaoua866f262015-02-10 14:56:06 -0800695 }
696
Cosmos Nicolaou01007a02015-02-11 15:38:38 -0800697 binary := &Binary{
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -0800698 env: e,
699 envVars: nil,
700 path: built_path,
James Ring65795102014-12-17 13:01:03 -0800701 }
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -0800702 e.builtBinaries[pkg] = binary
James Ring65795102014-12-17 13:01:03 -0800703 return binary
704}
705
Cosmos Nicolaou93dd88b2015-02-19 15:10:53 -0800706// NewTempFile creates a temporary file. Temporary files will be deleted
Cosmos Nicolaou01007a02015-02-11 15:38:38 -0800707// by Cleanup.
Cosmos Nicolaou93dd88b2015-02-19 15:10:53 -0800708func (e *T) NewTempFile() *os.File {
709 loc := Caller(1)
James Ring65795102014-12-17 13:01:03 -0800710 f, err := ioutil.TempFile("", "")
711 if err != nil {
Cosmos Nicolaou1fcb6a32015-02-17 07:46:02 -0800712 e.Fatalf("%s: TempFile() failed: %v", loc, err)
James Ring65795102014-12-17 13:01:03 -0800713 }
Cosmos Nicolaou1fcb6a32015-02-17 07:46:02 -0800714 vlog.Infof("%s: created temporary file at %s", loc, f.Name())
James Ring65795102014-12-17 13:01:03 -0800715 e.tempFiles = append(e.tempFiles, f)
716 return f
717}
718
Cosmos Nicolaou93dd88b2015-02-19 15:10:53 -0800719// NewTempDir creates a temporary directory. Temporary directories and
Cosmos Nicolaou01007a02015-02-11 15:38:38 -0800720// their contents will be deleted by Cleanup.
Cosmos Nicolaou93dd88b2015-02-19 15:10:53 -0800721func (e *T) NewTempDir() string {
722 loc := Caller(1)
James Ring23b54862015-01-07 11:54:48 -0800723 f, err := ioutil.TempDir("", "")
724 if err != nil {
Cosmos Nicolaou1fcb6a32015-02-17 07:46:02 -0800725 e.Fatalf("%s: TempDir() failed: %v", loc, err)
James Ring23b54862015-01-07 11:54:48 -0800726 }
Cosmos Nicolaou1fcb6a32015-02-17 07:46:02 -0800727 vlog.Infof("%s: created temporary directory at %s", loc, f)
James Ring23b54862015-01-07 11:54:48 -0800728 e.tempDirs = append(e.tempDirs, f)
729 return f
730}
731
Cosmos Nicolaou01007a02015-02-11 15:38:38 -0800732func (e *T) appendInvocation(inv *Invocation) {
Cosmos Nicolaoucc5a4a82015-02-07 23:09:28 -0800733 e.invocations = append(e.invocations, inv)
Cosmos Nicolaoud21f6b12015-02-07 11:40:03 -0800734}
735
James Ringa71e49a2015-01-16 14:08:02 -0800736// Creates a new local testing environment. A local testing environment has a
Cosmos Nicolaoud21f6b12015-02-07 11:40:03 -0800737// a security principle available via Principal().
James Ringa71e49a2015-01-16 14:08:02 -0800738//
739// You should clean up the returned environment using the env.Cleanup() method.
740// A typical end-to-end test will begin like:
741//
742// func TestFoo(t *testing.T) {
Cosmos Nicolaou01007a02015-02-11 15:38:38 -0800743// env := integration.NewT(t)
James Ringa71e49a2015-01-16 14:08:02 -0800744// defer env.Cleanup()
745//
746// ...
747// }
Cosmos Nicolaoud3b1cb12015-02-20 00:06:33 -0800748func New(t TB) *T {
Suharsh Sivakumar19fbf992015-01-23 11:02:27 -0800749 ctx, shutdown := veyron2.Init()
750
Cosmos Nicolaoucc5a4a82015-02-07 23:09:28 -0800751 vlog.Infof("creating root principal")
James Ring65795102014-12-17 13:01:03 -0800752 principal := tsecurity.NewPrincipal("root")
Suharsh Sivakumar19fbf992015-01-23 11:02:27 -0800753 ctx, err := veyron2.SetPrincipal(ctx, principal)
Ryan Browna08a2212015-01-15 15:40:10 -0800754 if err != nil {
Suharsh Sivakumar19fbf992015-01-23 11:02:27 -0800755 t.Fatalf("failed to set principal: %v", err)
Ryan Browna08a2212015-01-15 15:40:10 -0800756 }
Suharsh Sivakumar19fbf992015-01-23 11:02:27 -0800757
758 shell, err := modules.NewShell(ctx, principal)
James Ring65795102014-12-17 13:01:03 -0800759 if err != nil {
760 t.Fatalf("NewShell() failed: %v", err)
761 }
Cosmos Nicolaoud21f6b12015-02-07 11:40:03 -0800762 shell.SetStartTimeout(1 * time.Minute)
Cosmos Nicolaoufb3bee52015-02-05 11:59:14 -0800763 shell.SetWaitTimeout(5 * time.Minute)
James Ring65795102014-12-17 13:01:03 -0800764
Cosmos Nicolaou82d00d82015-02-10 21:31:00 -0800765 // The V23_BIN_DIR environment variable can be
766 // used to identify a directory that multiple integration
767 // tests can use to share binaries. Whoever sets this
768 // environment variable is responsible for cleaning up the
769 // directory it points to.
Cosmos Nicolaoua866f262015-02-10 14:56:06 -0800770 cachedBinDir := os.Getenv("V23_BIN_DIR")
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -0800771 e := &T{
Cosmos Nicolaoud3b1cb12015-02-20 00:06:33 -0800772 TB: t,
James Ring65795102014-12-17 13:01:03 -0800773 principal: principal,
Cosmos Nicolaou01007a02015-02-11 15:38:38 -0800774 builtBinaries: make(map[string]*Binary),
James Ring65795102014-12-17 13:01:03 -0800775 shell: shell,
James Ring65795102014-12-17 13:01:03 -0800776 tempFiles: []*os.File{},
James Ring23b54862015-01-07 11:54:48 -0800777 tempDirs: []string{},
Cosmos Nicolaoua866f262015-02-10 14:56:06 -0800778 cachedBinDir: cachedBinDir,
Suharsh Sivakumar19fbf992015-01-23 11:02:27 -0800779 shutdown: shutdown,
James Ring65795102014-12-17 13:01:03 -0800780 }
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -0800781 if len(e.cachedBinDir) == 0 {
782 e.binDir = e.NewTempDir()
783 }
784 return e
785}
786
787// BinDir returns the directory that binarie files are stored in.
788func (e *T) BinDir() string {
789 if len(e.cachedBinDir) > 0 {
790 return e.cachedBinDir
791 }
792 return e.binDir
James Ring65795102014-12-17 13:01:03 -0800793}
794
James Ringbd2216f2015-01-15 20:06:18 -0800795// BuildPkg returns a path to a directory that contains the built binary for
796// the given packages and a function that should be invoked to clean up the
797// build artifacts. Note that the clients of this function should not modify
798// the contents of this directory directly and instead defer to the cleanup
799// function.
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -0800800func buildPkg(binDir, pkg string) (bool, string, error) {
James Ringbd2216f2015-01-15 20:06:18 -0800801 binFile := filepath.Join(binDir, path.Base(pkg))
802 if _, err := os.Stat(binFile); err != nil {
803 if !os.IsNotExist(err) {
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -0800804 return false, "", err
James Ringbd2216f2015-01-15 20:06:18 -0800805 }
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -0800806 cmd := exec.Command("v23", "go", "build", "-o", binFile, pkg)
Cosmos Nicolaou1fcb6a32015-02-17 07:46:02 -0800807 if output, err := cmd.CombinedOutput(); err != nil {
808 vlog.VI(1).Infof("\n%v:\n%v\n", strings.Join(cmd.Args, " "), string(output))
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -0800809 return false, "", err
Jiri Simsae63e07d2014-12-04 11:12:08 -0800810 }
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -0800811 return false, binFile, nil
Jiri Simsae63e07d2014-12-04 11:12:08 -0800812 }
Cosmos Nicolaoua6fef892015-02-20 23:09:03 -0800813 return true, binFile, nil
Jiri Simsae63e07d2014-12-04 11:12:08 -0800814}
815
Cosmos Nicolaou4a77c192015-02-08 15:29:18 -0800816// RunTest runs a single Vanadium 'v23 style' integration test.
Cosmos Nicolaou01007a02015-02-11 15:38:38 -0800817func RunTest(t *testing.T, fn func(i *T)) {
Cosmos Nicolaoucc5a4a82015-02-07 23:09:28 -0800818 if !testutil.IntegrationTestsEnabled {
Cosmos Nicolaoud21f6b12015-02-07 11:40:03 -0800819 t.Skip()
Jiri Simsae63e07d2014-12-04 11:12:08 -0800820 }
Cosmos Nicolaoud21f6b12015-02-07 11:40:03 -0800821 i := New(t)
Cosmos Nicolaoucc5a4a82015-02-07 23:09:28 -0800822 // defer the Cleanup method so that it will be called even if
823 // t.Fatalf/FailNow etc are called and can print out useful information.
824 defer i.Cleanup()
Cosmos Nicolaoud21f6b12015-02-07 11:40:03 -0800825 fn(i)
Jiri Simsae63e07d2014-12-04 11:12:08 -0800826}
Cosmos Nicolaoud21f6b12015-02-07 11:40:03 -0800827
Cosmos Nicolaou4a77c192015-02-08 15:29:18 -0800828// RunRootMT builds and runs a root mount table instance. It populates
829// the NAMESPACE_ROOT variable in the test environment so that all subsequent
830// invocations will access this root mount table.
Cosmos Nicolaou01007a02015-02-11 15:38:38 -0800831func RunRootMT(i *T, args ...string) (*Binary, *Invocation) {
832 b := i.BuildGoPkg("v.io/core/veyron/services/mounttable/mounttabled")
Cosmos Nicolaoud3b1cb12015-02-20 00:06:33 -0800833 inv := b.start(1, args...)
Cosmos Nicolaou01007a02015-02-11 15:38:38 -0800834 name := inv.ExpectVar("NAME")
835 inv.Environment().SetVar("NAMESPACE_ROOT", name)
Cosmos Nicolaou82d00d82015-02-10 21:31:00 -0800836 vlog.Infof("Running root mount table: %q", name)
Cosmos Nicolaou01007a02015-02-11 15:38:38 -0800837 return b, inv
Cosmos Nicolaoud21f6b12015-02-07 11:40:03 -0800838}
839
Cosmos Nicolaou93dd88b2015-02-19 15:10:53 -0800840// UseSharedBinDir ensures that a shared directory is used for binaries
Cosmos Nicolaou82d00d82015-02-10 21:31:00 -0800841// across multiple instances of the test environment. This is achieved
842// by setting the V23_BIN_DIR environment variable if it is not already
843// set in the test processes environment (as will typically be the case when
844// these tests are run from the v23 tool). It is intended to be called
845// from TestMain.
846func UseSharedBinDir() func() {
847 if v23BinDir := os.Getenv("V23_BIN_DIR"); len(v23BinDir) == 0 {
848 v23BinDir, err := ioutil.TempDir("", "bin-")
849 if err == nil {
850 vlog.Infof("Setting V23_BIN_DIR to %q", v23BinDir)
851 os.Setenv("V23_BIN_DIR", v23BinDir)
852 return func() { os.RemoveAll(v23BinDir) }
853 }
854 } else {
855 vlog.Infof("Using V23_BIN_DIR %q", v23BinDir)
856 }
857 return func() {}
858}