Merge "v.io/core: another step in eliminating verror1, and making verror2.E be a struct"
diff --git a/lib/testutil/init.go b/lib/testutil/init.go
index d451c27..9dd4630 100644
--- a/lib/testutil/init.go
+++ b/lib/testutil/init.go
@@ -56,12 +56,15 @@
var Rand *Random
var once sync.Once
-var RunIntegrationTests bool
+var IntegrationTestsEnabled bool
+var IntegrationTestsDebugShellOnError bool
const IntegrationTestsFlag = "v23.tests"
+const IntegrationTestsDebugShellOnErrorFlag = "v23.tests.shell-on-fail"
func init() {
- flag.BoolVar(&RunIntegrationTests, IntegrationTestsFlag, false, "Run integration tests.")
+ flag.BoolVar(&IntegrationTestsEnabled, IntegrationTestsFlag, false, "Run integration tests.")
+ flag.BoolVar(&IntegrationTestsDebugShellOnError, IntegrationTestsDebugShellOnErrorFlag, false, "Drop into a debug shell if an integration test fails.")
}
// Init sets up state for running tests: Adjusting GOMAXPROCS,
diff --git a/lib/testutil/integration/testdata/integration_test.go b/lib/testutil/integration/testdata/integration_test.go
deleted file mode 100644
index eadb7be..0000000
--- a/lib/testutil/integration/testdata/integration_test.go
+++ /dev/null
@@ -1,32 +0,0 @@
-package testdata
-
-import (
- "bufio"
- "bytes"
- "testing"
-
- "v.io/core/veyron/lib/modules"
- "v.io/core/veyron/lib/testutil/integration"
- _ "v.io/core/veyron/profiles"
-)
-
-func TestHelperProcess(t *testing.T) {
- modules.DispatchInTest()
-}
-
-func TestBinaryFromPath(t *testing.T) {
- env := integration.New(t)
- defer env.Cleanup()
-
- bash := env.BinaryFromPath("/bin/bash")
- if want, got := "hello world\n", bash.Start("-c", "echo hello world").Output(); want != got {
- t.Fatalf("unexpected output, want %s, got %s", want, got)
- }
-
- inv := bash.Start("-c", "echo hello world 1>&2")
- var buf bytes.Buffer
- inv.WaitOrDie(nil, bufio.NewWriter(&buf))
- if want, got := "hello world\n", string(buf.Bytes()); want != got {
- t.Fatalf("unexpected output, want %s, got %s", want, got)
- }
-}
diff --git a/lib/testutil/integration/util.go b/lib/testutil/integration/util.go
deleted file mode 100644
index 906dc3e..0000000
--- a/lib/testutil/integration/util.go
+++ /dev/null
@@ -1,546 +0,0 @@
-// This package provides support for writing end-to-end style tests. The
-// TestEnvironment type is the root of the API, you can use this type to set up
-// your test environment and to perform operations within the environment. To
-// create a new test environment, use the NewTestEnvironment method, e.g.
-//
-// func TestFoo(t *testing.T) {
-// env := integration.NewTestEnvironment(t)
-// defer env.Cleanup()
-//
-// ...
-// }
-//
-// The methods in this API typically do not return error in the case of
-// failure. Instead, the current test will fail with an appropriate error
-// message. This alleviates the need to handle errors in the test itself.
-//
-// End-to-end style tests may involve several communicating processes. These
-// kinds of tests can be hard to debug using Go alone. The TestEnvironment
-// interface provides a DebugShell() to assist in test debugging. This method
-// will pause the current test and spawn a new shell that can be used to
-// manually inspect and interact with the test environment.
-package integration
-
-import (
- "bytes"
- "fmt"
- "io"
- "io/ioutil"
- "os"
- "os/exec"
- "path"
- "path/filepath"
- "runtime"
- "strings"
- "syscall"
- "time"
-
- "v.io/core/veyron/lib/expect"
- "v.io/core/veyron/lib/modules"
- "v.io/core/veyron/lib/modules/core"
- "v.io/core/veyron/lib/testutil"
-
- tsecurity "v.io/core/veyron/lib/testutil/security"
- "v.io/core/veyron2"
- "v.io/core/veyron2/security"
-)
-
-// Test represents the currently running test. In a local end-to-end test
-// environment obtained though New, this interface will be
-// implemented by Go's standard testing.T.
-//
-// We are planning to implement a regression testing environment that does not
-// depend on Go's testing framework. In this case, users of this interface will
-// ideally not have to change their code to run on the new environment.
-type Test interface {
- Error(args ...interface{})
- Errorf(format string, args ...interface{})
- Fail()
- FailNow()
- Failed() bool
- Fatal(args ...interface{})
- Fatalf(format string, args ...interface{})
- Log(args ...interface{})
- Logf(format string, args ...interface{})
- Skip(args ...interface{})
- SkipNow()
- Skipf(format string, args ...interface{})
- Skipped() bool
-}
-
-// T represents a test environment. Typically, an end-to-end
-// test will begin with:
-// func TestFoo(t *testing.T) {
-// env := integration.New(t)
-// defer env.Cleanup()
-//
-// ...
-// }
-type T interface {
- Test
-
- // Cleanup cleans up the environment and deletes all its artifacts.
- Cleanup()
-
- // BinaryFromPath returns a new TestBinary that, when started, will
- // execute the executable or script at the given path.
- //
- // E.g. env.BinaryFromPath("/bin/bash").Start("-c", "echo hello world").Output() -> "hello world"
- BinaryFromPath(path string) TestBinary
-
- // BuildGoPkg expects a Go package path that identifies a "main"
- // package and returns a TestBinary representing the newly built
- // binary.
- BuildGoPkg(path string) TestBinary
-
- // RootMT returns the endpoint to the root mounttable for this test
- // environment.
- RootMT() string
-
- // Principal returns the security principal of this environment.
- Principal() security.Principal
-
- // DebugShell drops the user into a debug shell. If there is no
- // controlling TTY, DebugShell will emit a warning message and take no
- // futher action.
- DebugShell()
-
- // TempFile creates a temporary file. Temporary files will be deleted
- // by Cleanup.
- TempFile() *os.File
-
- // TempDir creates a temporary directory. Temporary directories and
- // their contents will be deleted by Cleanup.
- TempDir() string
-}
-
-type TestBinary interface {
- // Start starts the given binary with the given arguments.
- Start(args ...string) Invocation
-
- // Path returns the path to the binary.
- Path() string
-
- // Returns a copy of this binary that, when Start is called, will use
- // the given environment variables.
- WithEnv(env []string) TestBinary
-}
-
-// Invocation represents a single invocation of a TestBinary.
-//
-// Any bytes written by the invocation to its standard error may be recovered
-// using the Wait or WaitOrDie functions.
-//
-// For example:
-// bin := env.BinaryFromPath("/bin/bash")
-// inv := bin.Start("-c", "echo hello world 1>&2")
-// var stderr bytes.Buffer
-// inv.WaitOrDie(nil, &stderr)
-// // stderr.Bytes() now contains 'hello world\n'
-type Invocation interface {
- Stdin() io.Writer
- Stdout() io.Reader
-
- // Output reads the invocation's stdout until EOF and then returns what
- // was read as a string.
- Output() string
-
- // Sends the given signal to this invocation. It is up to the test
- // author to decide whether failure to deliver the signal is fatal to
- // the test.
- Kill(syscall.Signal) error
-
- // Wait waits for this invocation to finish. If either stdout or stderr
- // is non-nil, any remaining unread output from those sources will be
- // written to the corresponding writer. The returned error represents
- // the exit status of the underlying command.
- Wait(stdout, stderr io.Writer) error
-
- // Wait waits for this invocation to finish. If either stdout or stderr
- // is non-nil, any remaining unread output from those sources will be
- // written to the corresponding writer. If the underlying command
- // exited with anything but success (exit status 0), this function will
- // cause the current test to fail.
- WaitOrDie(stdout, stderr io.Writer)
-}
-
-type integrationTestEnvironment struct {
- // The testing framework.
- Test
-
- // The function to shutdown the context used to create the environment.
- shutdown veyron2.Shutdown
-
- // The shell to use to start commands.
- shell *modules.Shell
-
- // The environment's root security principal.
- principal security.Principal
-
- // Maps path to TestBinary.
- builtBinaries map[string]*integrationTestBinary
-
- mtHandle modules.Handle
- mtEndpoint string
-
- tempFiles []*os.File
- tempDirs []string
-}
-
-type integrationTestBinary struct {
- // The environment to which this binary belongs.
- env *integrationTestEnvironment
-
- // The path to the binary.
- path string
-
- // Environment variables that will be used when creating invocations
- // via Start.
- envVars []string
-
- // The cleanup function to run when the binary exits.
- cleanupFunc func()
-}
-
-type integrationTestBinaryInvocation struct {
- // The environment to which this invocation belongs.
- env *integrationTestEnvironment
-
- // The handle to the process that was run when this invocation was started.
- handle *modules.Handle
-}
-
-func (i *integrationTestBinaryInvocation) Stdin() io.Writer {
- return (*i.handle).Stdin()
-}
-
-func (i *integrationTestBinaryInvocation) Stdout() io.Reader {
- return (*i.handle).Stdout()
-}
-
-func (i *integrationTestBinaryInvocation) Kill(sig syscall.Signal) error {
- // TODO(sjr): consider using vexec to manage subprocesses.
- pid := (*i.handle).Pid()
- i.env.Logf("sending signal %v to PID %d", sig, pid)
- err := syscall.Kill(pid, sig)
- (*i.handle).Shutdown(nil, nil)
- return err
-}
-
-func readerToString(t Test, r io.Reader) string {
- buf := bytes.Buffer{}
- _, err := buf.ReadFrom(r)
- if err != nil {
- t.Fatalf("ReadFrom() failed: %v", err)
- }
- return buf.String()
-}
-
-func (i *integrationTestBinaryInvocation) Output() string {
- return readerToString(i.env, i.Stdout())
-}
-
-func (i *integrationTestBinaryInvocation) Wait(stdout, stderr io.Writer) error {
- return (*i.handle).Shutdown(stdout, stderr)
-}
-
-func (i *integrationTestBinaryInvocation) WaitOrDie(stdout, stderr io.Writer) {
- if err := i.Wait(stdout, stderr); err != nil {
- i.env.Fatalf("FATAL: Wait() for pid %d failed: %v", (*i.handle).Pid(), err)
- }
-}
-
-func (b *integrationTestBinary) cleanup() {
- binaryDir := path.Dir(b.path)
- b.env.Logf("cleaning up %s", binaryDir)
- if err := os.RemoveAll(binaryDir); err != nil {
- b.env.Logf("WARNING: RemoveAll(%s) failed (%v)", binaryDir, err)
- }
-}
-
-func (b *integrationTestBinary) Path() string {
- return b.path
-}
-
-func (b *integrationTestBinary) Start(args ...string) Invocation {
- locationString := ""
- if _, file, line, ok := runtime.Caller(1); ok {
- locationString = fmt.Sprintf("(requested at %s:%d) ", filepath.Base(file), line)
- }
- b.env.Logf("%sstarting %s %s", locationString, b.Path(), strings.Join(args, " "))
- handle, err := b.env.shell.StartExternalCommand(b.envVars, append([]string{b.Path()}, args...)...)
- if err != nil {
- b.env.Fatalf("StartExternalCommand(%v, %v) failed: %v", b.Path(), strings.Join(args, ", "), err)
- }
- b.env.Logf("started PID %d\n", handle.Pid())
- return &integrationTestBinaryInvocation{
- env: b.env,
- handle: &handle,
- }
-}
-
-func (b *integrationTestBinary) WithEnv(env []string) TestBinary {
- newBin := *b
- newBin.envVars = env
- return &newBin
-}
-
-func (e *integrationTestEnvironment) RootMT() string {
- return e.mtEndpoint
-}
-
-func (e *integrationTestEnvironment) Principal() security.Principal {
- return e.principal
-}
-
-func (e *integrationTestEnvironment) Cleanup() {
- for _, binary := range e.builtBinaries {
- binary.cleanupFunc()
- }
-
- for _, tempFile := range e.tempFiles {
- e.Logf("cleaning up %s", tempFile.Name())
- if err := tempFile.Close(); err != nil {
- e.Logf("WARNING: Close(%q) failed: %v", tempFile.Name(), err)
- }
- if err := os.RemoveAll(tempFile.Name()); err != nil {
- e.Logf("WARNING: RemoveAll(%q) failed: %v", tempFile.Name(), err)
- }
- }
-
- for _, tempDir := range e.tempDirs {
- e.Logf("cleaning up %s", tempDir)
- if err := os.RemoveAll(tempDir); err != nil {
- e.Logf("WARNING: RemoveAll(%q) failed: %v", tempDir, err)
- }
- }
-
- if err := e.shell.Cleanup(os.Stdout, os.Stderr); err != nil {
- e.Fatalf("WARNING: could not clean up shell (%v)", err)
- }
- e.mtHandle.Shutdown(os.Stdout, os.Stderr)
- e.shutdown()
-}
-
-func writeStringOrDie(t Test, f *os.File, s string) {
- if _, err := f.WriteString(s); err != nil {
- t.Fatalf("Write() failed: %v", err)
- }
-}
-
-func (e *integrationTestEnvironment) DebugShell() {
- // Get the current working directory.
- cwd, err := os.Getwd()
- if err != nil {
- e.Fatalf("Getwd() failed: %v", err)
- }
-
- // Transfer stdin, stdout, and stderr to the new process
- // and also set target directory for the shell to start in.
- dev := "/dev/tty"
- fd, err := syscall.Open(dev, syscall.O_RDWR, 0)
- if err != nil {
- e.Logf("WARNING: Open(%v) failed, was asked to create a debug shell but cannot: %v", dev, err)
- return
- }
- file := os.NewFile(uintptr(fd), dev)
- attr := os.ProcAttr{
- Files: []*os.File{file, file, file},
- Dir: cwd,
- }
-
- // Start up a new shell.
- writeStringOrDie(e, file, ">> Starting a new interactive shell\n")
- writeStringOrDie(e, file, "Hit CTRL-D to resume the test\n")
- if len(e.builtBinaries) > 0 {
- writeStringOrDie(e, file, "Built binaries:\n")
- for _, value := range e.builtBinaries {
- writeStringOrDie(e, file, "\t"+value.Path()+"\n")
- }
- }
- writeStringOrDie(e, file, fmt.Sprintf("Root mounttable endpoint: %s\n", e.RootMT()))
-
- shellPath := "/bin/sh"
- if shellPathFromEnv := os.Getenv("SHELL"); shellPathFromEnv != "" {
- shellPath = shellPathFromEnv
- }
- proc, err := os.StartProcess(shellPath, []string{}, &attr)
- if err != nil {
- e.Fatalf("StartProcess(%q) failed: %v", shellPath, err)
- }
-
- // Wait until user exits the shell
- state, err := proc.Wait()
- if err != nil {
- e.Fatalf("Wait(%v) failed: %v", shellPath, err)
- }
-
- writeStringOrDie(e, file, fmt.Sprintf("<< Exited shell: %s\n", state.String()))
-}
-
-func (e *integrationTestEnvironment) BinaryFromPath(path string) TestBinary {
- return &integrationTestBinary{
- env: e,
- envVars: nil,
- path: path,
- cleanupFunc: func() {},
- }
-}
-
-func (e *integrationTestEnvironment) BuildGoPkg(binary_path string) TestBinary {
- e.Logf("building %s...", binary_path)
- if cached_binary := e.builtBinaries[binary_path]; cached_binary != nil {
- e.Logf("using cached binary for %s at %s.", binary_path, cached_binary.Path())
- return cached_binary
- }
- built_path, cleanup, err := buildPkg(binary_path)
- if err != nil {
- e.Fatalf("buildPkg() failed: %v", err)
- return nil
- }
- output_path := path.Join(built_path, path.Base(binary_path))
- e.Logf("done building %s, written to %s.", binary_path, output_path)
- binary := &integrationTestBinary{
- env: e,
- envVars: nil,
- path: output_path,
- cleanupFunc: cleanup,
- }
- e.builtBinaries[binary_path] = binary
- return binary
-}
-
-func (e *integrationTestEnvironment) TempFile() *os.File {
- f, err := ioutil.TempFile("", "")
- if err != nil {
- e.Fatalf("TempFile() failed: %v", err)
- }
- e.Logf("created temporary file at %s", f.Name())
- e.tempFiles = append(e.tempFiles, f)
- return f
-}
-
-func (e *integrationTestEnvironment) TempDir() string {
- f, err := ioutil.TempDir("", "")
- if err != nil {
- e.Fatalf("TempDir() failed: %v", err)
- }
- e.Logf("created temporary directory at %s", f)
- e.tempDirs = append(e.tempDirs, f)
- return f
-}
-
-// Creates a new local testing environment. A local testing environment has a
-// root mounttable endpoint at RootMT() and a security principle available via
-// Principal().
-//
-// You should clean up the returned environment using the env.Cleanup() method.
-// A typical end-to-end test will begin like:
-//
-// func TestFoo(t *testing.T) {
-// env := integration.NewTestEnvironment(t)
-// defer env.Cleanup()
-//
-// ...
-// }
-func New(t Test) T {
- ctx, shutdown := veyron2.Init()
-
- t.Log("creating root principal")
- principal := tsecurity.NewPrincipal("root")
- ctx, err := veyron2.SetPrincipal(ctx, principal)
- if err != nil {
- t.Fatalf("failed to set principal: %v", err)
- }
-
- shell, err := modules.NewShell(ctx, principal)
- if err != nil {
- t.Fatalf("NewShell() failed: %v", err)
- }
- shell.SetWaitTimeout(5 * time.Minute)
- t.Log("starting root mounttable...")
- mtHandle, mtEndpoint, err := startRootMT(shell)
- if err != nil {
- t.Fatalf("startRootMT() failed: %v", err)
- }
- shell.Forget(mtHandle)
- t.Logf("mounttable available at %s", mtEndpoint)
-
- return &integrationTestEnvironment{
- Test: t,
- principal: principal,
- builtBinaries: make(map[string]*integrationTestBinary),
- shell: shell,
- mtHandle: mtHandle,
- mtEndpoint: mtEndpoint,
- tempFiles: []*os.File{},
- tempDirs: []string{},
- shutdown: shutdown,
- }
-}
-
-// RunTest runs a single Vanadium integration test.
-func RunTest(t Test, fn func(i T)) {
- if !testutil.RunIntegrationTests {
- t.Skip()
- }
- i := New(t)
- fn(i)
- i.Cleanup()
-}
-
-// BuildPkg returns a path to a directory that contains the built binary for
-// the given packages and a function that should be invoked to clean up the
-// build artifacts. Note that the clients of this function should not modify
-// the contents of this directory directly and instead defer to the cleanup
-// function.
-func buildPkg(pkg string) (string, func(), error) {
- // The VEYRON_INTEGRATION_BIN_DIR environment variable can be
- // used to identify a directory that multiple integration
- // tests can use to share binaries. Whoever sets this
- // environment variable is responsible for cleaning up the
- // directory it points to.
- binDir, cleanupFn := os.Getenv("VEYRON_INTEGRATION_BIN_DIR"), func() {}
- if binDir == "" {
- // If the aforementioned environment variable is not
- // set, the given packages are built in a temporary
- // directory, which the cleanup function removes.
- tmpDir, err := ioutil.TempDir("", "")
- if err != nil {
- return "", nil, fmt.Errorf("TempDir() failed: %v", err)
- }
- binDir, cleanupFn = tmpDir, func() { os.RemoveAll(tmpDir) }
- }
- binFile := filepath.Join(binDir, path.Base(pkg))
- if _, err := os.Stat(binFile); err != nil {
- if !os.IsNotExist(err) {
- return "", nil, err
- }
- cmd := exec.Command("v23", "go", "build", "-o", filepath.Join(binDir, path.Base(pkg)), pkg)
- if err := cmd.Run(); err != nil {
- return "", nil, err
- }
- }
- return binDir, cleanupFn, nil
-}
-
-// startRootMT uses the given shell to start a root mount table and
-// returns a handle for the started command along with the object name
-// of the mount table.
-func startRootMT(shell *modules.Shell) (modules.Handle, string, error) {
- handle, err := shell.Start(core.RootMTCommand, nil, "--veyron.tcp.address=127.0.0.1:0")
- if err != nil {
- return nil, "", err
- }
- s := expect.NewSession(nil, handle.Stdout(), 10*time.Second)
- s.ExpectVar("PID")
- if err := s.Error(); err != nil {
- return nil, "", err
- }
- name := s.ExpectVar("MT_NAME")
- if err := s.Error(); err != nil {
- return nil, "", err
- }
-
- return handle, name, nil
-}
diff --git a/lib/testutil/v23tests/v23tests.go b/lib/testutil/v23tests/v23tests.go
new file mode 100644
index 0000000..b85afb6
--- /dev/null
+++ b/lib/testutil/v23tests/v23tests.go
@@ -0,0 +1,736 @@
+// Package v23tests provides support for writing end-to-end style integration
+// tests. In particular, support is provided for building binaries, running
+// processes, making assertions about their output/state and ensuring that
+// no processes or files are left behind on exit. Since such tests are often
+// difficult to debug facilities are provided to help do so.
+//
+// The preferred usage of this integration test framework is via the v23
+// tool which generates supporting code. The primary reason for doing so is
+// to cleanly separate integration tests, which can be very expensive to run,
+// from normal unit tests which are intended to be fast and used constantly.
+// However, it still beneficial to be able to always compile the integration
+// test code with the normal test code, just not to run it. Similarly, it
+// is beneficial to share as much of the existing go test infrastructure as
+// possible, so the generated code uses a flag and a naming convention to
+// separate the tests. Integration tests may be run in addition to unit tests
+// by supplying the --v23.tests flag; the -run flag can be used
+// to avoid running unit tests by specifying a prefix of TestV23 since
+// the generate test functions always. Thus:
+//
+// v23 go test -v <pkgs> --v23.test // runs both unit and integration tests
+// v23 go test -v -run=TestV23 <pkgs> --v23.test // runs just integration tests
+//
+// The go generate mechanism is used to generate the test code, thus the
+// comment:
+//
+// //go:generate v23 integration generate
+//
+// will generate the files v23_test.go and internal_v23_test.go for the
+// package in which it occurs. Run v23 integration generate help for full
+// details and options. In short, any function in an external
+// (i.e. <pgk>_test) test package of the following form:
+//
+// V23Test<x>(t integration.T)
+//
+// will be invoked as integration test if the --v23.tests flag is used.
+//
+// The generated code makes use of the RunTest function, documented below.
+//
+// The test environment is implemented by an instance of the interface T.
+// It is constructed with an instance of another interface Test, which is
+// generally implemented by testing.T. Thus, the integration test environment
+// directly as follows:
+//
+// func TestFoo(t *testing.T) {
+// env := v23tests.New(t)
+// defer env.Cleanup()
+//
+// ...
+// }
+//
+// The methods in this API typically do not return error in the case of
+// failure. Instead, the current test will fail with an appropriate error
+// message. This avoids the need to handle errors inline the test itself.
+//
+// The test environment manages all built packages, subprocesses and a
+// set of environment variables that are passed to subprocesses.
+//
+// Debugging is supported as follows:
+// 1. The DebugShell method creates an interative shell at that point in
+// the tests execution that has access to all of the running processes
+// and environment of those processes. The developer can interact with
+// those processes to determine the state of the test.
+// 2. Calls to methods on Test (e.g. FailNow, Fatalf) that fail the test
+// cause the Cleanup method to print out the status of all invocations.
+// 3. Similarly, if the --v23.tests.shell-on-error flag is set then the
+// cleanup method will invoke a DebugShell on a test failure allowing
+// the developer to inspect the state of the test.
+// 4. The implementation of this package uses filenames that start with v23test
+// to allow for easy tracing with --vmodule=v23test*=2 for example.
+//
+package v23tests
+
+import (
+ "bytes"
+ "container/list"
+ "errors"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "os"
+ "os/exec"
+ "path"
+ "path/filepath"
+ "strings"
+ "syscall"
+ "time"
+
+ "v.io/core/veyron2"
+ "v.io/core/veyron2/security"
+ "v.io/core/veyron2/vlog"
+
+ "v.io/core/veyron/lib/expect"
+ "v.io/core/veyron/lib/modules"
+ "v.io/core/veyron/lib/testutil"
+ tsecurity "v.io/core/veyron/lib/testutil/security"
+)
+
+// TODO(cnicolaou): need to enable running under the agent as per the old shell tests,
+// via the shell_test::enable_agent "$@" mechanism.
+//
+//
+// TODO(sjr,cnicolaou): caching of binaries is per test environment -
+// it should be in a file system somewhere and should handle all tests run
+// from a single invocation of v23.
+//
+// TODO(sjr): make this thread safe.
+//
+// TODO(sjr): document all of the methods.
+//
+// TODO(sjr): we need I/O redirection and piping. This is one of the
+// conveniences of the shell based tests that we've lost here. There's
+// current no way to redirect I/O and it won't always be possible
+// to change the command to take a command line arg. Similarly,
+// we should provide support for piping output from one command to
+// to another, or for creating process pipelines directly.
+//
+// TODO(sjr): need more testing of this core package, especially wrt to
+// process cleanup, making sure debug output is captured correctly, etc.
+//
+// TODO(sjr): provide a utility function to retry an operation for a specific
+// time before failing. Useful for synchronising across process startups etc.
+//
+// Test represents the currently running test. In a local end-to-end test
+// environment obtained though New, this interface will be
+// typically be implemented by Go's standard testing.T.
+type Test interface {
+ Error(args ...interface{})
+ Errorf(format string, args ...interface{})
+ Fail()
+ FailNow()
+ Failed() bool
+ Fatal(args ...interface{})
+ Fatalf(format string, args ...interface{})
+ Log(args ...interface{})
+ Logf(format string, args ...interface{})
+ Skip(args ...interface{})
+ SkipNow()
+ Skipf(format string, args ...interface{})
+ Skipped() bool
+}
+
+// T represents an integration test environment.
+type T interface {
+ Test
+
+ // Cleanup cleans up the environment, deletes all its artifacts and
+ // kills all subprocesses. It will kill subprocesses in LIFO order.
+ // Cleanup checks to see if the test has failed and logs information
+ // as to the state of the processes it was asked to invoke up to that
+ // point and optionally, if the --v23.tests.shell-on-fail flag is set
+ // then it will run a debug shell before cleaning up its state.
+ Cleanup()
+
+ // BinaryFromPath returns a new TestBinary that, when started, will
+ // execute the executable or script at the given path.
+ //
+ // E.g. env.BinaryFromPath("/bin/bash").Start("-c", "echo hello world").Output() -> "hello world"
+ BinaryFromPath(path string) TestBinary
+
+ // BuildGoPkg expects a Go package path that identifies a "main"
+ // package and returns a TestBinary representing the newly built
+ // binary.
+ BuildGoPkg(path string) TestBinary
+
+ // Principal returns the security principal of this environment.
+ Principal() security.Principal
+
+ // DebugShell drops the user into a debug shell. If there is no
+ // controlling TTY, DebugShell will emit a warning message and take no
+ // futher action.
+ DebugShell()
+
+ // TempFile creates a temporary file. Temporary files will be deleted
+ // by Cleanup.
+ TempFile() *os.File
+
+ // TempDir creates a temporary directory. Temporary directories and
+ // their contents will be deleted by Cleanup.
+ TempDir() string
+
+ // SetVar sets the value to be associated with key.
+ SetVar(key, value string)
+
+ // GetVar returns the variable associated with the specified key
+ // and an indication of whether it is defined or not.
+ GetVar(key string) (string, bool)
+
+ // ClearVar removes the speficied variable from the Shell's environment
+ ClearVar(key string)
+}
+
+type TestBinary interface {
+ // Start starts the given binary with the given arguments.
+ Start(args ...string) Invocation
+
+ // Path returns the path to the binary.
+ Path() string
+
+ // Returns a copy of this binary that, when Start is called, will use
+ // the given environment variables.
+ WithEnv(env []string) TestBinary
+}
+
+// Session mirrors veyron/lib/expect is used to allow us to embed all of
+// expect.Session's methods in Invocation below.
+type Session interface {
+ Expect(expected string)
+ Expectf(format string, args ...interface{})
+ ExpectRE(pattern string, n int) [][]string
+ ExpectVar(name string) string
+ ExpectSetRE(expected ...string) [][]string
+ ExpectSetEventuallyRE(expected ...string) [][]string
+ ReadLine() string
+ ExpectEOF() error
+}
+
+// Invocation represents a single invocation of a TestBinary.
+//
+// Any bytes written by the invocation to its standard error may be recovered
+// using the Wait or WaitOrDie functions.
+//
+// For example:
+// bin := env.BinaryFromPath("/bin/bash")
+// inv := bin.Start("-c", "echo hello world 1>&2")
+// var stderr bytes.Buffer
+// inv.WaitOrDie(nil, &stderr)
+// // stderr.Bytes() now contains 'hello world\n'
+type Invocation interface {
+ Session
+
+ Stdin() io.Writer
+ Stdout() io.Reader
+
+ // Output reads the invocation's stdout until EOF and then returns what
+ // was read as a string.
+ Output() string
+
+ // Sends the given signal to this invocation. It is up to the test
+ // author to decide whether failure to deliver the signal is fatal to
+ // the test.
+ Kill(syscall.Signal) error
+
+ // Exists returns true if the invocation still exists.
+ Exists() bool
+
+ // Wait waits for this invocation to finish. If either stdout or stderr
+ // is non-nil, any remaining unread output from those sources will be
+ // written to the corresponding writer. The returned error represents
+ // the exit status of the underlying command.
+ Wait(stdout, stderr io.Writer) error
+
+ // Wait waits for this invocation to finish. If either stdout or stderr
+ // is non-nil, any remaining unread output from those sources will be
+ // written to the corresponding writer. If the underlying command
+ // exited with anything but success (exit status 0), this function will
+ // cause the current test to fail.
+ WaitOrDie(stdout, stderr io.Writer)
+
+ // Environment returns the instance of the test environment that this
+ // invocation was from.
+ Environment() T
+
+ // Path returns the path to the binary that was used for this invocation.
+ Path() string
+}
+
+type testEnvironment struct {
+ // The testing framework.
+ Test
+
+ // The function to shutdown the context used to create the environment.
+ shutdown veyron2.Shutdown
+
+ // The shell to use to start commands.
+ shell *modules.Shell
+
+ // The environment's root security principal.
+ principal security.Principal
+
+ // Maps path to TestBinary.
+ builtBinaries map[string]*testBinary
+
+ tempFiles []*os.File
+ tempDirs []string
+
+ invocations []*testBinaryInvocation
+}
+
+type testBinary struct {
+ // The environment to which this binary belongs.
+ env *testEnvironment
+
+ // The path to the binary.
+ path string
+
+ // Environment variables that will be used when creating invocations
+ // via Start.
+ envVars []string
+
+ // The cleanup function to run when the binary exits.
+ cleanupFunc func()
+}
+
+type testBinaryInvocation struct {
+ // The embedded Session
+ Session
+
+ // The environment to which this invocation belongs.
+ env *testEnvironment
+
+ // The handle to the process that was run when this invocation was started.
+ handle modules.Handle
+
+ // The element representing this invocation in the list of
+ // invocations stored in the environment
+ el *list.Element
+
+ // The path of the binary used for this invocation.
+ path string
+
+ // The args the binary was started with
+ args []string
+
+ // True if the process has been shutdown
+ hasShutdown bool
+
+ // The error, if any, as determined when the invocation was
+ // shutdown. It must be set to a default initial value of
+ // errNotShutdown rather than nil to allow us to distinguish between
+ // a successful shutdown or an error.
+ shutdownErr error
+}
+
+var errNotShutdown = errors.New("has not been shutdown")
+
+func (i *testBinaryInvocation) Stdin() io.Writer {
+ return i.handle.Stdin()
+}
+
+func (i *testBinaryInvocation) Stdout() io.Reader {
+ return i.handle.Stdout()
+}
+
+func (i *testBinaryInvocation) Path() string {
+ return i.path
+}
+
+func (i *testBinaryInvocation) Exists() bool {
+ return syscall.Kill(i.handle.Pid(), 0) == nil
+}
+
+func (i *testBinaryInvocation) Kill(sig syscall.Signal) error {
+ // TODO(sjr): consider using vexec to manage subprocesses.
+ // TODO(sjr): if we use vexec, will want to tee stderr reliably to a file
+ // as well as a stderr stream, maintain an 'enviroment' here.
+ // We'll also want to maintain an environment, as per modules.Shell
+ pid := i.handle.Pid()
+ vlog.VI(1).Infof("sending signal %v to PID %d", sig, pid)
+ return syscall.Kill(pid, sig)
+}
+
+func readerToString(t Test, r io.Reader) string {
+ buf := bytes.Buffer{}
+ _, err := buf.ReadFrom(r)
+ if err != nil {
+ t.Fatalf("ReadFrom() failed: %v", err)
+ }
+ return buf.String()
+}
+
+func (i *testBinaryInvocation) Output() string {
+ return readerToString(i.env, i.Stdout())
+}
+
+func (i *testBinaryInvocation) Wait(stdout, stderr io.Writer) error {
+ err := i.handle.Shutdown(stdout, stderr)
+ i.hasShutdown = true
+ i.shutdownErr = err
+ return err
+}
+
+func (i *testBinaryInvocation) WaitOrDie(stdout, stderr io.Writer) {
+ if err := i.Wait(stdout, stderr); err != nil {
+ i.env.Fatalf("FATAL: Wait() for pid %d failed: %v", i.handle.Pid(), err)
+ }
+}
+
+func (i *testBinaryInvocation) Environment() T {
+ return i.env
+}
+
+func (b *testBinary) cleanup() {
+ binaryDir := path.Dir(b.path)
+ vlog.Infof("cleaning up %s", binaryDir)
+ if err := os.RemoveAll(binaryDir); err != nil {
+ vlog.Infof("WARNING: RemoveAll(%s) failed (%v)", binaryDir, err)
+ }
+}
+
+func (b *testBinary) Path() string {
+ return b.path
+}
+
+func (b *testBinary) Start(args ...string) Invocation {
+ depth := testutil.DepthToExternalCaller()
+ vlog.Infof(testutil.FormatLogLine(depth, "starting %s %s", b.Path(), strings.Join(args, " ")))
+ handle, err := b.env.shell.StartExternalCommand(b.envVars, append([]string{b.Path()}, args...)...)
+ if err != nil {
+ b.env.Fatalf("StartExternalCommand(%v, %v) failed: %v", b.Path(), strings.Join(args, ", "), err)
+ }
+ vlog.Infof("started PID %d\n", handle.Pid())
+ inv := &testBinaryInvocation{
+ env: b.env,
+ handle: handle,
+ path: b.path,
+ args: args,
+ shutdownErr: errNotShutdown,
+ Session: expect.NewSession(b.env, handle.Stdout(), 5*time.Minute),
+ }
+ b.env.appendInvocation(inv)
+ return inv
+}
+
+func (b *testBinary) WithEnv(env []string) TestBinary {
+ newBin := *b
+ newBin.envVars = env
+ return &newBin
+}
+
+func (e *testEnvironment) Principal() security.Principal {
+ return e.principal
+}
+
+func (e *testEnvironment) Cleanup() {
+ if e.Failed() {
+ if testutil.IntegrationTestsDebugShellOnError {
+ e.DebugShell()
+ }
+ // Print out a summary of the invocations and their status.
+ for i, inv := range e.invocations {
+ if inv.hasShutdown && inv.Exists() {
+ m := fmt.Sprintf("%d: %s has been shutdown but still exists: %v", i, inv.path, inv.shutdownErr)
+ e.Log(m)
+ vlog.VI(1).Info(m)
+ vlog.VI(2).Infof("%d: %s %v", i, inv.path, inv.args)
+ continue
+ }
+ m := fmt.Sprintf("%d: %s: shutdown status: %v", i, inv.path, inv.shutdownErr)
+ e.Log(m)
+ vlog.VI(1).Info(m)
+ vlog.VI(2).Infof("%d: %s %v", i, inv.path, inv.args)
+ }
+ }
+
+ vlog.VI(1).Infof("V23Test.Cleanup")
+ // Shut down all processes before attempting to delete any
+ // files/directories to avoid potential 'file system busy' problems
+ // on non-unix systems.
+ for _, inv := range e.invocations {
+ if inv.hasShutdown {
+ vlog.VI(1).Infof("V23Test.Cleanup: %q has been shutdown", inv.Path())
+ continue
+ }
+ vlog.VI(1).Infof("V23Test.Cleanup: Kill: %q", inv.Path())
+ err := inv.Kill(syscall.SIGTERM)
+ inv.Wait(os.Stdout, os.Stderr)
+ vlog.VI(1).Infof("V23Test.Cleanup: Killed: %q: %v", inv.Path(), err)
+ }
+ vlog.VI(1).Infof("V23Test.Cleanup: all invocations taken care of.")
+
+ if err := e.shell.Cleanup(os.Stdout, os.Stderr); err != nil {
+ e.Fatalf("WARNING: could not clean up shell (%v)", err)
+ }
+
+ vlog.VI(1).Infof("V23Test.Cleanup: cleaning up binaries & files")
+
+ for _, binary := range e.builtBinaries {
+ binary.cleanupFunc()
+ }
+
+ for _, tempFile := range e.tempFiles {
+ vlog.VI(1).Infof("V23Test.Cleanup: cleaning up %s", tempFile.Name())
+ if err := tempFile.Close(); err != nil {
+ vlog.Errorf("WARNING: Close(%q) failed: %v", tempFile.Name(), err)
+ }
+ if err := os.RemoveAll(tempFile.Name()); err != nil {
+ vlog.Errorf("WARNING: RemoveAll(%q) failed: %v", tempFile.Name(), err)
+ }
+ }
+
+ for _, tempDir := range e.tempDirs {
+ vlog.VI(1).Infof("V23Test.Cleanup: cleaning up %s", tempDir)
+ if err := os.RemoveAll(tempDir); err != nil {
+ vlog.Errorf("WARNING: RemoveAll(%q) failed: %v", tempDir, err)
+ }
+ }
+
+ // shutdown the runtime
+ e.shutdown()
+}
+
+func (e *testEnvironment) GetVar(key string) (string, bool) {
+ return e.shell.GetVar(key)
+}
+
+func (e *testEnvironment) SetVar(key, value string) {
+ e.shell.SetVar(key, value)
+}
+
+func (e *testEnvironment) ClearVar(key string) {
+ e.shell.ClearVar(key)
+}
+
+func writeStringOrDie(t Test, f *os.File, s string) {
+ if _, err := f.WriteString(s); err != nil {
+ t.Fatalf("Write() failed: %v", err)
+ }
+}
+
+func (e *testEnvironment) DebugShell() {
+ // Get the current working directory.
+ cwd, err := os.Getwd()
+ if err != nil {
+ e.Fatalf("Getwd() failed: %v", err)
+ }
+
+ // Transfer stdin, stdout, and stderr to the new process
+ // and also set target directory for the shell to start in.
+ dev := "/dev/tty"
+ fd, err := syscall.Open(dev, syscall.O_RDWR, 0)
+ if err != nil {
+ vlog.Errorf("WARNING: Open(%v) failed, was asked to create a debug shell but cannot: %v", dev, err)
+ return
+ }
+ file := os.NewFile(uintptr(fd), dev)
+ attr := os.ProcAttr{
+ Files: []*os.File{file, file, file},
+ Dir: cwd,
+ }
+
+ // Set up environment for Child.
+ if ns, ok := e.GetVar("NAMESPACE_ROOT"); ok {
+ attr.Env = append(attr.Env, "NAMESPACE_ROOT="+ns)
+ }
+ // TODO(sjr): talk to Ankur about how to do this properly/safely
+ // using either the agent, or a file descriptor inherited by the shell
+ // and its children. This is preferable since it avoids compiling and
+ // running the agent which can be an overhead in tests.
+ dir, _ := tsecurity.ForkCredentials(e.principal, "debugShell")
+ attr.Env = append(attr.Env, "VEYRON_CRED="+dir)
+
+ // Start up a new shell.
+ writeStringOrDie(e, file, ">> Starting a new interactive shell\n")
+ writeStringOrDie(e, file, "Hit CTRL-D to resume the test\n")
+ if len(e.builtBinaries) > 0 {
+ writeStringOrDie(e, file, "Built binaries:\n")
+ for _, value := range e.builtBinaries {
+ writeStringOrDie(e, file, "\t"+value.Path()+"\n")
+ }
+ }
+
+ shellPath := "/bin/sh"
+ if shellPathFromEnv := os.Getenv("SHELL"); shellPathFromEnv != "" {
+ shellPath = shellPathFromEnv
+ }
+ proc, err := os.StartProcess(shellPath, []string{}, &attr)
+ if err != nil {
+ e.Fatalf("StartProcess(%q) failed: %v", shellPath, err)
+ }
+
+ // Wait until user exits the shell
+ state, err := proc.Wait()
+ if err != nil {
+ e.Fatalf("Wait(%v) failed: %v", shellPath, err)
+ }
+
+ writeStringOrDie(e, file, fmt.Sprintf("<< Exited shell: %s\n", state.String()))
+}
+
+func (e *testEnvironment) BinaryFromPath(path string) TestBinary {
+ return &testBinary{
+ env: e,
+ envVars: nil,
+ path: path,
+ cleanupFunc: func() {},
+ }
+}
+
+func (e *testEnvironment) BuildGoPkg(binary_path string) TestBinary {
+ vlog.Infof("building %s...", binary_path)
+ if cached_binary := e.builtBinaries[binary_path]; cached_binary != nil {
+ vlog.Infof("using cached binary for %s at %s.", binary_path, cached_binary.Path())
+ return cached_binary
+ }
+ built_path, cleanup, err := buildPkg(binary_path)
+ if err != nil {
+ e.Fatalf("buildPkg() failed: %v", err)
+ return nil
+ }
+ output_path := path.Join(built_path, path.Base(binary_path))
+ vlog.Infof("done building %s, written to %s.", binary_path, output_path)
+ binary := &testBinary{
+ env: e,
+ envVars: nil,
+ path: output_path,
+ cleanupFunc: cleanup,
+ }
+ e.builtBinaries[binary_path] = binary
+ return binary
+}
+
+func (e *testEnvironment) TempFile() *os.File {
+ f, err := ioutil.TempFile("", "")
+ if err != nil {
+ e.Fatalf("TempFile() failed: %v", err)
+ }
+ vlog.Infof("created temporary file at %s", f.Name())
+ e.tempFiles = append(e.tempFiles, f)
+ return f
+}
+
+func (e *testEnvironment) TempDir() string {
+ f, err := ioutil.TempDir("", "")
+ if err != nil {
+ e.Fatalf("TempDir() failed: %v", err)
+ }
+ vlog.Infof("created temporary directory at %s", f)
+ e.tempDirs = append(e.tempDirs, f)
+ return f
+}
+
+func (e *testEnvironment) appendInvocation(inv *testBinaryInvocation) {
+ e.invocations = append(e.invocations, inv)
+}
+
+// Creates a new local testing environment. A local testing environment has a
+// a security principle available via Principal().
+//
+// You should clean up the returned environment using the env.Cleanup() method.
+// A typical end-to-end test will begin like:
+//
+// func TestFoo(t *testing.T) {
+// env := integration.NewTestEnvironment(t)
+// defer env.Cleanup()
+//
+// ...
+// }
+func New(t Test) T {
+ ctx, shutdown := veyron2.Init()
+
+ vlog.Infof("creating root principal")
+ principal := tsecurity.NewPrincipal("root")
+ ctx, err := veyron2.SetPrincipal(ctx, principal)
+ if err != nil {
+ t.Fatalf("failed to set principal: %v", err)
+ }
+
+ shell, err := modules.NewShell(ctx, principal)
+ if err != nil {
+ t.Fatalf("NewShell() failed: %v", err)
+ }
+ shell.SetStartTimeout(1 * time.Minute)
+ shell.SetWaitTimeout(5 * time.Minute)
+
+ return &testEnvironment{
+ Test: t,
+ principal: principal,
+ builtBinaries: make(map[string]*testBinary),
+ shell: shell,
+ tempFiles: []*os.File{},
+ tempDirs: []string{},
+ shutdown: shutdown,
+ }
+}
+
+// BuildPkg returns a path to a directory that contains the built binary for
+// the given packages and a function that should be invoked to clean up the
+// build artifacts. Note that the clients of this function should not modify
+// the contents of this directory directly and instead defer to the cleanup
+// function.
+func buildPkg(pkg string) (string, func(), error) {
+ // The VEYRON_INTEGRATION_BIN_DIR environment variable can be
+ // used to identify a directory that multiple integration
+ // tests can use to share binaries. Whoever sets this
+ // environment variable is responsible for cleaning up the
+ // directory it points to.
+ binDir, cleanupFn := os.Getenv("VEYRON_INTEGRATION_BIN_DIR"), func() {}
+ if binDir == "" {
+ // If the aforementioned environment variable is not
+ // set, the given packages are built in a temporary
+ // directory, which the cleanup function removes.
+ tmpDir, err := ioutil.TempDir("", "")
+ if err != nil {
+ return "", nil, fmt.Errorf("TempDir() failed: %v", err)
+ }
+ binDir, cleanupFn = tmpDir, func() { os.RemoveAll(tmpDir) }
+ }
+ binFile := filepath.Join(binDir, path.Base(pkg))
+ if _, err := os.Stat(binFile); err != nil {
+ if !os.IsNotExist(err) {
+ return "", nil, err
+ }
+ cmd := exec.Command("v23", "go", "build", "-o", filepath.Join(binDir, path.Base(pkg)), pkg)
+ if err := cmd.Run(); err != nil {
+ return "", nil, err
+ }
+ }
+ return binDir, cleanupFn, nil
+}
+
+// RunTest runs a single Vanadium 'v23 style' integration test.
+func RunTest(t Test, fn func(i T)) {
+ if !testutil.IntegrationTestsEnabled {
+ t.Skip()
+ }
+ i := New(t)
+ // defer the Cleanup method so that it will be called even if
+ // t.Fatalf/FailNow etc are called and can print out useful information.
+ defer i.Cleanup()
+ fn(i)
+}
+
+// RunRootMT builds and runs a root mount table instance. It populates
+// the NAMESPACE_ROOT variable in the test environment so that all subsequent
+// invocations will access this root mount table.
+func RunRootMT(t T, args ...string) (TestBinary, Invocation) {
+ b := t.BuildGoPkg("v.io/core/veyron/services/mounttable/mounttabled")
+ i := b.Start(args...)
+ s := expect.NewSession(t, i.Stdout(), time.Minute)
+ name := s.ExpectVar("NAME")
+ i.Environment().SetVar("NAMESPACE_ROOT", name)
+ return b, i
+}
+
+// TODO(sjr): provided convenience wrapper for dealing with credentials if
+// necessary.
diff --git a/lib/testutil/v23tests/v23tests_test.go b/lib/testutil/v23tests/v23tests_test.go
new file mode 100644
index 0000000..8c0a3d8
--- /dev/null
+++ b/lib/testutil/v23tests/v23tests_test.go
@@ -0,0 +1,139 @@
+package v23tests_test
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+ "regexp"
+ "strings"
+ "syscall"
+ "testing"
+ "time"
+
+ "v.io/core/veyron2/naming"
+ "v.io/core/veyron2/vlog"
+
+ "v.io/core/veyron/lib/expect"
+ "v.io/core/veyron/lib/modules"
+ "v.io/core/veyron/lib/testutil"
+ "v.io/core/veyron/lib/testutil/v23tests"
+ _ "v.io/core/veyron/profiles"
+)
+
+// TODO(sjr): add more unit tests, especially for errors cases.
+// TODO(sjr): need to make sure processes don't get left lying around.
+
+func TestBinaryFromPath(t *testing.T) {
+ env := v23tests.New(t)
+ defer env.Cleanup()
+
+ bash := env.BinaryFromPath("/bin/bash")
+ if want, got := "hello world\n", bash.Start("-c", "echo hello world").Output(); want != got {
+ t.Fatalf("unexpected output, want %s, got %s", want, got)
+ }
+
+ inv := bash.Start("-c", "echo hello world")
+ var buf bytes.Buffer
+ inv.WaitOrDie(&buf, nil)
+ if want, got := "hello world\n", buf.String(); want != got {
+ t.Fatalf("unexpected output, want %s, got %s", want, got)
+ }
+}
+
+func TestMountTable(t *testing.T) {
+ env := v23tests.New(t)
+ defer env.Cleanup()
+
+ v23tests.RunRootMT(env, "--veyron.tcp.address=127.0.0.1:0")
+ proxyBin := env.BuildGoPkg("v.io/core/veyron/services/proxy/proxyd")
+ nsBin := env.BuildGoPkg("v.io/core/veyron/tools/namespace")
+
+ mt, ok := env.GetVar("NAMESPACE_ROOT")
+ if !ok || len(mt) == 0 {
+ t.Fatalf("expected a mount table name")
+ }
+
+ proxy := proxyBin.Start("--address=127.0.0.1:0", "-name=proxyd")
+ proxyName := proxy.ExpectVar("NAME")
+ proxyAddress, _ := naming.SplitAddressName(proxyName)
+
+ re := regexp.MustCompile("proxyd (.*) \\(.*")
+ for i := 0; i < 10; i++ {
+ time.Sleep(100 * time.Millisecond)
+ inv := nsBin.Start("glob", "...")
+ parts := re.FindStringSubmatch(inv.ReadLine())
+ if len(parts) == 2 {
+ if got, want := proxyAddress, parts[1]; got != want {
+ t.Fatalf("got: %v, want: %v", got, want)
+ } else {
+ break
+ }
+ }
+ }
+ if got, want := proxy.Exists(), true; got != want {
+ t.Fatalf("got: %v, want: %v", got, want)
+ }
+}
+
+// The next set of tests are a complicated dance to test the correct
+// detection and logging of failed integration tests. The complication
+// is that we need to run these tests in a child process so that we
+// can examine their output, but not in the parent process. We use the
+// modules framework to do so, with the added twist that we need access
+// to an instance of testing.T which we obtain via a global variable.
+// TODO(cnicolaou): this will need to change once we switch to using
+// TestMain.
+func IntegrationTestInChild(i v23tests.T) {
+ fmt.Println("Hello")
+ sleep := i.BinaryFromPath("/bin/sleep")
+ sleep.Start("3600")
+ s2 := sleep.Start("6400")
+ sleep.Start("21600")
+ s2.Kill(syscall.SIGTERM)
+ s2.Wait(nil, nil)
+ i.FailNow()
+ panic("should never get here")
+}
+
+var globalT *testing.T
+
+func TestHelperProcess(t *testing.T) {
+ globalT = t
+ modules.DispatchInTest()
+}
+
+func RunIntegrationTestInChild(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error {
+ v23tests.RunTest(globalT, IntegrationTestInChild)
+ return nil
+}
+
+func init() {
+ testutil.Init()
+ modules.RegisterChild("RunIntegrationTestInChild", "", RunIntegrationTestInChild)
+}
+
+func TestDeferHandling(t *testing.T) {
+ sh, _ := modules.NewShell(nil, nil)
+ child, err := sh.Start("RunIntegrationTestInChild", nil, "--v23.tests")
+ if err != nil {
+ t.Fatal(err)
+ }
+ s := expect.NewSession(t, child.Stdout(), time.Minute)
+ s.Expect("Hello")
+ s.ExpectRE("--- FAIL: TestHelperProcess", -1)
+ for _, e := range []string{
+ ".* 0: /bin/sleep: shutdown status: has not been shutdown",
+ ".* 1: /bin/sleep: shutdown status: signal: terminated",
+ ".* 2: /bin/sleep: shutdown status: has not been shutdown",
+ } {
+ s.ExpectRE(e, -1)
+ }
+ var stderr bytes.Buffer
+ if err := child.Shutdown(nil, &stderr); err != nil {
+ if !strings.Contains(err.Error(), "exit status 1") {
+ t.Fatal(err)
+ }
+ }
+ vlog.Infof("Child\n=============\n%s", stderr.String())
+ vlog.Infof("-----------------")
+}
diff --git a/services/identity/server/identityd.go b/services/identity/server/identityd.go
index 3416ef8..9ef16cb 100644
--- a/services/identity/server/identityd.go
+++ b/services/identity/server/identityd.go
@@ -186,6 +186,12 @@
dischargerService: services.DischargerServer(discharger.NewDischarger()),
oauthBlesserService: blesser.NewOAuthBlesserServer(blesserParams),
})
+ // Set up the glob invoker.
+ var children []string
+ for k, _ := range d {
+ children = append(children, k)
+ }
+ d[""] = ipc.ChildrenGlobberInvoker(children...)
return d
}
diff --git a/services/mgmt/application/applicationd/testdata/integration_test.go b/services/mgmt/application/applicationd/testdata/integration_test.go
index 6140f73..697b482 100644
--- a/services/mgmt/application/applicationd/testdata/integration_test.go
+++ b/services/mgmt/application/applicationd/testdata/integration_test.go
@@ -4,12 +4,10 @@
"encoding/json"
"os"
"strings"
- "syscall"
"testing"
- "v.io/core/veyron/lib/modules"
- "v.io/core/veyron/lib/testutil/integration"
libsecurity "v.io/core/veyron/lib/testutil/security"
+ "v.io/core/veyron/lib/testutil/v23tests"
_ "v.io/core/veyron/profiles"
vsecurity "v.io/core/veyron/security"
"v.io/core/veyron2/naming"
@@ -21,8 +19,8 @@
"v.io/core/veyron/tools/application",
}
-func helper(t *testing.T, env integration.T, clientBin integration.TestBinary, expectError bool, credentials, cmd string, args ...string) string {
- args = append([]string{"-veyron.credentials=" + credentials, "-veyron.namespace.root=" + env.RootMT(), cmd}, args...)
+func helper(t *testing.T, env v23tests.T, clientBin v23tests.TestBinary, expectError bool, credentials, cmd string, args ...string) string {
+ args = append([]string{"-veyron.credentials=" + credentials, cmd}, args...)
inv := clientBin.Start(args...)
out := inv.Output()
err := inv.Wait(os.Stdout, os.Stderr)
@@ -36,26 +34,24 @@
}
-func matchEnvelope(t *testing.T, env integration.T, clientBin integration.TestBinary, expectError bool, credentials, name, suffix string) string {
+func matchEnvelope(t *testing.T, env v23tests.T, clientBin v23tests.TestBinary, expectError bool, credentials, name, suffix string) string {
return helper(t, env, clientBin, expectError, credentials, "match", naming.Join(name, suffix), "test-profile")
}
-func putEnvelope(t *testing.T, env integration.T, clientBin integration.TestBinary, credentials, name, suffix, envelope string) string {
+func putEnvelope(t *testing.T, env v23tests.T, clientBin v23tests.TestBinary, credentials, name, suffix, envelope string) string {
return helper(t, env, clientBin, false, credentials, "put", naming.Join(name, suffix), "test-profile", envelope)
}
-func removeEnvelope(t *testing.T, env integration.T, clientBin integration.TestBinary, credentials, name, suffix string) string {
+func removeEnvelope(t *testing.T, env v23tests.T, clientBin v23tests.TestBinary, credentials, name, suffix string) string {
return helper(t, env, clientBin, false, credentials, "remove", naming.Join(name, suffix), "test-profile")
}
-func TestHelperProcess(t *testing.T) {
- modules.DispatchInTest()
-}
-
func TestApplicationRepository(t *testing.T) {
- env := integration.New(t)
+ env := v23tests.New(t)
defer env.Cleanup()
+ v23tests.RunRootMT(env, "--veyron.tcp.address=127.0.0.1:0")
+ // TODO(sjr): talk to caprita about the necessity/correctness of these.
// Generate credentials.
serverCred, serverPrin := libsecurity.NewCredentials("server")
defer os.RemoveAll(serverCred)
@@ -68,13 +64,11 @@
args := []string{
"-name=" + appRepoName,
"-store=" + appRepoStore,
+ "-v=2",
"-veyron.tcp.address=127.0.0.1:0",
"-veyron.credentials=" + serverCred,
- "-veyron.namespace.root=" + env.RootMT(),
}
- serverBin := env.BuildGoPkg("v.io/core/veyron/services/mgmt/application/applicationd")
- serverProcess := serverBin.Start(args...)
- defer serverProcess.Kill(syscall.SIGTERM)
+ env.BuildGoPkg("v.io/core/veyron/services/mgmt/application/applicationd").Start(args...)
// Build the client binary.
clientBin := env.BuildGoPkg("v.io/core/veyron/tools/application")
diff --git a/services/mgmt/binary/binaryd/testdata/integration_test.go b/services/mgmt/binary/binaryd/testdata/integration_test.go
index c1f61df..80e39c1 100644
--- a/services/mgmt/binary/binaryd/testdata/integration_test.go
+++ b/services/mgmt/binary/binaryd/testdata/integration_test.go
@@ -8,13 +8,11 @@
"os"
"os/exec"
"strings"
- "syscall"
"testing"
- "v.io/core/veyron/lib/modules"
"v.io/core/veyron/lib/testutil"
- "v.io/core/veyron/lib/testutil/integration"
"v.io/core/veyron/lib/testutil/security"
+ "v.io/core/veyron/lib/testutil/v23tests"
_ "v.io/core/veyron/profiles"
"v.io/core/veyron2/naming"
)
@@ -50,19 +48,17 @@
}
}
-func deleteFile(env integration.T, clientBin integration.TestBinary, credentials, name, suffix string) {
+func deleteFile(env v23tests.T, clientBin v23tests.TestBinary, credentials, name, suffix string) {
deleteArgs := []string{
"-veyron.credentials=" + credentials,
- "-veyron.namespace.root=" + env.RootMT(),
"delete", naming.Join(name, suffix),
}
clientBin.Start(deleteArgs...).WaitOrDie(nil, nil)
}
-func downloadFile(t *testing.T, env integration.T, clientBin integration.TestBinary, expectError bool, credentials, name, path, suffix string) {
+func downloadFile(t *testing.T, env v23tests.T, clientBin v23tests.TestBinary, expectError bool, credentials, name, path, suffix string) {
downloadArgs := []string{
"-veyron.credentials=" + credentials,
- "-veyron.namespace.root=" + env.RootMT(),
"download", naming.Join(name, suffix), path,
}
err := clientBin.Start(downloadArgs...).Wait(os.Stdout, os.Stderr)
@@ -90,31 +86,26 @@
}
}
-func rootURL(t *testing.T, env integration.T, clientBin integration.TestBinary, credentials, name string) string {
+func rootURL(t *testing.T, env v23tests.T, clientBin v23tests.TestBinary, credentials, name string) string {
rootArgs := []string{
"-veyron.credentials=" + credentials,
- "-veyron.namespace.root=" + env.RootMT(),
"url", name,
}
return strings.TrimSpace(clientBin.Start(rootArgs...).Output())
}
-func uploadFile(t *testing.T, env integration.T, clientBin integration.TestBinary, credentials, name, path, suffix string) {
+func uploadFile(t *testing.T, env v23tests.T, clientBin v23tests.TestBinary, credentials, name, path, suffix string) {
uploadArgs := []string{
"-veyron.credentials=" + credentials,
- "-veyron.namespace.root=" + env.RootMT(),
"upload", naming.Join(name, suffix), path,
}
clientBin.Start(uploadArgs...).WaitOrDie(os.Stdout, os.Stderr)
}
-func TestHelperProcess(t *testing.T) {
- modules.DispatchInTest()
-}
-
func TestBinaryRepositoryIntegration(t *testing.T) {
- env := integration.New(t)
+ env := v23tests.New(t)
defer env.Cleanup()
+ v23tests.RunRootMT(env, "--veyron.tcp.address=127.0.0.1:0")
// Build the required binaries.
binaryRepoBin := env.BuildGoPkg("v.io/core/veyron/services/mgmt/binary/binaryd")
@@ -133,11 +124,9 @@
"-http=127.0.0.1:0",
"-veyron.tcp.address=127.0.0.1:0",
"-veyron.credentials=" + serverCred,
- "-veyron.namespace.root=" + env.RootMT(),
}
- server := binaryRepoBin.Start(args...)
- defer server.Kill(syscall.SIGTERM)
+ binaryRepoBin.Start(args...)
// Upload a random binary file.
binFile := env.TempFile()
diff --git a/services/mgmt/binary/impl/fs_utils.go b/services/mgmt/binary/impl/fs_utils.go
index 9468fb6..02808c0 100644
--- a/services/mgmt/binary/impl/fs_utils.go
+++ b/services/mgmt/binary/impl/fs_utils.go
@@ -13,9 +13,11 @@
)
const (
- checksum = "checksum"
- data = "data"
- lock = "lock"
+ checksumFileName = "checksum"
+ dataFileName = "data"
+ lockFileName = "lock"
+ nameFileName = "name"
+ mediaInfoFileName = "mediainfo"
)
// checksumExists checks whether the given part path is valid and
@@ -31,7 +33,7 @@
vlog.Errorf("Stat(%v) failed: %v", path, err)
return verror.Make(ErrOperationFailed, nil, path)
}
- checksumFile := filepath.Join(path, checksum)
+ checksumFile := filepath.Join(path, checksumFileName)
_, err := os.Stat(checksumFile)
switch {
case os.IsNotExist(err):
@@ -80,7 +82,7 @@
}
result[idx] = filepath.Join(path, partName)
} else {
- if info.Name() == "name" || info.Name() == "mediainfo" {
+ if info.Name() == nameFileName || info.Name() == mediaInfoFileName {
continue
}
// The only entries should correspond to the part dirs.
@@ -97,7 +99,7 @@
for d := 0; d < i.state.depth; d++ {
pattern = filepath.Join(pattern, "*")
}
- pattern = filepath.Join(pattern, "*", "name")
+ pattern = filepath.Join(pattern, "*", nameFileName)
matches, err := filepath.Glob(pattern)
if err != nil {
return nil
diff --git a/services/mgmt/binary/impl/http.go b/services/mgmt/binary/impl/http.go
index 1b34812..7f672d5 100644
--- a/services/mgmt/binary/impl/http.go
+++ b/services/mgmt/binary/impl/http.go
@@ -39,7 +39,7 @@
if err := checksumExists(part); err != nil {
return nil, err
}
- dataPath := filepath.Join(part, data)
+ dataPath := filepath.Join(part, dataFileName)
var err error
if partFiles[i], err = os.Open(dataPath); err != nil {
vlog.Errorf("Open(%v) failed: %v", dataPath, err)
diff --git a/services/mgmt/binary/impl/service.go b/services/mgmt/binary/impl/service.go
index 718e9e2..9b659da 100644
--- a/services/mgmt/binary/impl/service.go
+++ b/services/mgmt/binary/impl/service.go
@@ -12,6 +12,8 @@
// name
// acls/data
// acls/sig
+// mediainfo
+// name
// <part_1>/checksum
// <part_1>/data
// ...
@@ -115,7 +117,7 @@
vlog.Errorf("TempDir(%v, %v) failed: %v", parent, prefix, err)
return verror.Make(ErrOperationFailed, context.Context())
}
- nameFile := filepath.Join(tmpDir, "name")
+ nameFile := filepath.Join(tmpDir, nameFileName)
if err := ioutil.WriteFile(nameFile, []byte(i.suffix), os.FileMode(0600)); err != nil {
vlog.Errorf("WriteFile(%q) failed: %v", nameFile)
return verror.Make(ErrOperationFailed, context.Context())
@@ -128,7 +130,7 @@
return verror.Make(ErrOperationFailed, context.Context())
}
- infoFile := filepath.Join(tmpDir, "mediainfo")
+ infoFile := filepath.Join(tmpDir, mediaInfoFileName)
jInfo, err := json.Marshal(mediaInfo)
if err != nil {
vlog.Errorf("json.Marshal(%v) failed: %v", mediaInfo, err)
@@ -209,7 +211,7 @@
if err := checksumExists(path); err != nil {
return err
}
- dataPath := filepath.Join(path, data)
+ dataPath := filepath.Join(path, dataFileName)
file, err := os.Open(dataPath)
if err != nil {
vlog.Errorf("Open(%v) failed: %v", dataPath, err)
@@ -250,7 +252,7 @@
return []binary.PartInfo{}, repository.MediaInfo{}, err
}
for _, part := range parts {
- checksumFile := filepath.Join(part, checksum)
+ checksumFile := filepath.Join(part, checksumFileName)
bytes, err := ioutil.ReadFile(checksumFile)
if err != nil {
if os.IsNotExist(err) {
@@ -260,7 +262,7 @@
vlog.Errorf("ReadFile(%v) failed: %v", checksumFile, err)
return []binary.PartInfo{}, repository.MediaInfo{}, verror.Make(ErrOperationFailed, context.Context())
}
- dataFile := filepath.Join(part, data)
+ dataFile := filepath.Join(part, dataFileName)
fi, err := os.Stat(dataFile)
if err != nil {
if os.IsNotExist(err) {
@@ -272,7 +274,7 @@
}
result = append(result, binary.PartInfo{Checksum: string(bytes), Size: fi.Size()})
}
- infoFile := filepath.Join(i.path, "mediainfo")
+ infoFile := filepath.Join(i.path, mediaInfoFileName)
jInfo, err := ioutil.ReadFile(infoFile)
if err != nil {
vlog.Errorf("ReadFile(%q) failed: %v", infoFile)
@@ -296,7 +298,7 @@
return err
}
// Use os.OpenFile() to resolve races.
- lockPath, flags, perm := filepath.Join(path, lock), os.O_CREATE|os.O_WRONLY|os.O_EXCL, os.FileMode(0600)
+ lockPath, flags, perm := filepath.Join(path, lockFileName), os.O_CREATE|os.O_WRONLY|os.O_EXCL, os.FileMode(0600)
lockFile, err := os.OpenFile(lockPath, flags, perm)
if err != nil {
if os.IsExist(err) {
@@ -336,7 +338,7 @@
}
hash := hex.EncodeToString(h.Sum(nil))
- checksumFile, perm := filepath.Join(path, checksum), os.FileMode(0600)
+ checksumFile, perm := filepath.Join(path, checksumFileName), os.FileMode(0600)
if err := ioutil.WriteFile(checksumFile, []byte(hash), perm); err != nil {
vlog.Errorf("WriteFile(%v, %v, %v) failed: %v", checksumFile, hash, perm, err)
if err := os.Remove(file.Name()); err != nil {
@@ -344,7 +346,7 @@
}
return verror.Make(ErrOperationFailed, context.Context())
}
- dataFile := filepath.Join(path, data)
+ dataFile := filepath.Join(path, dataFileName)
if err := os.Rename(file.Name(), dataFile); err != nil {
vlog.Errorf("Rename(%v, %v) failed: %v", file.Name(), dataFile, err)
if err := os.Remove(file.Name()); err != nil {
diff --git a/services/mgmt/build/buildd/testdata/integration_test.go b/services/mgmt/build/buildd/testdata/integration_test.go
index f913b35..96c0e12 100644
--- a/services/mgmt/build/buildd/testdata/integration_test.go
+++ b/services/mgmt/build/buildd/testdata/integration_test.go
@@ -8,12 +8,11 @@
"path/filepath"
"runtime"
"strings"
- "syscall"
"testing"
"v.io/core/veyron/lib/modules"
- "v.io/core/veyron/lib/testutil/integration"
"v.io/core/veyron/lib/testutil/security"
+ "v.io/core/veyron/lib/testutil/v23tests"
_ "v.io/core/veyron/profiles"
)
@@ -29,8 +28,9 @@
}
func TestBuildServerIntegration(t *testing.T) {
- env := integration.New(t)
+ env := v23tests.New(t)
defer env.Cleanup()
+ v23tests.RunRootMT(env, "--veyron.tcp.address=127.0.0.1:0")
// Generate credentials.
serverCred, serverPrin := security.NewCredentials("server")
@@ -50,10 +50,8 @@
"-name=" + buildServerName, "-gobin=" + goBin, "-goroot=" + goRoot,
"-veyron.tcp.address=127.0.0.1:0",
"-veyron.credentials=" + serverCred,
- "-veyron.namespace.root=" + env.RootMT(),
}
- serverProcess := buildServerBin.Start(args...)
- defer serverProcess.Kill(syscall.SIGTERM)
+ buildServerBin.Start(args...)
// Create and build a test source file.
testGoPath := env.TempDir()
@@ -72,7 +70,6 @@
}
buildArgs := []string{
"-veyron.credentials=" + clientCred,
- "-veyron.namespace.root=" + env.RootMT(),
"build", buildServerName, "test",
}
buildEnv := []string{"GOPATH=" + testGoPath, "GOROOT=" + goRoot, "TMPDIR=" + testBinDir}
diff --git a/services/mgmt/profile/profiled/testdata/integration_test.go b/services/mgmt/profile/profiled/testdata/integration_test.go
index 9c4f6ac..1d76a11 100644
--- a/services/mgmt/profile/profiled/testdata/integration_test.go
+++ b/services/mgmt/profile/profiled/testdata/integration_test.go
@@ -3,18 +3,15 @@
import (
"os"
"strings"
- "syscall"
"testing"
- "v.io/core/veyron/lib/modules"
- "v.io/core/veyron/lib/testutil/integration"
+ "v.io/core/veyron/lib/testutil/v23tests"
_ "v.io/core/veyron/profiles"
"v.io/core/veyron2/naming"
)
-func profileCommandOutput(t *testing.T, env integration.T, profileBin integration.TestBinary, expectError bool, command, name, suffix string) string {
+func profileCommandOutput(t *testing.T, env v23tests.T, profileBin v23tests.TestBinary, expectError bool, command, name, suffix string) string {
labelArgs := []string{
- "-veyron.namespace.root=" + env.RootMT(),
command, naming.Join(name, suffix),
}
labelCmd := profileBin.Start(labelArgs...)
@@ -29,29 +26,24 @@
return strings.TrimSpace(out)
}
-func putProfile(t *testing.T, env integration.T, profileBin integration.TestBinary, name, suffix string) {
+func putProfile(t *testing.T, env v23tests.T, profileBin v23tests.TestBinary, name, suffix string) {
putArgs := []string{
- "-veyron.namespace.root=" + env.RootMT(),
"put", naming.Join(name, suffix),
}
profileBin.Start(putArgs...).WaitOrDie(os.Stdout, os.Stderr)
}
-func removeProfile(t *testing.T, env integration.T, profileBin integration.TestBinary, name, suffix string) {
+func removeProfile(t *testing.T, env v23tests.T, profileBin v23tests.TestBinary, name, suffix string) {
removeArgs := []string{
- "-veyron.namespace.root=" + env.RootMT(),
"remove", naming.Join(name, suffix),
}
profileBin.Start(removeArgs...).WaitOrDie(os.Stdout, os.Stderr)
}
-func TestHelperProcess(t *testing.T) {
- modules.DispatchInTest()
-}
-
func TestProfileRepository(t *testing.T) {
- env := integration.New(t)
+ env := v23tests.New(t)
defer env.Cleanup()
+ v23tests.RunRootMT(env, "--veyron.tcp.address=127.0.0.1:0")
// Start the profile repository.
profileRepoName := "test-profile-repo"
@@ -59,11 +51,8 @@
args := []string{
"-name=" + profileRepoName, "-store=" + profileRepoStore,
"-veyron.tcp.address=127.0.0.1:0",
- "-veyron.namespace.root=" + env.RootMT(),
}
- serverBin := env.BuildGoPkg("v.io/core/veyron/services/mgmt/profile/profiled")
- serverInv := serverBin.Start(args...)
- defer serverInv.Kill(syscall.SIGTERM)
+ env.BuildGoPkg("v.io/core/veyron/services/mgmt/profile/profiled").Start(args...)
clientBin := env.BuildGoPkg("v.io/core/veyron/tools/profile")
diff --git a/services/mounttable/lib/servers.go b/services/mounttable/lib/servers.go
index 6edc5e3..1f844e7 100644
--- a/services/mounttable/lib/servers.go
+++ b/services/mounttable/lib/servers.go
@@ -67,7 +67,11 @@
return "", nil, err
}
- nh, err := NewLoopbackNeighborhoodDispatcher(nhName, mtName)
+ addresses := []string{}
+ for _, ep := range mtEndpoints {
+ addresses = append(addresses, ep.Name())
+ }
+ nh, err := NewNeighborhoodDispatcher(nhName, addresses...)
if err != nil {
vlog.Errorf("NewNeighborhoodServer failed: %v", err)
stop()
diff --git a/services/mounttable/mounttabled/mounttable.go b/services/mounttable/mounttabled/mounttable.go
index 62017a3..307cb33 100644
--- a/services/mounttable/mounttabled/mounttable.go
+++ b/services/mounttable/mounttabled/mounttable.go
@@ -3,6 +3,7 @@
import (
"flag"
+ "fmt"
"os"
"v.io/core/veyron2"
@@ -23,13 +24,17 @@
ctx, shutdown := veyron2.Init()
defer shutdown()
- _, stop, err := mounttable.StartServers(ctx, veyron2.GetListenSpec(ctx), *mountName, *nhName, *aclFile)
+ name, stop, err := mounttable.StartServers(ctx, veyron2.GetListenSpec(ctx), *mountName, *nhName, *aclFile)
if err != nil {
vlog.Errorf("mounttable.StartServers failed: %v", err)
os.Exit(1)
}
defer stop()
+ // Print out a directly accessible name of the mount table so that
+ // integration tests can reliably read it from stdout.
+ fmt.Printf("NAME=%s\n", name)
+
// Wait until signal is received.
vlog.Info("Received signal ", <-signals.ShutdownOnSignals(ctx))
}
diff --git a/services/mounttable/mounttabled/testdata/integration_test.go b/services/mounttable/mounttabled/testdata/integration_test.go
index 7b48cfe..4c9dd9d 100644
--- a/services/mounttable/mounttabled/testdata/integration_test.go
+++ b/services/mounttable/mounttabled/testdata/integration_test.go
@@ -4,31 +4,12 @@
"fmt"
"os"
"regexp"
- "strings"
- "syscall"
"testing"
- "time"
- "v.io/core/veyron/lib/expect"
- "v.io/core/veyron/lib/modules"
- "v.io/core/veyron/lib/testutil"
- "v.io/core/veyron/lib/testutil/integration"
+ "v.io/core/veyron/lib/testutil/v23tests"
_ "v.io/core/veyron/profiles/static"
)
-func TestHelperProcess(t *testing.T) {
- modules.DispatchInTest()
-}
-
-func pickUnusedPort(t *testing.T) int {
- if port, err := testutil.FindUnusedPort(); err != nil {
- t.Fatalf("FindUnusedPort failed: %v", err)
- return 0
- } else {
- return port
- }
-}
-
func getHostname(t *testing.T) string {
if hostname, err := os.Hostname(); err != nil {
t.Fatalf("Hostname() failed: %v", err)
@@ -39,53 +20,43 @@
}
func TestMount(t *testing.T) {
- env := integration.New(t)
+ env := v23tests.New(t)
defer env.Cleanup()
- // Build and run mounttabled. The test environment already gives us
- // one, but we want to run it with our own options (e.g.
- // --neighborhood_name), and currently the test environment does not
- // let us customize the execution of the automatically-provided
- // mounttabled.
- serverBin := env.BuildGoPkg("v.io/core/veyron/services/mounttable/mounttabled")
- serverPort := pickUnusedPort(t)
- address := fmt.Sprintf("127.0.0.1:%d", serverPort)
- endpoint := fmt.Sprintf("/%s", address)
neighborhood := fmt.Sprintf("test-%s-%d", getHostname(t), os.Getpid())
- inv := serverBin.Start(fmt.Sprintf("--veyron.tcp.address=127.0.0.1:%d", serverPort), "--neighborhood_name="+neighborhood)
- defer inv.Kill(syscall.SIGTERM)
+ v23tests.RunRootMT(env, "--veyron.tcp.address=127.0.0.1:0", "--neighborhood_name="+neighborhood)
+
+ name, _ := env.GetVar("NAMESPACE_ROOT")
clientBin := env.BuildGoPkg("v.io/core/veyron/tools/mounttable")
// Get the neighborhood endpoint from the mounttable.
- e := expect.NewSession(t, clientBin.Start("glob", endpoint, "nh").Stdout(), 10*time.Second)
- neighborhoodEndpoint := e.ExpectSetEventuallyRE(`^nh (.*) \(TTL .*\)$`)[0][1]
+ neighborhoodEndpoint := clientBin.Start("glob", name, "nh").ExpectSetEventuallyRE(`^nh (.*) \(TTL .*\)$`)[0][1]
- if clientBin.Start("mount", endpoint+"/myself", endpoint, "5m").Wait(os.Stdout, os.Stderr) != nil {
+ if clientBin.Start("mount", name+"/myself", name, "5m").Wait(os.Stdout, os.Stderr) != nil {
t.Fatalf("failed to mount the mounttable on itself")
}
- if clientBin.Start("mount", endpoint+"/google", "/www.google.com:80", "5m").Wait(os.Stdout, os.Stderr) != nil {
+ if clientBin.Start("mount", name+"/google", "/www.google.com:80", "5m").Wait(os.Stdout, os.Stderr) != nil {
t.Fatalf("failed to mount www.google.com")
}
// Test glob output. We expect three entries (two we mounted plus the
// neighborhood). The 'myself' entry should be the IP:port we
// specified for the mounttable.
- e = expect.NewSession(t, clientBin.Start("glob", endpoint, "*").Stdout(), 10*time.Second)
- matches := e.ExpectSetEventuallyRE(
+ glob := clientBin.Start("glob", name, "*")
+ matches := glob.ExpectSetEventuallyRE(
`^google /www\.google\.com:80 \(TTL .*\)$`,
`^myself (.*) \(TTL .*\)$`,
`^nh `+regexp.QuoteMeta(neighborhoodEndpoint)+` \(TTL .*\)$`)
- if matches[1][1] != endpoint {
- t.Fatalf("expected 'myself' entry to be %s, but was %s", endpoint, matches[1][1])
+ if matches[1][1] != name {
+ t.Fatalf("expected 'myself' entry to be %q, but was %q", name, matches[1][1])
}
- // Test globbing on the neighborhood name. Its endpoint should contain
- // the IP:port we specified for the mounttable.
- e = expect.NewSession(t, clientBin.Start("glob", "/"+neighborhoodEndpoint, neighborhood).Stdout(), 10*time.Second)
- matches = e.ExpectSetEventuallyRE("^" + regexp.QuoteMeta(neighborhood) + ` (.*) \(TTL .*\)$`)
- if want := "@" + address + "@"; !strings.Contains(matches[0][1], want) {
- t.Fatalf("expected neighborhood endpoint to contain %s, but did not. Was: %s", want, matches[0][1])
+ // Test globbing on the neighborhood name. Its endpoint should be the
+ // endpoint of the mount table.
+ glob = clientBin.Start("glob", "/"+neighborhoodEndpoint, neighborhood)
+ matches = glob.ExpectSetEventuallyRE("^" + regexp.QuoteMeta(neighborhood) + ` (.*) \(TTL .*\)$`)
+ if matches[0][1] != name {
+ t.Fatalf("expected endpoint of mount table for name %s", neighborhood)
}
-
}
diff --git a/services/proxy/proxyd/main.go b/services/proxy/proxyd/main.go
index 63bf175..a4945a6 100644
--- a/services/proxy/proxyd/main.go
+++ b/services/proxy/proxyd/main.go
@@ -4,6 +4,7 @@
import (
"flag"
+ "fmt"
"net/http"
_ "net/http/pprof"
"time"
@@ -49,6 +50,9 @@
defer publisher.Stop()
publisher.AddServer(proxy.Endpoint().String(), false)
publisher.AddName(*name)
+ // Print out a directly accessible name for the proxy table so
+ // that integration tests can reliably read it from stdout.
+ fmt.Printf("NAME=%s\n", proxy.Endpoint().Name())
}
if len(*healthzAddr) != 0 {
diff --git a/tools/binary/doc.go b/tools/binary/doc.go
index cac49eb..31712d3 100644
--- a/tools/binary/doc.go
+++ b/tools/binary/doc.go
@@ -10,7 +10,7 @@
The binary commands are:
delete Delete a binary
download Download a binary
- upload Upload a binary
+ upload Upload a binary or directory archive
url Fetch a download URL
help Display help for commands or topics
Run "binary help [command]" for command usage.
@@ -78,13 +78,14 @@
Binary Upload
Upload connects to the binary repository and uploads the binary of the specified
-file. When successful, it writes the name of the new binary to stdout.
+file or archive of the specified directory. When successful, it writes the name
+of the new binary to stdout.
Usage:
binary upload <von> <filename>
<von> is the veyron object name of the binary to upload <filename> is the name
-of the file to upload
+of the file or directory to upload
Binary Url
diff --git a/tools/binary/impl.go b/tools/binary/impl.go
index cfd78f9..9d9f56a 100644
--- a/tools/binary/impl.go
+++ b/tools/binary/impl.go
@@ -59,15 +59,15 @@
var cmdUpload = &cmdline.Command{
Run: runUpload,
Name: "upload",
- Short: "Upload a binary",
+ Short: "Upload a binary or directory archive",
Long: `
Upload connects to the binary repository and uploads the binary of the specified
-file. When successful, it writes the name of the new binary to stdout.
+file or archive of the specified directory. When successful, it writes the name of the new binary to stdout.
`,
ArgsName: "<von> <filename>",
ArgsLong: `
<von> is the veyron object name of the binary to upload
-<filename> is the name of the file to upload
+<filename> is the name of the file or directory to upload
`,
}
diff --git a/tools/debug/impl.go b/tools/debug/impl.go
index 4d423ba..c8a9406 100644
--- a/tools/debug/impl.go
+++ b/tools/debug/impl.go
@@ -161,6 +161,10 @@
if !ok {
return lastErr
}
+ if me.Error != nil {
+ fmt.Fprintf(cmd.Stderr(), "Error: %s: %v\n", me.Name, me.Error)
+ continue
+ }
fmt.Fprint(cmd.Stdout(), me.Name)
for _, s := range me.Servers {
fmt.Fprintf(cmd.Stdout(), " %s (Expires %s)", s.Server, s.Expires)
diff --git a/tools/debug/testdata/integration_test.go b/tools/debug/testdata/integration_test.go
index 1c23518..0a09142 100644
--- a/tools/debug/testdata/integration_test.go
+++ b/tools/debug/testdata/integration_test.go
@@ -8,30 +8,29 @@
"regexp"
"strconv"
"strings"
- "syscall"
"testing"
"time"
- "v.io/core/veyron/lib/modules"
- "v.io/core/veyron/lib/testutil/integration"
-
+ "v.io/core/veyron/lib/testutil/v23tests"
_ "v.io/core/veyron/profiles/static"
)
-func TestHelperProcess(t *testing.T) {
- modules.DispatchInTest()
-}
+// TODO(sjr): consolidate some of these tests to amortize the cost
+// of the build/setup times.
+// TODO(sjr): caching of binaries is limited to a single instance of
+// of the integration environment which makes this test very slow.
func TestDebugGlob(t *testing.T) {
- env := integration.New(t)
+ env := v23tests.New(t)
defer env.Cleanup()
+ v23tests.RunRootMT(env, "--veyron.tcp.address=127.0.0.1:0")
binary := env.BuildGoPkg("v.io/core/veyron/tools/debug")
- inv := binary.Start("glob", env.RootMT()+"/__debug/*")
+ inv := binary.Start("glob", "__debug/*")
var want string
for _, entry := range []string{"logs", "pprof", "stats", "vtrace"} {
- want += env.RootMT() + "/__debug/" + entry + "\n"
+ want += "__debug/" + entry + "\n"
}
if got := inv.Output(); got != want {
t.Fatalf("unexpected output, want %s, got %s", want, got)
@@ -39,12 +38,13 @@
}
func TestDebugGlobLogs(t *testing.T) {
- env := integration.New(t)
+ env := v23tests.New(t)
defer env.Cleanup()
+ v23tests.RunRootMT(env, "--veyron.tcp.address=127.0.0.1:0")
fileName := filepath.Base(env.TempFile().Name())
binary := env.BuildGoPkg("v.io/core/veyron/tools/debug")
- output := binary.Start("glob", env.RootMT()+"/__debug/logs/*").Output()
+ output := binary.Start("glob", "__debug/logs/*").Output()
// The output should contain the filename.
want := "/logs/" + fileName
@@ -54,10 +54,11 @@
}
func TestReadHostname(t *testing.T) {
- env := integration.New(t)
+ env := v23tests.New(t)
defer env.Cleanup()
+ v23tests.RunRootMT(env, "--veyron.tcp.address=127.0.0.1:0")
- path := env.RootMT() + "/__debug/stats/system/hostname"
+ path := "__debug/stats/system/hostname"
binary := env.BuildGoPkg("v.io/core/veyron/tools/debug")
got := binary.Start("stats", "read", path).Output()
hostname, err := os.Hostname()
@@ -69,7 +70,7 @@
}
}
-func createTestLogFile(t *testing.T, env integration.T, content string) *os.File {
+func createTestLogFile(t *testing.T, env v23tests.T, content string) *os.File {
file := env.TempFile()
_, err := file.Write([]byte(content))
if err != nil {
@@ -79,15 +80,16 @@
}
func TestLogSize(t *testing.T) {
- env := integration.New(t)
+ env := v23tests.New(t)
defer env.Cleanup()
+ v23tests.RunRootMT(env, "--veyron.tcp.address=127.0.0.1:0")
binary := env.BuildGoPkg("v.io/core/veyron/tools/debug")
testLogData := "This is a test log file"
file := createTestLogFile(t, env, testLogData)
// Check to ensure the file size is accurate
- str := strings.TrimSpace(binary.Start("logs", "size", env.RootMT()+"/__debug/logs/"+filepath.Base(file.Name())).Output())
+ str := strings.TrimSpace(binary.Start("logs", "size", "__debug/logs/"+filepath.Base(file.Name())).Output())
got, err := strconv.Atoi(str)
if err != nil {
t.Fatalf("Atoi(\"%q\") failed", str)
@@ -99,8 +101,9 @@
}
func TestStatsRead(t *testing.T) {
- env := integration.New(t)
+ env := v23tests.New(t)
defer env.Cleanup()
+ v23tests.RunRootMT(env, "--veyron.tcp.address=127.0.0.1:0")
binary := env.BuildGoPkg("v.io/core/veyron/tools/debug")
testLogData := "This is a test log file\n"
@@ -108,9 +111,11 @@
logName := filepath.Base(file.Name())
runCount := 12
for i := 0; i < runCount; i++ {
- binary.Start("logs", "read", env.RootMT()+"/__debug/logs/"+logName).WaitOrDie(nil, nil)
+ binary.Start("logs", "read", "__debug/logs/"+logName).WaitOrDie(nil, nil)
}
- got := binary.Start("stats", "read", env.RootMT()+"/__debug/stats/ipc/server/routing-id/*/methods/ReadLog/latency-ms").Output()
+
+ got := binary.Start("stats", "read", "__debug/stats/ipc/server/routing-id/*/methods/ReadLog/latency-ms").Output()
+
want := fmt.Sprintf("Count: %d", runCount)
if !strings.Contains(got, want) {
t.Fatalf("expected output to contain %s, but did not\n", want, got)
@@ -118,16 +123,17 @@
}
func TestStatsWatch(t *testing.T) {
- env := integration.New(t)
+ env := v23tests.New(t)
defer env.Cleanup()
+ v23tests.RunRootMT(env, "--veyron.tcp.address=127.0.0.1:0")
binary := env.BuildGoPkg("v.io/core/veyron/tools/debug")
testLogData := "This is a test log file\n"
file := createTestLogFile(t, env, testLogData)
logName := filepath.Base(file.Name())
- binary.Start("logs", "read", env.RootMT()+"/__debug/logs/"+logName).WaitOrDie(nil, nil)
+ binary.Start("logs", "read", "__debug/logs/"+logName).WaitOrDie(nil, nil)
- inv := binary.Start("stats", "watch", "-raw", env.RootMT()+"/__debug/stats/ipc/server/routing-id/*/methods/ReadLog/latency-ms")
+ inv := binary.Start("stats", "watch", "-raw", "__debug/stats/ipc/server/routing-id/*/methods/ReadLog/latency-ms")
lineChan := make(chan string)
// Go off and read the invocation's stdout.
@@ -151,23 +157,20 @@
t.Errorf("wanted but could not find %q in output\n%s", want, got)
}
}
-
- // TODO(sjr): make env cleanup take care of invocations that are still
- // running at the end of the test.
- inv.Kill(syscall.SIGTERM)
}
-func performTracedRead(debugBinary integration.TestBinary, path string) string {
+func performTracedRead(debugBinary v23tests.TestBinary, path string) string {
return debugBinary.Start("--veyron.vtrace.sample_rate=1", "logs", "read", path).Output()
}
func TestVTrace(t *testing.T) {
- env := integration.New(t)
+ env := v23tests.New(t)
defer env.Cleanup()
+ v23tests.RunRootMT(env, "--veyron.tcp.address=127.0.0.1:0")
binary := env.BuildGoPkg("v.io/core/veyron/tools/debug")
logContent := "Hello, world!\n"
- logPath := env.RootMT() + "/__debug/logs/" + filepath.Base(createTestLogFile(t, env, logContent).Name())
+ logPath := "__debug/logs/" + filepath.Base(createTestLogFile(t, env, logContent).Name())
// Create a log file with tracing, read it and check that the resulting trace exists.
got := performTracedRead(binary, logPath)
if logContent != got {
@@ -175,7 +178,7 @@
}
// Grab the ID of the first and only trace.
- want, traceContent := 1, binary.Start("vtrace", env.RootMT()+"/__debug/vtrace").Output()
+ want, traceContent := 1, binary.Start("vtrace", "__debug/vtrace").Output()
if count := strings.Count(traceContent, "Trace -"); count != want {
t.Fatalf("unexpected trace count, want %d, got %d\n%s", want, count, traceContent)
}
@@ -194,14 +197,14 @@
performTracedRead(binary, logPath)
// Read vtrace, we should have 2 traces now.
- want, output := 2, binary.Start("vtrace", env.RootMT()+"/__debug/vtrace").Output()
+ want, output := 2, binary.Start("vtrace", "__debug/vtrace").Output()
if count := strings.Count(output, "Trace -"); count != want {
t.Fatalf("unexpected trace count, want %d, got %d\n%s", want, count, output)
}
// Now ask for a particular trace. The output should contain exactly
// one trace whose ID is equal to the one we asked for.
- want, got = 1, binary.Start("vtrace", env.RootMT()+"/__debug/vtrace", traceId).Output()
+ want, got = 1, binary.Start("vtrace", "__debug/vtrace", traceId).Output()
if count := strings.Count(got, "Trace -"); count != want {
t.Fatalf("unexpected trace count, want %d, got %d\n%s", want, count, got)
}
@@ -216,11 +219,12 @@
}
func TestPprof(t *testing.T) {
- env := integration.New(t)
+ env := v23tests.New(t)
defer env.Cleanup()
+ v23tests.RunRootMT(env, "--veyron.tcp.address=127.0.0.1:0")
binary := env.BuildGoPkg("v.io/core/veyron/tools/debug")
- inv := binary.Start("pprof", "run", env.RootMT()+"/__debug/pprof", "heap", "--text")
+ inv := binary.Start("pprof", "run", "__debug/pprof", "heap", "--text")
// Assert that a profile indicating the heap size was written out.
want, got := "(.*) of (.*) total", inv.Output()
diff --git a/tools/mgmt/device/doc.go b/tools/mgmt/device/doc.go
index c8f4780..26d2164 100644
--- a/tools/mgmt/device/doc.go
+++ b/tools/mgmt/device/doc.go
@@ -91,14 +91,16 @@
Install the given application, specified using a local path.
Usage:
- device install-local [flags] <device> <title> [ENV=VAL ...] binary [--flag=val ...]
+ device install-local [flags] <device> <title> [ENV=VAL ...] binary [--flag=val ...] [PACKAGES path ...]
<device> is the veyron object name of the device manager's app service.
<title> is the app title.
This is followed by an arbitrary number of environment variable settings, the
-local path for the binary to install, and arbitrary flag settings.
+local path for the binary to install, and arbitrary flag settings and args.
+Optionally, this can be followed by 'PACKAGES' and a list of local files and
+directories to be installed as packages for the app
The device install-local flags are:
-config={}
diff --git a/tools/mgmt/device/impl/devicemanager_mock_test.go b/tools/mgmt/device/impl/devicemanager_mock_test.go
index d0da827..254d370 100644
--- a/tools/mgmt/device/impl/devicemanager_mock_test.go
+++ b/tools/mgmt/device/impl/devicemanager_mock_test.go
@@ -1,7 +1,11 @@
package impl_test
import (
+ "fmt"
+ "io/ioutil"
"log"
+ "os"
+ "path/filepath"
"testing"
"v.io/core/veyron2"
@@ -17,6 +21,7 @@
"v.io/core/veyron2/vlog"
binlib "v.io/core/veyron/services/mgmt/lib/binary"
+ "v.io/core/veyron/services/mgmt/lib/packages"
)
type mockDeviceInvoker struct {
@@ -79,11 +84,14 @@
// Mock Install
type InstallStimulus struct {
- fun string
- appName string
- config device.Config
- envelope application.Envelope
- binarySize int64
+ fun string
+ appName string
+ config device.Config
+ envelope application.Envelope
+ // files holds a map from file or package name to file or package size.
+ // The app binary has the key "binary". Each of the packages will have
+ // the key "package/<package name>".
+ files map[string]int64
}
type InstallResponse struct {
@@ -103,8 +111,28 @@
binaryNameAfterFetch = "binary-fetched"
)
+func packageSize(pkgPath string) int64 {
+ info, err := os.Stat(pkgPath)
+ if err != nil {
+ return -1
+ }
+ if info.IsDir() {
+ infos, err := ioutil.ReadDir(pkgPath)
+ if err != nil {
+ return -1
+ }
+ var size int64
+ for _, i := range infos {
+ size += i.Size()
+ }
+ return size
+ } else {
+ return info.Size()
+ }
+}
+
func (mni *mockDeviceInvoker) Install(call ipc.ServerContext, appName string, config device.Config) (string, error) {
- is := InstallStimulus{"Install", appName, config, application.Envelope{}, 0}
+ is := InstallStimulus{"Install", appName, config, application.Envelope{}, nil}
if appName != appNameNoFetch {
// Fetch the envelope and record it in the stimulus.
envelope, err := repository.ApplicationClient(appName).Match(call.Context(), []string{"test"})
@@ -113,14 +141,38 @@
}
binaryName := envelope.Binary
envelope.Binary = binaryNameAfterFetch
- is.envelope = envelope
is.appName = appNameAfterFetch
+ is.files = make(map[string]int64)
// Fetch the binary and record its size in the stimulus.
- data, _, err := binlib.Download(call.Context(), binaryName)
+ data, mediaInfo, err := binlib.Download(call.Context(), binaryName)
if err != nil {
return "", err
}
- is.binarySize = int64(len(data))
+ is.files["binary"] = int64(len(data))
+ if mediaInfo.Type != "application/octet-stream" {
+ return "", fmt.Errorf("unexpected media type: %v", mediaInfo)
+ }
+ // Iterate over the packages, download them, compute the size of
+ // the file(s) that make up each package, and record that in the
+ // stimulus.
+ for pkgLocalName, pkgVON := range envelope.Packages {
+ dir, err := ioutil.TempDir("", "package")
+ if err != nil {
+ return "", fmt.Errorf("failed to create temp package dir: %v", err)
+ }
+ defer os.RemoveAll(dir)
+ tmpFile := filepath.Join(dir, pkgLocalName)
+ if err := binlib.DownloadToFile(call.Context(), pkgVON, tmpFile); err != nil {
+ return "", fmt.Errorf("DownloadToFile failed: %v", err)
+ }
+ dst := filepath.Join(dir, "install")
+ if err := packages.Install(tmpFile, dst); err != nil {
+ return "", fmt.Errorf("packages.Install failed: %v", err)
+ }
+ is.files[naming.Join("packages", pkgLocalName)] = packageSize(dst)
+ }
+ envelope.Packages = nil
+ is.envelope = envelope
}
r := mni.tape.Record(is).(InstallResponse)
return r.appId, r.err
diff --git a/tools/mgmt/device/impl/impl_test.go b/tools/mgmt/device/impl/impl_test.go
index 09e0a83..3a14a76 100644
--- a/tools/mgmt/device/impl/impl_test.go
+++ b/tools/mgmt/device/impl/impl_test.go
@@ -215,14 +215,14 @@
nil,
false,
InstallResponse{appId, nil},
- InstallStimulus{"Install", appNameNoFetch, nil, application.Envelope{}, 0},
+ InstallStimulus{"Install", appNameNoFetch, nil, application.Envelope{}, nil},
},
{
[]string{deviceName, appNameNoFetch},
cfg,
false,
InstallResponse{appId, nil},
- InstallStimulus{"Install", appNameNoFetch, cfg, application.Envelope{}, 0},
+ InstallStimulus{"Install", appNameNoFetch, cfg, application.Envelope{}, nil},
},
} {
tape.SetResponses([]interface{}{c.tapeResponse})
diff --git a/tools/mgmt/device/impl/local_install.go b/tools/mgmt/device/impl/local_install.go
index 1598559..b6a926d 100644
--- a/tools/mgmt/device/impl/local_install.go
+++ b/tools/mgmt/device/impl/local_install.go
@@ -7,6 +7,7 @@
"io"
"io/ioutil"
"os"
+ "path/filepath"
"strings"
"v.io/core/veyron2"
@@ -20,6 +21,8 @@
"v.io/core/veyron2/services/mgmt/repository"
"v.io/core/veyron2/services/security/access"
"v.io/core/veyron2/uniqueid"
+
+ "v.io/core/veyron/services/mgmt/lib/packages"
"v.io/lib/cmdline"
)
@@ -28,15 +31,16 @@
Name: "install-local",
Short: "Install the given application from the local system.",
Long: "Install the given application, specified using a local path.",
- ArgsName: "<device> <title> [ENV=VAL ...] binary [--flag=val ...]",
+ ArgsName: "<device> <title> [ENV=VAL ...] binary [--flag=val ...] [PACKAGES path ...]",
ArgsLong: `
<device> is the veyron object name of the device manager's app service.
<title> is the app title.
This is followed by an arbitrary number of environment variable settings, the
-local path for the binary to install, and arbitrary flag settings.`,
-}
+local path for the binary to install, and arbitrary flag settings and args.
+Optionally, this can be followed by 'PACKAGES' and a list of local files and
+directories to be installed as packages for the app`}
func init() {
cmdInstallLocal.Flags.Var(&configOverride, "config", "JSON-encoded device.Config object, of the form: '{\"flag1\":\"value1\",\"flag2\":\"value2\"}'")
@@ -152,7 +156,7 @@
}
h.Write(bytes)
part := binary.PartInfo{Checksum: hex.EncodeToString(h.Sum(nil)), Size: int64(len(bytes))}
- return []binary.PartInfo{part}, repository.MediaInfo{Type: "application/octet-stream"}, nil
+ return []binary.PartInfo{part}, packages.MediaInfoForFileName(fileName), nil
}
func (binaryInvoker) Upload(repository.BinaryUploadContext, int32) error {
@@ -213,7 +217,17 @@
return cmd.UsageErrorf("install-local: missing binary")
}
binary := args[0]
- envelope.Args = args[1:]
+ args = args[1:]
+ firstNonArg, firstPackage := len(args), len(args)
+ for i, arg := range args {
+ if arg == "PACKAGES" {
+ firstNonArg = i
+ firstPackage = i + 1
+ break
+ }
+ }
+ envelope.Args = args[:firstNonArg]
+ pkgs := args[firstPackage:]
if _, err := os.Stat(binary); err != nil {
return fmt.Errorf("binary %v not found: %v", binary, err)
}
@@ -225,6 +239,43 @@
defer cancel()
envelope.Binary = naming.Join(name, "binary")
+ // For each package dir/file specified in the arguments list, set up an
+ // object in the binary service to serve that package, and add the
+ // object name to the envelope's Packages map.
+ var tmpZipDir string
+ for _, p := range pkgs {
+ if envelope.Packages == nil {
+ envelope.Packages = make(map[string]string)
+ }
+ info, err := os.Stat(p)
+ if os.IsNotExist(err) {
+ return fmt.Errorf("%v not found: %v", p, err)
+ } else if err != nil {
+ return fmt.Errorf("Stat(%v) failed: %v", p, err)
+ }
+ pkgName := naming.Join("packages", info.Name())
+ if _, ok := objects[pkgName]; ok {
+ return fmt.Errorf("can't have more than one package with name %v", info.Name())
+ }
+ fileName := p
+ // Directory packages first get zip'ped.
+ if info.IsDir() {
+ if tmpZipDir == "" {
+ tmpZipDir, err = ioutil.TempDir("", "packages")
+ if err != nil {
+ return fmt.Errorf("failed to create a temp dir for zip packages: %v", err)
+ }
+ defer os.RemoveAll(tmpZipDir)
+ }
+ fileName = filepath.Join(tmpZipDir, info.Name()+".zip")
+ if err := packages.CreateZip(fileName, p); err != nil {
+ return err
+ }
+ }
+ objects[pkgName] = repository.BinaryServer(binaryInvoker(fileName))
+ envelope.Packages[info.Name()] = naming.Join(name, pkgName)
+ }
+
objects["application"] = repository.ApplicationServer(envelopeInvoker(envelope))
appName := naming.Join(name, "application")
appID, err := device.ApplicationClient(deviceName).Install(gctx, appName, device.Config(configOverride))
diff --git a/tools/mgmt/device/impl/local_install_test.go b/tools/mgmt/device/impl/local_install_test.go
index 1029ec1..4c8b5b5 100644
--- a/tools/mgmt/device/impl/local_install_test.go
+++ b/tools/mgmt/device/impl/local_install_test.go
@@ -4,7 +4,9 @@
"bytes"
"encoding/json"
"fmt"
+ "io/ioutil"
"os"
+ "path/filepath"
"reflect"
"strings"
"testing"
@@ -17,6 +19,12 @@
"v.io/core/veyron/tools/mgmt/device/impl"
)
+func createFile(t *testing.T, path string, contents string) {
+ if err := ioutil.WriteFile(path, []byte(contents), 0700); err != nil {
+ t.Fatalf("Failed to create %v: %v", path, err)
+ }
+}
+
func TestInstallLocalCommand(t *testing.T) {
shutdown := initTest()
defer shutdown()
@@ -32,7 +40,13 @@
var stdout, stderr bytes.Buffer
cmd.Init(nil, &stdout, &stderr)
deviceName := naming.JoinAddressName(endpoint.String(), "")
- appTitle := "Appo di tutti Appi"
+ const appTitle = "Appo di tutti Appi"
+ binary := os.Args[0]
+ fi, err := os.Stat(binary)
+ if err != nil {
+ t.Fatalf("Failed to stat %v: %v", binary, err)
+ }
+ binarySize := fi.Size()
for i, c := range []struct {
args []string
stderrSubstr string
@@ -49,6 +63,9 @@
{
[]string{deviceName, appTitle, "foo"}, "binary foo not found",
},
+ {
+ []string{deviceName, appTitle, binary, "PACKAGES", "foo"}, "foo not found",
+ },
} {
c.args = append([]string{"install-local"}, c.args...)
if err := cmd.Execute(c.args); err == nil {
@@ -66,16 +83,27 @@
stdout.Reset()
stderr.Reset()
}
- appId := "myBestAppID"
- binary := os.Args[0]
- fi, err := os.Stat(binary)
- if err != nil {
- t.Fatalf("Failed to stat %v: %v", binary, err)
- }
emptySig := security.Signature{Purpose: []uint8{}, Hash: "", R: []uint8{}, S: []uint8{}}
emptyBlessings := security.WireBlessings{}
- binarySize := fi.Size()
cfg := device.Config{"someflag": "somevalue"}
+
+ testPackagesDir, err := ioutil.TempDir("", "testdir")
+ if err != nil {
+ t.Fatalf("Failed to create temp dir: %v", err)
+ }
+ defer os.RemoveAll(testPackagesDir)
+ pkgFile1 := filepath.Join(testPackagesDir, "file1.txt")
+ createFile(t, pkgFile1, "1234567")
+ pkgFile2 := filepath.Join(testPackagesDir, "file2")
+ createFile(t, pkgFile2, string([]byte{0x01, 0x02, 0x03, 0x04}))
+ pkgDir := filepath.Join(testPackagesDir, "dir")
+ if err := os.Mkdir(pkgDir, 0700); err != nil {
+ t.Fatalf("Failed to create dir: %v", err)
+ }
+ createFile(t, filepath.Join(pkgDir, "f1"), "123")
+ createFile(t, filepath.Join(pkgDir, "f2"), "456")
+ createFile(t, filepath.Join(pkgDir, "f3"), "7890")
+
for i, c := range []struct {
args []string
config device.Config
@@ -84,19 +112,68 @@
{
[]string{deviceName, appTitle, binary},
nil,
- InstallStimulus{"Install", appNameAfterFetch, nil, application.Envelope{Title: appTitle, Binary: binaryNameAfterFetch, Signature: emptySig, Publisher: emptyBlessings}, binarySize},
+ InstallStimulus{
+ "Install",
+ appNameAfterFetch,
+ nil,
+ application.Envelope{
+ Title: appTitle,
+ Binary: binaryNameAfterFetch,
+ Signature: emptySig,
+ Publisher: emptyBlessings,
+ },
+ map[string]int64{"binary": binarySize}},
},
{
[]string{deviceName, appTitle, binary},
cfg,
- InstallStimulus{"Install", appNameAfterFetch, cfg, application.Envelope{Title: appTitle, Binary: binaryNameAfterFetch, Signature: emptySig, Publisher: emptyBlessings}, binarySize},
+ InstallStimulus{
+ "Install",
+ appNameAfterFetch,
+ cfg,
+ application.Envelope{
+ Title: appTitle,
+ Binary: binaryNameAfterFetch,
+ Signature: emptySig,
+ Publisher: emptyBlessings,
+ },
+ map[string]int64{"binary": binarySize}},
},
{
[]string{deviceName, appTitle, "ENV1=V1", "ENV2=V2", binary, "FLAG1=V1", "FLAG2=V2"},
nil,
- InstallStimulus{"Install", appNameAfterFetch, nil, application.Envelope{Title: appTitle, Binary: binaryNameAfterFetch, Signature: emptySig, Publisher: emptyBlessings, Env: []string{"ENV1=V1", "ENV2=V2"}, Args: []string{"FLAG1=V1", "FLAG2=V2"}}, binarySize},
+ InstallStimulus{
+ "Install",
+ appNameAfterFetch,
+ nil,
+ application.Envelope{
+ Title: appTitle,
+ Binary: binaryNameAfterFetch,
+ Signature: emptySig,
+ Publisher: emptyBlessings,
+ Env: []string{"ENV1=V1", "ENV2=V2"},
+ Args: []string{"FLAG1=V1", "FLAG2=V2"},
+ },
+ map[string]int64{"binary": binarySize}},
+ },
+ {
+ []string{deviceName, appTitle, "ENV=V", binary, "FLAG=V", "PACKAGES", pkgFile1, pkgFile2, pkgDir},
+ nil,
+ InstallStimulus{"Install",
+ appNameAfterFetch,
+ nil,
+ application.Envelope{
+ Title: appTitle,
+ Binary: binaryNameAfterFetch,
+ Signature: emptySig,
+ Publisher: emptyBlessings,
+ Env: []string{"ENV=V"},
+ Args: []string{"FLAG=V"},
+ },
+ map[string]int64{"binary": binarySize, "packages/file1.txt": 7, "packages/file2": 4, "packages/dir": 10}},
},
} {
+ const appId = "myBestAppID"
tape.SetResponses([]interface{}{InstallResponse{appId, nil}})
if c.config != nil {
jsonConfig, err := json.Marshal(c.config)
diff --git a/tools/namespace/impl.go b/tools/namespace/impl.go
index 53f73ae..a80902f 100644
--- a/tools/namespace/impl.go
+++ b/tools/namespace/impl.go
@@ -40,14 +40,14 @@
return err
}
for res := range c {
+ if res.Error != nil {
+ fmt.Fprintf(cmd.Stderr(), "Error: %s: %v\n", res.Name, res.Error)
+ continue
+ }
fmt.Fprint(cmd.Stdout(), res.Name)
for _, s := range res.Servers {
fmt.Fprintf(cmd.Stdout(), " %s (Expires %s)", s.Server, s.Expires)
}
- if res.Error != nil {
- fmt.Fprintln(cmd.Stdout())
- fmt.Fprintf(cmd.Stdout(), "result error: %v", res.Error)
- }
fmt.Fprintln(cmd.Stdout())
}
return nil
diff --git a/tools/naming/simulator/simulator_test.go b/tools/naming/simulator/simulator_test.go
index fae47ca..0e01268 100644
--- a/tools/naming/simulator/simulator_test.go
+++ b/tools/naming/simulator/simulator_test.go
@@ -11,13 +11,14 @@
"testing"
"v.io/core/veyron/lib/modules"
- "v.io/core/veyron/lib/testutil/integration"
+ "v.io/core/veyron/lib/testutil/v23tests"
)
func TestHelperProcess(t *testing.T) {
modules.DispatchInTest()
}
-func V23TestSimulator(t integration.T) {
+
+func V23TestSimulator(t v23tests.T) {
binary := t.BuildGoPkg("v.io/core/veyron/tools/naming/simulator")
files, err := ioutil.ReadDir("./testdata")
if err != nil {
diff --git a/tools/naming/simulator/testdata/integration_test.go b/tools/naming/simulator/testdata/integration_test.go
index 77c5ddb..12c8576 100644
--- a/tools/naming/simulator/testdata/integration_test.go
+++ b/tools/naming/simulator/testdata/integration_test.go
@@ -9,8 +9,7 @@
"testing"
"v.io/core/veyron/lib/modules"
- "v.io/core/veyron/lib/testutil/integration"
-
+ "v.io/core/veyron/lib/testutil/v23tests"
_ "v.io/core/veyron/profiles/static"
)
@@ -19,7 +18,7 @@
}
func TestSimulator(t *testing.T) {
- env := integration.New(t)
+ env := v23tests.New(t)
defer env.Cleanup()
binary := env.BuildGoPkg("v.io/core/veyron/tools/naming/simulator")
files, err := ioutil.ReadDir(".")
diff --git a/tools/naming/simulator/v23_test.go b/tools/naming/simulator/v23_test.go
index dfde460..be63305 100644
--- a/tools/naming/simulator/v23_test.go
+++ b/tools/naming/simulator/v23_test.go
@@ -6,7 +6,7 @@
import "os"
import "v.io/core/veyron/lib/testutil"
-import "v.io/core/veyron/lib/testutil/integration"
+import "v.io/core/veyron/lib/testutil/v23tests"
func TestMain(m *testing.M) {
testutil.Init()
@@ -15,5 +15,5 @@
}
func TestV23Simulator(t *testing.T) {
- integration.RunTest(t, V23TestSimulator)
+ v23tests.RunTest(t, V23TestSimulator)
}
diff --git a/tools/principal/principal_test.go b/tools/principal/principal_test.go
index 302a698..e38b017 100644
--- a/tools/principal/principal_test.go
+++ b/tools/principal/principal_test.go
@@ -8,7 +8,7 @@
"testing"
"v.io/core/veyron/lib/modules"
- "v.io/core/veyron/lib/testutil/integration"
+ "v.io/core/veyron/lib/testutil/v23tests"
)
//go:generate v23 integration generate
@@ -18,13 +18,13 @@
// redirect redirects the stdout of the given invocation to the file at the
// given path.
-func redirect(t integration.T, inv integration.Invocation, path string) {
+func redirect(t v23tests.T, inv v23tests.Invocation, path string) {
if err := ioutil.WriteFile(path, []byte(inv.Output()), 0600); err != nil {
t.Fatalf("WriteFile(%q) failed: %v\n", path, err)
}
}
-func V23TestBlessSelf(t integration.T) {
+func V23TestBlessSelf(t v23tests.T) {
var (
outputDir = t.TempDir()
aliceDir = filepath.Join(outputDir, "alice")
diff --git a/tools/principal/testdata/integration_test.go b/tools/principal/testdata/integration_test.go
index 030dd17..a1d454b 100644
--- a/tools/principal/testdata/integration_test.go
+++ b/tools/principal/testdata/integration_test.go
@@ -8,7 +8,7 @@
"testing"
"v.io/core/veyron/lib/modules"
- "v.io/core/veyron/lib/testutil/integration"
+ "v.io/core/veyron/lib/testutil/v23tests"
_ "v.io/core/veyron/profiles"
)
@@ -18,14 +18,14 @@
// redirect redirects the stdout of the given invocation to the file at the
// given path.
-func redirect(t *testing.T, inv integration.Invocation, path string) {
+func redirect(t *testing.T, inv v23tests.Invocation, path string) {
if err := ioutil.WriteFile(path, []byte(inv.Output()), 0600); err != nil {
t.Fatalf("WriteFile(%q) failed: %v\n", path, err)
}
}
func TestBlessSelf(t *testing.T) {
- env := integration.New(t)
+ env := v23tests.New(t)
defer env.Cleanup()
var (
diff --git a/tools/principal/v23_test.go b/tools/principal/v23_test.go
index f421ae1..4e12189 100644
--- a/tools/principal/v23_test.go
+++ b/tools/principal/v23_test.go
@@ -6,7 +6,7 @@
import "os"
import "v.io/core/veyron/lib/testutil"
-import "v.io/core/veyron/lib/testutil/integration"
+import "v.io/core/veyron/lib/testutil/v23tests"
func TestMain(m *testing.M) {
testutil.Init()
@@ -15,5 +15,5 @@
}
func TestV23BlessSelf(t *testing.T) {
- integration.RunTest(t, V23TestBlessSelf)
+ v23tests.RunTest(t, V23TestBlessSelf)
}