blob: ec770e3afb05c26a67b755cb7460ae7255b633c6 [file] [log] [blame]
// 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
}