blob: 9a9f615b464591b2c2094151e9a7eb5c0225ccd9 [file] [log] [blame]
// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package utiltest
import (
"encoding/json"
"flag"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"testing"
"time"
"v.io/v23"
"v.io/v23/context"
"v.io/v23/rpc"
"v.io/v23/security"
"v.io/x/lib/gosh"
"v.io/x/lib/vlog"
"v.io/x/ref/lib/exec"
"v.io/x/ref/lib/mgmt"
"v.io/x/ref/lib/signals"
"v.io/x/ref/services/device/internal/suid"
"v.io/x/ref/test"
"v.io/x/ref/test/testutil"
)
const (
TestFlagName = "random_test_flag"
)
var flagValue = flag.String(TestFlagName, "default", "")
func init() {
// The installer sets this flag on the installed device manager, so we
// need to ensure it's defined.
flag.String("name", "", "")
}
// appService defines a test service that the test app should be running.
// TODO(caprita): Use this to make calls to the app and verify how Kill
// interacts with an active service.
type appService struct{}
func (appService) Echo(_ *context.T, _ rpc.ServerCall, message string) (string, error) {
return message, nil
}
func (appService) Cat(_ *context.T, _ rpc.ServerCall, file string) (string, error) {
if file == "" || file[0] == filepath.Separator || file[0] == '.' {
return "", fmt.Errorf("illegal file name: %q", file)
}
bytes, err := ioutil.ReadFile(file)
if err != nil {
return "", err
}
return string(bytes), nil
}
type PingArgs struct {
Username, FlagValue, EnvValue,
DefaultPeerBlessings, PubBlessingPrefixes, InstanceName string
Pid int
}
// ping makes an RPC from the App back to the invoking device manager
// carrying a PingArgs instance.
func ping(ctx *context.T, flagValue string) {
ctx.Errorf("ping flagValue: %s", flagValue)
helperEnv := os.Getenv(suid.SavedArgs)
d := json.NewDecoder(strings.NewReader(helperEnv))
var savedArgs suid.ArgsSavedForTest
if err := d.Decode(&savedArgs); err != nil {
ctx.Fatalf("Failed to decode preserved argument %v: %v", helperEnv, err)
}
args := &PingArgs{
// TODO(rjkroege): Consider validating additional parameters
// from helper.
Username: savedArgs.Uname,
FlagValue: flagValue,
EnvValue: os.Getenv(TestEnvVarName),
Pid: os.Getpid(),
DefaultPeerBlessings: v23.GetPrincipal(ctx).BlessingStore().ForPeer("nonexistent").String(),
}
config, err := exec.ReadConfigFromOSEnv()
if config == nil || err != nil {
vlog.Fatalf("Couldn't get Config: %v", err)
} else {
args.PubBlessingPrefixes, _ = config.Get(mgmt.PublisherBlessingPrefixesKey)
args.InstanceName, _ = config.Get(mgmt.InstanceNameKey)
}
client := v23.GetClient(ctx)
if call, err := client.StartCall(ctx, "pingserver", "Ping", []interface{}{args}); err != nil {
ctx.Fatalf("StartCall failed: %v", err)
} else if err := call.Finish(); err != nil {
ctx.Fatalf("Finish failed: %v", err)
}
}
// Cat is an RPC invoked from the test harness process to the application
// process.
func Cat(ctx *context.T, name, file string) (string, error) {
ctx, cancel := context.WithTimeout(ctx, time.Minute)
defer cancel()
client := v23.GetClient(ctx)
call, err := client.StartCall(ctx, name, "Cat", []interface{}{file})
if err != nil {
return "", err
}
var content string
if err := call.Finish(&content); err != nil {
return "", err
}
return content, nil
}
// App is a test application. It pings the invoking device manager with state
// information.
var App = gosh.RegisterFunc("App", appFunc)
func appFunc(publishName string) error {
ctx, shutdown := test.V23Init()
defer shutdown()
ctx, server, err := v23.WithNewServer(ctx, publishName, new(appService), nil)
if err != nil {
ctx.Fatalf("NewServer(%v) failed: %v", publishName, err)
}
WaitForMount(ctx, ctx, publishName, server)
// Some of our tests look for log files, so make sure they are flushed
// to ensure that at least the files exist.
ctx.FlushLog()
shutdownChan := signals.ShutdownOnSignals(ctx)
ping(ctx, *flagValue)
<-shutdownChan
if err := ioutil.WriteFile("testfile", []byte("goodbye world"), 0600); err != nil {
ctx.Fatalf("Failed to write testfile: %v", err)
}
return nil
}
type PingServer struct {
ing chan PingArgs
}
// TODO(caprita): Set the timeout in a more principled manner.
const pingTimeout = time.Minute
func (p PingServer) Ping(_ *context.T, _ rpc.ServerCall, arg PingArgs) error {
p.ing <- arg
return nil
}
// SetupPingServer creates a server listening for a ping from a child app; it
// returns a channel on which the app's ping message is returned, and a cleanup
// function.
func SetupPingServer(t *testing.T, ctx *context.T) (PingServer, func()) {
pingCh := make(chan PingArgs, 1)
ctx, cancel := context.WithCancel(ctx)
ctx, server, err := v23.WithNewServer(ctx, "pingserver", PingServer{pingCh}, security.AllowEveryone())
if err != nil {
t.Fatalf("NewServer(%q, <dispatcher>) failed: %v", "pingserver", err)
}
WaitForMount(t, ctx, "pingserver", server)
return PingServer{pingCh}, func() {
cancel()
<-server.Closed()
}
}
func (p PingServer) WaitForPingArgs(t *testing.T) PingArgs {
var args PingArgs
select {
case args = <-p.ing:
case <-time.After(pingTimeout):
t.Fatal(testutil.FormatLogLine(2, "failed to get ping"))
}
return args
}
func (p PingServer) VerifyPingArgs(t *testing.T, username, flagValue, envValue string) PingArgs {
args := p.WaitForPingArgs(t)
if args.Username != username || args.FlagValue != flagValue || args.EnvValue != envValue {
t.Fatal(testutil.FormatLogLine(2, "got ping args %v, expected [username = %v, flag value = %v, env value = %v]", args, username, flagValue, envValue))
}
return args // Useful for tests that want to check other values in the PingArgs result.
}
// HangingApp is the same as App, except that it does not exit properly after
// being stopped.
var HangingApp = gosh.RegisterFunc("HangingApp", func(publishName string) error {
err := appFunc(publishName)
time.Sleep(24 * time.Hour)
return err
})