blob: e49788217b3289ac41ed509038215e67da5db531 [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 (
"errors"
"fmt"
"io/ioutil"
"os"
"os/user"
"path/filepath"
"reflect"
"regexp"
"sort"
"strings"
"syscall"
"testing"
"time"
"v.io/v23"
"v.io/v23/context"
"v.io/v23/naming"
"v.io/v23/options"
"v.io/v23/rpc"
"v.io/v23/security"
"v.io/v23/services/application"
"v.io/v23/services/device"
"v.io/v23/services/logreader"
"v.io/v23/services/pprof"
"v.io/v23/services/stats"
"v.io/v23/verror"
"v.io/x/lib/envvar"
"v.io/x/lib/gosh"
"v.io/x/ref"
"v.io/x/ref/internal/logger"
vsecurity "v.io/x/ref/lib/security"
_ "v.io/x/ref/runtime/factories/roaming"
"v.io/x/ref/services/device/deviced/internal/impl"
"v.io/x/ref/services/device/deviced/internal/versioning"
"v.io/x/ref/services/internal/binarylib"
"v.io/x/ref/services/internal/servicetest"
"v.io/x/ref/test"
"v.io/x/ref/test/testutil"
"v.io/x/ref/test/v23test"
)
const (
// TODO(caprita): Set the timeout in a more principled manner.
killTimeout = 20 * time.Second
)
func init() {
impl.Describe = func() (descr device.Description, err error) {
return device.Description{Profiles: map[string]struct{}{"test-profile": struct{}{}}}, nil
}
impl.CleanupDir = func(ctx *context.T, dir, helper string) {
if dir == "" {
return
}
parentDir, base := filepath.Dir(dir), filepath.Base(dir)
var renamed string
if helper != "" {
renamed = filepath.Join(parentDir, "helper_deleted_"+base)
} else {
renamed = filepath.Join(parentDir, "deleted_"+base)
}
if err := os.Rename(dir, renamed); err != nil {
ctx.Errorf("Rename(%v, %v) failed: %v", dir, renamed, err)
}
}
// Return a sequence of times separated by 25 hours.
impl.MockableNow = func() time.Time {
now := time.Now()
impl.MockableNow = func() time.Time {
now = now.Add(time.Hour * 25)
return now
}
return now
}
}
func EnvelopeFromShell(sh *v23test.Shell, vars, flags []string, f *gosh.Func, title string, retries int, window time.Duration, args ...interface{}) application.Envelope {
c := sh.FuncCmd(f, args...)
// Make sure the command is not provided with credentials from the shell;
// device manager is responsible for providing it credentials.
delete(c.Vars, ref.EnvCredentials)
delete(c.Vars, ref.EnvAgentPath)
// Note, vars is allowed to contain credentials env vars that were set
// deliberately.
c.Vars = envvar.MergeMaps(c.Vars, envvar.SliceToMap(vars))
// Configure the command to not exit when its parent exits, since device
// manager starts commands using a "suid helper" subprocess that exits
// immediately.
c.IgnoreParentExit = true
c.ExitAfter = time.Minute // make sure the child exits eventually
c.Args = append(c.Args, flags...)
return application.Envelope{
Title: title,
Args: c.Args[1:],
// TODO(caprita): revisit how the environment is sanitized for arbirary
// apps.
Env: impl.VanadiumEnvironment(envvar.MapToSlice(c.Vars)),
Binary: application.SignedFile{File: MockBinaryRepoName},
Restarts: int32(retries),
RestartTimeWindow: window,
}
}
func SignedEnvelopeFromShell(ctx *context.T, sh *v23test.Shell, vars, flags []string, f *gosh.Func, title string, retries int, window time.Duration, args ...interface{}) (application.Envelope, error) {
envelope := EnvelopeFromShell(sh, vars, flags, f, title, retries, window, args...)
reader, cleanup, err := mockBinaryBytesReader()
defer cleanup()
sig, err := binarylib.Sign(ctx, reader)
if err != nil {
return application.Envelope{}, err
}
envelope.Binary.Signature = *sig
// Add a publisher blessing
p := v23.GetPrincipal(ctx)
b, _ := p.BlessingStore().Default()
publisher, err := p.Bless(p.PublicKey(), b, "angryapp.v10", security.UnconstrainedUse())
if err != nil {
return application.Envelope{}, err
}
envelope.Publisher = publisher
return envelope, nil
}
// ResolveExpectNotFound verifies that the given name is not in the mounttable.
func ResolveExpectNotFound(f Fatalist, ctx *context.T, name string, retry bool) {
expectErr := naming.ErrNoSuchName.ID
for {
me, err := v23.GetNamespace(ctx).Resolve(ctx, name)
if err == nil || verror.ErrorID(err) != expectErr {
if retry {
time.Sleep(10 * time.Millisecond)
continue
}
if err == nil {
f.Fatal(testutil.FormatLogLine(2, "Resolve(%v) succeeded with results %v when it was expected to fail", name, me.Names()))
} else {
f.Fatal(testutil.FormatLogLine(2, "Resolve(%v) failed with error %v, expected error ID %v", name, err, expectErr))
}
} else {
return
}
}
}
// Resolve looks up the given name in the mounttable.
func Resolve(f Fatalist, ctx *context.T, name string, expectReplicas int, retry bool) []string {
for {
me, err := v23.GetNamespace(ctx).Resolve(ctx, name)
if err != nil {
if retry {
time.Sleep(10 * time.Millisecond)
continue
} else {
f.Fatalf("Resolve(%v) failed: %v", name, err)
}
}
// We are going to get a websocket and a tcp endpoint for each
// replica. Filter out non-tcp endpoints.
filteredResults := []string{}
for _, r := range me.Names() {
if strings.Index(r, "@tcp") != -1 {
filteredResults = append(filteredResults, r)
}
}
if want, got := expectReplicas, len(filteredResults); want != got {
f.Fatalf("Resolve(%v) expected %d result(s), got %d instead", name, want, got)
}
return filteredResults
}
}
// The following set of functions are convenience wrappers around Update and
// Revert for device manager.
func DeviceStub(name string) device.DeviceClientMethods {
deviceName := naming.Join(name, "device")
return device.DeviceClient(deviceName)
}
func ClaimDevice(t *testing.T, ctx *context.T, claimableName, deviceName, extension, pairingToken string) {
// Setup blessings to be granted to the claimed device
g := &granter{extension: extension}
s := options.ServerAuthorizer{security.AllowEveryone()}
// Call the Claim RPC: Skip server authorization because the unclaimed
// device presents nothing that can be used to recognize it.
if err := device.ClaimableClient(claimableName).Claim(ctx, pairingToken, g, s); err != nil {
t.Fatal(testutil.FormatLogLine(2, "%q.Claim(%q) failed: %v [%v]", claimableName, pairingToken, verror.ErrorID(err), err))
}
// Wait for the device to remount itself with the device service after
// being claimed.
start := time.Now()
for {
_, err := v23.GetNamespace(ctx).Resolve(ctx, deviceName)
if err == nil {
return
}
ctx.VI(4).Infof("Resolve(%q) failed: %v", err)
time.Sleep(time.Millisecond)
if elapsed := time.Since(start); elapsed > time.Minute {
t.Fatal(testutil.FormatLogLine(2, "Device hasn't remounted itself in %v since it was claimed", elapsed))
}
}
}
func ClaimDeviceExpectError(t *testing.T, ctx *context.T, name, extension, pairingToken string, errID verror.ID) {
// Setup blessings to be granted to the claimed device
g := &granter{extension: extension}
s := options.ServerAuthorizer{security.AllowEveryone()}
// Call the Claim RPC
if err := device.ClaimableClient(name).Claim(ctx, pairingToken, g, s); verror.ErrorID(err) != errID {
t.Fatal(testutil.FormatLogLine(2, "%q.Claim(%q) expected to fail with %v, got %v [%v]", name, pairingToken, errID, verror.ErrorID(err), err))
}
}
func UpdateDeviceExpectError(t *testing.T, ctx *context.T, name string, errID verror.ID) {
if err := DeviceStub(name).Update(ctx); verror.ErrorID(err) != errID {
t.Fatal(testutil.FormatLogLine(2, "%q.Update expected to fail with %v, got %v [%v]", name, errID, verror.ErrorID(err), err))
}
}
func UpdateDevice(t *testing.T, ctx *context.T, name string) {
if err := DeviceStub(name).Update(ctx); err != nil {
t.Fatal(testutil.FormatLogLine(2, "%q.Update() failed: %v [%v]", name, verror.ErrorID(err), err))
}
}
func RevertDeviceExpectError(t *testing.T, ctx *context.T, name string, errID verror.ID) {
if err := DeviceStub(name).Revert(ctx); verror.ErrorID(err) != errID {
t.Fatal(testutil.FormatLogLine(2, "%q.Revert() expected to fail with %v, got %v [%v]", name, errID, verror.ErrorID(err), err))
}
}
func RevertDevice(t *testing.T, ctx *context.T, name string) {
if err := DeviceStub(name).Revert(ctx); err != nil {
t.Fatal(testutil.FormatLogLine(2, "%q.Revert() failed: %v [%v]", name, verror.ErrorID(err), err))
}
}
func KillDevice(t *testing.T, ctx *context.T, name string) {
if err := DeviceStub(name).Kill(ctx, killTimeout); err != nil {
t.Fatal(testutil.FormatLogLine(2, "%q.Kill(%v) failed: %v [%v]", name, killTimeout, verror.ErrorID(err), err))
}
}
func ShutdownDevice(t *testing.T, ctx *context.T, name string) {
if err := DeviceStub(name).Delete(ctx); err != nil {
t.Fatal(testutil.FormatLogLine(2, "%q.Delete() failed: %v [%v]", name, verror.ErrorID(err), err))
}
}
// The following set of functions are convenience wrappers around various app
// management methods.
func Ocfg(opt []interface{}) device.Config {
for _, o := range opt {
if c, ok := o.(device.Config); ok {
return c
}
}
return device.Config{}
}
func Opkg(opt []interface{}) application.Packages {
for _, o := range opt {
if c, ok := o.(application.Packages); ok {
return c
}
}
return application.Packages{}
}
func AppStub(nameComponents ...string) device.ApplicationClientMethods {
appsName := "dm/apps"
appName := naming.Join(append([]string{appsName}, nameComponents...)...)
return device.ApplicationClient(appName)
}
func StatsStub(nameComponents ...string) stats.StatsClientMethods {
statsName := naming.Join(nameComponents...)
return stats.StatsClient(statsName)
}
func InstallApp(t *testing.T, ctx *context.T, opt ...interface{}) string {
appID, err := AppStub().Install(ctx, MockApplicationRepoName, Ocfg(opt), Opkg(opt))
if err != nil {
t.Fatal(testutil.FormatLogLine(2, "Install failed: %v [%v]", verror.ErrorID(err), err))
}
return appID
}
func InstallAppExpectError(t *testing.T, ctx *context.T, expectedError verror.ID, opt ...interface{}) {
if _, err := AppStub().Install(ctx, MockApplicationRepoName, Ocfg(opt), Opkg(opt)); err == nil || verror.ErrorID(err) != expectedError {
t.Fatal(testutil.FormatLogLine(2, "Install expected to fail with %v, got %v [%v]", expectedError, verror.ErrorID(err), err))
}
}
type granter struct {
rpc.CallOpt
p security.Principal
extension string
}
func (g *granter) Grant(ctx *context.T, call security.Call) (security.Blessings, error) {
p := call.LocalPrincipal()
b, _ := p.BlessingStore().Default()
return p.Bless(call.RemoteBlessings().PublicKey(), b, g.extension, security.UnconstrainedUse())
}
func LaunchAppImpl(t *testing.T, ctx *context.T, appID, grant string) (string, error) {
instanceID, err := NewInstanceImpl(t, ctx, appID, grant)
if err != nil {
return "", err
}
return instanceID, AppStub(appID, instanceID).Run(ctx)
}
func NewInstanceImpl(t *testing.T, ctx *context.T, appID, grant string) (string, error) {
call, err := AppStub(appID).Instantiate(ctx)
if err != nil {
return "", err
}
// We should finish the rpc call, even if we exit early due to an error.
defer call.Finish()
for call.RecvStream().Advance() {
switch msg := call.RecvStream().Value().(type) {
case device.BlessServerMessageInstancePublicKey:
p := v23.GetPrincipal(ctx)
b, _ := p.BlessingStore().Default()
pubKey, err := security.UnmarshalPublicKey(msg.Value)
if err != nil {
return "", err
}
blessings, err := p.Bless(pubKey, b, grant, security.UnconstrainedUse())
if err != nil {
return "", errors.New("bless failed")
}
call.SendStream().Send(device.BlessClientMessageAppBlessings{Value: blessings})
default:
return "", fmt.Errorf("newInstanceImpl: received unexpected message: %#v", msg)
}
}
var instanceID string
if instanceID, err = call.Finish(); err != nil {
return "", err
}
return instanceID, nil
}
func LaunchApp(t *testing.T, ctx *context.T, appID string) string {
instanceID, err := LaunchAppImpl(t, ctx, appID, "forapp")
if err != nil {
t.Fatal(testutil.FormatLogLine(2, "launching %v failed: %v [%v]", appID, verror.ErrorID(err), err))
}
return instanceID
}
func LaunchAppExpectError(t *testing.T, ctx *context.T, appID string, expectedError verror.ID) {
if _, err := LaunchAppImpl(t, ctx, appID, "forapp"); err == nil || verror.ErrorID(err) != expectedError {
t.Fatal(testutil.FormatLogLine(2, "launching %v expected to fail with %v, got %v [%v]", appID, expectedError, verror.ErrorID(err), err))
}
}
func TerminateApp(t *testing.T, ctx *context.T, appID, instanceID string) {
if err := AppStub(appID, instanceID).Kill(ctx, killTimeout); err != nil {
t.Fatal(testutil.FormatLogLine(2, "Kill(%v/%v) failed: %v [%v]", appID, instanceID, verror.ErrorID(err), err))
}
if err := AppStub(appID, instanceID).Delete(ctx); err != nil {
t.Fatal(testutil.FormatLogLine(2, "Delete(%v/%v) failed: %v [%v]", appID, instanceID, verror.ErrorID(err), err))
}
}
func KillApp(t *testing.T, ctx *context.T, appID, instanceID string) {
if err := AppStub(appID, instanceID).Kill(ctx, killTimeout); err != nil {
t.Fatal(testutil.FormatLogLine(2, "Kill(%v/%v) failed: %v [%v]", appID, instanceID, verror.ErrorID(err), err))
}
}
func DeleteApp(t *testing.T, ctx *context.T, appID, instanceID string) {
if err := AppStub(appID, instanceID).Delete(ctx); err != nil {
t.Fatal(testutil.FormatLogLine(2, "Delete(%v/%v) failed: %v [%v]", appID, instanceID, verror.ErrorID(err), err))
}
}
func RunApp(t *testing.T, ctx *context.T, appID, instanceID string) {
if err := AppStub(appID, instanceID).Run(ctx); err != nil {
t.Fatal(testutil.FormatLogLine(2, "Run(%v/%v) failed: %v [%v]", appID, instanceID, verror.ErrorID(err), err))
}
}
func RunAppExpectError(t *testing.T, ctx *context.T, appID, instanceID string, expectedError verror.ID) {
if err := AppStub(appID, instanceID).Run(ctx); err == nil || verror.ErrorID(err) != expectedError {
t.Fatal(testutil.FormatLogLine(2, "Run(%v/%v) expected to fail with %v, got %v [%v]", appID, instanceID, expectedError, verror.ErrorID(err), err))
}
}
func UpdateInstance(t *testing.T, ctx *context.T, appID, instanceID string) {
if err := AppStub(appID, instanceID).Update(ctx); err != nil {
t.Fatal(testutil.FormatLogLine(2, "Update(%v/%v) failed: %v [%v]", appID, instanceID, verror.ErrorID(err), err))
}
}
func UpdateInstanceExpectError(t *testing.T, ctx *context.T, appID, instanceID string, expectedError verror.ID) {
if err := AppStub(appID, instanceID).Update(ctx); err == nil || verror.ErrorID(err) != expectedError {
t.Fatal(testutil.FormatLogLine(2, "Update(%v/%v) expected to fail with %v, got %v [%v]", appID, instanceID, expectedError, verror.ErrorID(err), err))
}
}
func UpdateApp(t *testing.T, ctx *context.T, appID string) {
if err := AppStub(appID).Update(ctx); err != nil {
t.Fatal(testutil.FormatLogLine(2, "Update(%v) failed: %v [%v]", appID, verror.ErrorID(err), err))
}
}
func UpdateAppExpectError(t *testing.T, ctx *context.T, appID string, expectedError verror.ID) {
if err := AppStub(appID).Update(ctx); err == nil || verror.ErrorID(err) != expectedError {
t.Fatal(testutil.FormatLogLine(2, "Update(%v) expected to fail with %v, got %v [%v]", appID, expectedError, verror.ErrorID(err), err))
}
}
func RevertApp(t *testing.T, ctx *context.T, appID string) {
if err := AppStub(appID).Revert(ctx); err != nil {
t.Fatal(testutil.FormatLogLine(2, "Revert(%v) failed: %v [%v]", appID, verror.ErrorID(err), err))
}
}
func RevertAppExpectError(t *testing.T, ctx *context.T, appID string, expectedError verror.ID) {
if err := AppStub(appID).Revert(ctx); err == nil || verror.ErrorID(err) != expectedError {
t.Fatal(testutil.FormatLogLine(2, "Revert(%v) expected to fail with %v, got %v [%v]", appID, expectedError, verror.ErrorID(err), err))
}
}
func UninstallApp(t *testing.T, ctx *context.T, appID string) {
if err := AppStub(appID).Uninstall(ctx); err != nil {
t.Fatal(testutil.FormatLogLine(2, "Uninstall(%v) failed: %v [%v]", appID, verror.ErrorID(err), err))
}
}
func Debug(t *testing.T, ctx *context.T, nameComponents ...string) string {
dbg, err := AppStub(nameComponents...).Debug(ctx)
if err != nil {
t.Fatal(testutil.FormatLogLine(2, "Debug(%v) failed: %v [%v]", nameComponents, verror.ErrorID(err), err))
}
return dbg
}
func VerifyDeviceState(t *testing.T, ctx *context.T, want device.InstanceState, name string) string {
s, err := DeviceStub(name).Status(ctx)
if err != nil {
t.Fatal(testutil.FormatLogLine(2, "Status(%v) failed: %v [%v]", name, verror.ErrorID(err), err))
}
status, ok := s.(device.StatusDevice)
if !ok {
t.Fatal(testutil.FormatLogLine(2, "Status(%v) returned unknown type: %T", name, s))
}
if status.Value.State != want {
t.Fatal(testutil.FormatLogLine(2, "Status(%v) state: wanted %v, got %v", name, want, status.Value.State))
}
return status.Value.Version
}
func Status(t *testing.T, ctx *context.T, nameComponents ...string) device.Status {
s, err := AppStub(nameComponents...).Status(ctx)
if err != nil {
t.Error(testutil.FormatLogLine(3, "Status(%v) failed: %v [%v]", nameComponents, verror.ErrorID(err), err))
}
return s
}
func VerifyState(t *testing.T, ctx *context.T, want interface{}, nameComponents ...string) string {
s := Status(t, ctx, nameComponents...)
var (
state interface{}
version string
)
switch s := s.(type) {
case device.StatusInstance:
state = s.Value.State
version = s.Value.Version
case device.StatusInstallation:
state = s.Value.State
version = s.Value.Version
default:
t.Error(testutil.FormatLogLine(2, "Status(%v) returned unknown type: %T", nameComponents, s))
}
if state != want {
t.Error(testutil.FormatLogLine(2, "Status(%v) state: wanted %v (%T), got %v (%T)", nameComponents, want, want, state, state))
}
return version
}
func WaitForState(t *testing.T, ctx *context.T, want interface{}, nameComponents ...string) {
timeOut := time.After(30 * time.Second)
for {
s, err := AppStub(nameComponents...).Status(ctx)
// err may be non-nil when the app state cannot be determined.
// This can happen as a result of
// getInstanceState/getInstallationState not being thread-safe
// when the app state is changing. For such cases, just retry.
if err == nil {
var state interface{}
switch s := s.(type) {
case device.StatusInstance:
state = s.Value.State
case device.StatusInstallation:
state = s.Value.State
default:
t.Error(testutil.FormatLogLine(2, "Status(%v) returned unknown type: %T", nameComponents, s))
}
if state == want {
return
}
}
select {
case <-timeOut:
t.Fatal(testutil.FormatLogLine(2, "Timed out waiting for %v to reach state %v", nameComponents, want))
case <-time.After(time.Millisecond):
// Try again.
}
}
}
// Code to make Association lists sortable.
type byIdentity []device.Association
func (a byIdentity) Len() int { return len(a) }
func (a byIdentity) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a byIdentity) Less(i, j int) bool { return a[i].IdentityName < a[j].IdentityName }
func CompareAssociations(t *testing.T, got, expected []device.Association) {
sort.Sort(byIdentity(got))
sort.Sort(byIdentity(expected))
if !reflect.DeepEqual(got, expected) {
t.Errorf("ListAssociations() got %v, expected %v", got, expected)
}
}
// GenerateSuidHelperScript builds a script to execute the test target as
// a suidhelper instance and returns the path to the script.
func GenerateSuidHelperScript(t *testing.T, root string) string {
output := "#!" + impl.ShellPath + "\n"
output += "V23_SUIDHELPER_TEST=1"
output += " "
output += "exec " + os.Args[0] + " -minuid=1 -test.run=TestSuidHelper \"$@\""
output += "\n"
logger.Global().VI(1).Infof("script\n%s", output)
if err := os.MkdirAll(root, 0755); err != nil {
t.Fatal(testutil.FormatLogLine(2, "MkdirAll failed: %v", err))
}
path := filepath.Join(root, "helper.sh")
if err := ioutil.WriteFile(path, []byte(output), 0755); err != nil {
t.Fatal(testutil.FormatLogLine(2, "WriteFile(%v) failed: %v", path, err))
}
return path
}
// GenerateRestarter creates a simple script that acts as the restarter
// for tests. It blackholes arguments meant for the restarter.
func GenerateRestarter(t *testing.T, root string) string {
output := "#!" + impl.ShellPath + "\n" +
`
ARGS=$*
for ARG in ${ARGS[@]}; do
if [[ ${ARG} = -- ]]; then
ARGS=(${ARGS[@]/$ARG})
break
elif [[ ${ARG} == --* ]]; then
ARGS=(${ARGS[@]/$ARG})
else
break
fi
done
exec ${ARGS[@]}
`
if err := os.MkdirAll(root, 0755); err != nil {
t.Fatal(testutil.FormatLogLine(2, "MkdirAll failed: %v", err))
}
path := filepath.Join(root, "agenthelper.sh")
if err := ioutil.WriteFile(path, []byte(output), 0755); err != nil {
t.Fatal(testutil.FormatLogLine(2, "WriteFile(%v) failed: %v", path, err))
}
return path
}
func CtxWithNewPrincipal(t *testing.T, ctx *context.T, idp *testutil.IDProvider, extension string) *context.T {
ret, err := v23.WithPrincipal(ctx, testutil.NewPrincipal())
if err != nil {
t.Fatal(testutil.FormatLogLine(2, "v23.WithPrincipal failed: %v", err))
}
if err := idp.Bless(v23.GetPrincipal(ret), extension); err != nil {
t.Fatal(testutil.FormatLogLine(2, "idp.Bless(?, %q) failed: %v", extension, err))
}
return ret
}
// CreatePrincipal sets up a principal in a temporary directory (to be cleaned
// up by the shell at the end) and returns that directory.
func CreatePrincipal(t *testing.T, sh *v23test.Shell) string {
principalDir := sh.MakeTempDir()
if _, err := vsecurity.CreatePersistentPrincipal(principalDir, nil); err != nil {
t.Fatal(err)
}
return principalDir
}
// TODO(rjkroege): This helper is generally useful. Use it to reduce
// boilerplate across all device manager tests.
func StartupHelper(t *testing.T) (func(), *context.T, *v23test.Shell, *application.Envelope, string, string, *testutil.IDProvider) {
ctx, shutdown := test.V23Init()
// Make a new identity context.
idp := testutil.NewIDProvider("root")
ctx = CtxWithNewPrincipal(t, ctx, idp, "self")
sh, deferFn := servicetest.CreateShellAndMountTable(t, ctx)
// Set up mock application and binary repositories.
envelope, envCleanup := StartMockRepos(t, ctx)
root, rootCleanup := servicetest.SetupRootDir(t, "devicemanager")
if err := versioning.SaveCreatorInfo(ctx, root); err != nil {
t.Fatal(testutil.FormatLogLine(2, "SaveCreatorInfo failed: %v", err))
}
// Create a script wrapping the test target that implements suidhelper.
helperPath := GenerateSuidHelperScript(t, root)
return func() {
rootCleanup()
envCleanup()
deferFn()
shutdown()
}, ctx, sh, envelope, root, helperPath, idp
}
type GlobTestVector struct {
Name, Pattern string
Expected []string
}
type globTestRegexHelper struct {
logFileTimeStampRE *regexp.Regexp
logFileTrimInfoRE *regexp.Regexp
logFileRemoveErrorFatalWarningRE *regexp.Regexp
statsTrimRE *regexp.Regexp
}
func NewGlobTestRegexHelper(appName string) *globTestRegexHelper {
return &globTestRegexHelper{
logFileTimeStampRE: regexp.MustCompile("(STDOUT|STDERR)-[0-9]+$"),
logFileTrimInfoRE: regexp.MustCompile(appName + `\..*\.INFO\.[0-9.-]+$`),
logFileRemoveErrorFatalWarningRE: regexp.MustCompile("(ERROR|FATAL|WARNING)"),
statsTrimRE: regexp.MustCompile("/stats/(rpc|system(/start-time.*)?)$"),
}
}
// VerifyGlob verifies that for each GlobTestVector instance that the
// pattern returns the expected matches.
func VerifyGlob(t *testing.T, ctx *context.T, appName string, testcases []GlobTestVector, res *globTestRegexHelper) {
for _, tc := range testcases {
results, _, err := testutil.GlobName(ctx, tc.Name, tc.Pattern)
if err != nil {
t.Error(testutil.FormatLogLine(2, "unexpected glob error for (%q, %q): %v", tc.Name, tc.Pattern, err))
continue
}
filteredResults := []string{}
for _, name := range results {
// Keep only the stats object names that match this RE.
if strings.Contains(name, "/stats/") && !res.statsTrimRE.MatchString(name) {
continue
}
// Remove ERROR, WARNING, FATAL log files because
// they're not consistently there.
if res.logFileRemoveErrorFatalWarningRE.MatchString(name) {
continue
}
name = res.logFileTimeStampRE.ReplaceAllString(name, "$1-<timestamp>")
name = res.logFileTrimInfoRE.ReplaceAllString(name, appName+".<*>.INFO.<timestamp>")
filteredResults = append(filteredResults, name)
}
sort.Strings(filteredResults)
sort.Strings(tc.Expected)
if !reflect.DeepEqual(filteredResults, tc.Expected) {
t.Error(testutil.FormatLogLine(2, "unexpected result for (%q, %q). Got %q, want %q", tc.Name, tc.Pattern, filteredResults, tc.Expected))
}
}
}
// VerifyFailGlob verifies that for each GlobTestVector instance that the
// pattern returns no matches.
func VerifyFailGlob(t *testing.T, ctx *context.T, testcases []GlobTestVector) {
for _, tc := range testcases {
results, _, _ := testutil.GlobName(ctx, tc.Name, tc.Pattern)
if len(results) != 0 {
t.Error(testutil.FormatLogLine(2, "verifyFailGlob should have failed for %q, %q", tc.Name, tc.Pattern))
}
}
}
// VerifyLog calls Size() on a selection of log file objects to
// demonstrate that the log files are accessible and have been written by
// the application.
func VerifyLog(t *testing.T, ctx *context.T, nameComponents ...string) {
a := nameComponents
pattern, prefix := a[len(a)-1], a[:len(a)-1]
path := naming.Join(prefix...)
files, _, err := testutil.GlobName(ctx, path, pattern)
if err != nil {
t.Error(testutil.FormatLogLine(2, "unexpected glob error: %v", err))
}
if want, got := 4, len(files); got < want {
t.Error(testutil.FormatLogLine(2, "Unexpected number of matches. Got %d, want at least %d", got, want))
}
for _, file := range files {
name := naming.Join(path, file)
c := logreader.LogFileClient(name)
if _, err := c.Size(ctx); err != nil {
t.Error(testutil.FormatLogLine(2, "Size(%q) failed: %v", name, err))
}
}
}
// VerifyStatsValues call Value() on some of the stats objects to prove
// that they are correctly being proxied to the device manager.
func VerifyStatsValues(t *testing.T, ctx *context.T, nameComponents ...string) {
a := nameComponents
pattern, prefix := a[len(a)-1], a[:len(a)-1]
path := naming.Join(prefix...)
objects, _, err := testutil.GlobName(ctx, path, pattern)
if err != nil {
t.Error(testutil.FormatLogLine(2, "unexpected glob error: %v", err))
}
if want, got := 2, len(objects); got != want {
t.Error(testutil.FormatLogLine(2, "Unexpected number of matches. Got %d, want %d", got, want))
}
for _, obj := range objects {
name := naming.Join(path, obj)
c := stats.StatsClient(name)
if _, err := c.Value(ctx); err != nil {
t.Error(testutil.FormatLogLine(2, "Value(%q) failed: %v", name, err))
}
}
}
// VerifyPProfCmdLine calls CmdLine() on the pprof object to validate
// that it the proxy correctly accessess pprof names.
func VerifyPProfCmdLine(t *testing.T, ctx *context.T, appName string, nameComponents ...string) {
name := naming.Join(nameComponents...)
c := pprof.PProfClient(name)
v, err := c.CmdLine(ctx)
if err != nil {
t.Error(testutil.FormatLogLine(2, "CmdLine(%q) failed: %v", name, err))
}
if len(v) == 0 {
t.Errorf("Unexpected empty cmdline: %v", v)
}
if got, want := filepath.Base(v[0]), appName; got != want {
t.Error(testutil.FormatLogLine(2, "Unexpected value for argv[0]. Got %v, want %v", got, want))
}
}
func VerifyNoRunningProcesses(t *testing.T) {
if impl.RunningChildrenProcesses() {
t.Errorf("device manager incorrectly terminating with child processes still running")
}
}
func SetNamespaceRootsForUnclaimedDevice(ctx *context.T) (*context.T, error) {
origroots := v23.GetNamespace(ctx).Roots()
roots := make([]string, len(origroots))
for i, orig := range origroots {
addr, suffix := naming.SplitAddressName(orig)
origep, err := naming.ParseEndpoint(addr)
if err != nil {
return nil, err
}
ep := naming.FormatEndpoint(
origep.Addr().Network(),
origep.Addr().String(),
origep.RoutingID,
naming.ServesMountTable(origep.ServesMountTable))
roots[i] = naming.JoinAddressName(ep, suffix)
}
ctx.Infof("Changing namespace roots from %v to %v", origroots, roots)
ctx, _, err := v23.WithNewNamespace(ctx, roots...)
return ctx, err
}
func UserName(t *testing.T) string {
u, err := user.Current()
if err != nil {
t.Fatal(testutil.FormatLogLine(2, "user.Current() failed: %v", err))
}
return u.Username
}
func StartRealBinaryRepository(t *testing.T, ctx *context.T, von string) func() {
rootDir, err := binarylib.SetupRootDir("")
if err != nil {
t.Fatal(testutil.FormatLogLine(2, "binarylib.SetupRootDir failed: %v", err))
}
state, err := binarylib.NewState(rootDir, "", 3)
if err != nil {
t.Fatal(testutil.FormatLogLine(2, "binarylib.NewState failed: %v", err))
}
d, err := binarylib.NewDispatcher(ctx, state)
if err != nil {
t.Fatal(testutil.FormatLogLine(2, "server.NewDispatcher failed: %v", err))
}
ctx, cancel := context.WithCancel(ctx)
ctx, server, err := v23.WithNewDispatchingServer(ctx, von, d)
if err != nil {
t.Fatal(testutil.FormatLogLine(2, "server.ServeDispatcher failed: %v", err))
}
WaitForMount(t, ctx, von, server)
return func() {
cancel()
<-server.Closed()
if err := os.RemoveAll(rootDir); err != nil {
t.Fatal(testutil.FormatLogLine(2, "os.RemoveAll(%q) failed: %v", rootDir, err))
}
}
}
func GetPid(t *testing.T, ctx *context.T, appID, instanceID string) int {
name := naming.Join("dm", "apps/"+appID+"/"+instanceID+"/stats/system/pid")
c := stats.StatsClient(name)
v, err := c.Value(ctx)
if err != nil {
t.Fatal(testutil.FormatLogLine(2, "Value() failed: %v", err))
}
var pid int
if err := v.ToValue(&pid); err != nil {
t.Fatal(testutil.FormatLogLine(2, "ToValue() failed: %v", err))
}
return pid
}
// PollingWait polls a given process to make sure that it has exited
// before continuing or fails with a time-out.
func PollingWait(t *testing.T, pid int) {
timeOut := time.After(30 * time.Second)
for syscall.Kill(pid, 0) == nil {
select {
case <-timeOut:
syscall.Kill(pid, 9)
t.Fatal(testutil.FormatLogLine(2, "Timed out waiting for PID %v to terminate", pid))
case <-time.After(time.Millisecond):
// Try again.
}
}
}
type Fatalist interface {
Fatal(args ...interface{})
Fatalf(format string, args ...interface{})
}
// WaitForMount waits (with a reasonable timeout) for the given server's
// endpoint to be mounted under the given name. Since ctx.WithNewServer and
// ctx.WithNewDispatchingServer publish the names asynchronously, use
// WaitForMount if you need a guarantee that the publish has happened.
func WaitForMount(f Fatalist, ctx *context.T, name string, server rpc.Server) {
var serverEPName string
if serverEPS := server.Status().Endpoints; len(serverEPS) != 1 {
f.Fatalf("Expected 1 server endpoint, found: %v", serverEPS)
} else {
serverEPName = naming.JoinAddressName(serverEPS[0].String(), "")
}
timeout := time.After(time.Minute)
for {
// NOTE(caprita): We could have also used server.Status().Mounts
// to save the trouble of resolving the mounttable, but given
// the churn in the server logic, it's safer to just look for
// the 'ground truth' in the mounttable.
eps, err := v23.GetNamespace(ctx).Resolve(ctx, name)
if err == nil {
for _, ep := range eps.Names() {
if ep == serverEPName {
return
}
}
}
select {
case <-timeout:
f.Fatalf("Timed out waiting for %v to appear in mounttable under %v; found: %v (error: %v)", serverEPName, name, eps, err)
default:
}
time.Sleep(10 * time.Millisecond)
}
}