| // Copyright 2015 The Vanadium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package v23tests |
| |
| import ( |
| "bytes" |
| "io" |
| "os" |
| "strings" |
| |
| "v.io/x/lib/vlog" |
| "v.io/x/ref/test/modules" |
| ) |
| |
| // Binary represents an executable program that will be executed during a |
| // test. A binary may be invoked multiple times by calling Start, each call |
| // will return a new Invocation. |
| // |
| // Binary instances are typically obtained from a T by calling BuldV23Pkg, |
| // BuildGoPkg (for Vanadium and other Go binaries) or BinaryFromPath (to |
| // start binaries that are already present on the system). |
| type Binary struct { |
| // The environment to which this binary belongs. |
| env *T |
| |
| // The path to the binary. |
| path string |
| |
| // StartOpts |
| opts modules.StartOpts |
| |
| // Environment variables that will be used when creating invocations |
| // via Start. |
| envVars []string |
| |
| // Optional prefix arguments are added to each invocation. |
| prefixArgs []string |
| } |
| |
| // StartOpts returns the current the StartOpts |
| func (b *Binary) StartOpts() modules.StartOpts { |
| return b.opts |
| } |
| |
| // Path returns the path to the binary. |
| func (b *Binary) Path() string { |
| return b.path |
| } |
| |
| // Start starts the given binary with the given arguments. |
| func (b *Binary) Start(args ...string) *Invocation { |
| return b.start(1, args...) |
| } |
| |
| func (b *Binary) start(skip int, oargs ...string) *Invocation { |
| args := make([]string, len(b.prefixArgs), len(oargs)+len(b.prefixArgs)) |
| copy(args, b.prefixArgs) |
| args = append(args, oargs...) |
| vlog.Infof("%s: starting %s %s", Caller(skip+1), b.Path(), strings.Join(args, " ")) |
| opts := b.opts |
| if opts.ExecProtocol && opts.Credentials == nil { |
| opts.Credentials, opts.Error = b.env.shell.NewChildCredentials("child") |
| } |
| opts.ExpectTesting = b.env.TB |
| handle, err := b.env.shell.StartWithOpts(opts, b.envVars, modules.Program(b.Path()), args...) |
| if err != nil { |
| if handle != nil { |
| vlog.Infof("%s: start failed", Caller(skip+1)) |
| handle.Shutdown(nil, os.Stderr) |
| } |
| // TODO(cnicolaou): calling Fatalf etc from a goroutine often leads |
| // to deadlock. Need to make sure that we handle this here. Maybe |
| // it's best to just return an error? Or provide a StartWithError |
| // call for use from goroutines. |
| b.env.Fatalf("%s: StartWithOpts(%v, %v) failed: %v", Caller(skip+1), b.Path(), strings.Join(args, ", "), err) |
| } |
| vlog.Infof("started PID %d\n", handle.Pid()) |
| inv := &Invocation{ |
| env: b.env, |
| path: b.path, |
| args: args, |
| shutdownErr: errNotShutdown, |
| Handle: handle, |
| } |
| b.env.appendInvocation(inv) |
| return inv |
| } |
| |
| func (b *Binary) run(args ...string) string { |
| inv := b.start(2, args...) |
| var stdout, stderr bytes.Buffer |
| err := inv.Wait(&stdout, &stderr) |
| if err != nil { |
| a := strings.Join(args, ", ") |
| b.env.Fatalf("%s: Run(%s): failed: %v: \n%s\n", Caller(2), a, err, stderr.String()) |
| } |
| return strings.TrimRight(stdout.String(), "\n") |
| } |
| |
| // Run runs the binary with the specified arguments to completion. On |
| // success it returns the contents of stdout, on failure it terminates the |
| // test with an error message containing the error and the contents of |
| // stderr. |
| func (b *Binary) Run(args ...string) string { |
| return b.run(args...) |
| } |
| |
| // WithStdin returns a copy of this binary that, when Start is called, |
| // will read its input from the given reader. Once the reader returns |
| // EOF, the returned invocation's standard input will be closed (see |
| // Invocation.CloseStdin). |
| func (b *Binary) WithStdin(r io.Reader) *Binary { |
| opts := b.opts |
| opts.Stdin = r |
| return b.WithStartOpts(opts) |
| } |
| |
| // WithEnv returns a copy of this binary that, when Start is called, will use |
| // the given environment variables. Each environment variable should be |
| // in "key=value" form. For example: |
| // |
| // bin.WithEnv("EXAMPLE_ENV=/tmp/something").Start(...) |
| func (b *Binary) WithEnv(env ...string) *Binary { |
| newBin := *b |
| newBin.envVars = env |
| return &newBin |
| } |
| |
| // WithStartOpts eturns a copy of this binary that, when Start is called, will |
| // use the given StartOpts. |
| // |
| // bin.WithStartOpts(opts).Start(...) |
| // or |
| // bin.WithStartOpts().Run(...) |
| func (b *Binary) WithStartOpts(opts modules.StartOpts) *Binary { |
| newBin := *b |
| newBin.opts = opts |
| return &newBin |
| } |
| |
| // WithPrefixArgs returns a copy of this binary that, when Start or Run |
| // is called, will use the given additional arguments. For example: given |
| // a Binary b built from "git", then b2 := WithPrefixArgs("checkout") |
| // will let one run git checkout a; git checkout b with b2.Run("a"), |
| // b2.Run("b"). |
| func (b *Binary) WithPrefixArgs(prefixArgs ...string) *Binary { |
| newBin := *b |
| newBin.prefixArgs = prefixArgs |
| return &newBin |
| } |