blob: 4ca86c922cfd96b3d4713fbf9b5c181137428933 [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 main_test
import (
"fmt"
"io/ioutil"
"log"
"os"
"path/filepath"
"testing"
"time"
"v.io/v23/context"
"v.io/v23/glob"
"v.io/v23/naming"
"v.io/v23/rpc"
"v.io/v23/security"
"v.io/v23/security/access"
"v.io/v23/services/application"
"v.io/v23/services/binary"
"v.io/v23/services/device"
"v.io/v23/services/repository"
"v.io/x/ref/services/internal/binarylib"
"v.io/x/ref/services/internal/packages"
"v.io/x/ref/services/internal/servicetest"
)
type mockDeviceInvoker struct {
tape *servicetest.Tape
t *testing.T
}
// Mock ListAssociations
type ListAssociationResponse struct {
na []device.Association
err error
}
func (mdi *mockDeviceInvoker) ListAssociations(ctx *context.T, _ rpc.ServerCall) (associations []device.Association, err error) {
ctx.VI(2).Infof("ListAssociations() was called")
ir := mdi.tape.Record("ListAssociations")
r := ir.(ListAssociationResponse)
return r.na, r.err
}
// Mock AssociateAccount
type AddAssociationStimulus struct {
fun string
identityNames []string
accountName string
}
// simpleCore implements the core of all mock methods that take
// arguments and return error.
func (mdi *mockDeviceInvoker) simpleCore(callRecord interface{}, name string) error {
ri := mdi.tape.Record(callRecord)
switch r := ri.(type) {
case nil:
return nil
case error:
return r
}
log.Fatalf("%s (mock) response %v is of bad type", name, ri)
return nil
}
func (mdi *mockDeviceInvoker) AssociateAccount(_ *context.T, _ rpc.ServerCall, identityNames []string, accountName string) error {
return mdi.simpleCore(AddAssociationStimulus{"AssociateAccount", identityNames, accountName}, "AssociateAccount")
}
func (mdi *mockDeviceInvoker) Claim(_ *context.T, _ rpc.ServerCall, pairingToken string) error {
return mdi.simpleCore("Claim", "Claim")
}
func (*mockDeviceInvoker) Describe(*context.T, rpc.ServerCall) (device.Description, error) {
return device.Description{}, nil
}
func (*mockDeviceInvoker) IsRunnable(_ *context.T, _ rpc.ServerCall, description binary.Description) (bool, error) {
return false, nil
}
func (*mockDeviceInvoker) Reset(_ *context.T, _ rpc.ServerCall, deadline uint64) error { return nil }
// Mock Install
type InstallStimulus struct {
fun string
appName string
config device.Config
packages application.Packages
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>". The override packages will have the
// key "overridepackage/<package name>".
files map[string]int64
}
type InstallResponse struct {
appId string
err error
}
const (
// If provided with this app name, the mock device manager skips trying
// to fetch the envelope from the name.
appNameNoFetch = "skip-envelope-fetching"
// If provided with a fetcheable app name, the mock device manager sets
// the app name in the stimulus to this constant.
appNameAfterFetch = "envelope-fetched"
// The mock device manager sets the binary name in the envelope in the
// stimulus to this constant.
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 fetchPackageSize(ctx *context.T, pkgVON string) (int64, error) {
dir, err := ioutil.TempDir("", "package")
if err != nil {
return 0, fmt.Errorf("failed to create temp package dir: %v", err)
}
defer os.RemoveAll(dir)
tmpFile := filepath.Join(dir, "downloaded")
if err := binarylib.DownloadToFile(ctx, pkgVON, tmpFile); err != nil {
return 0, fmt.Errorf("DownloadToFile failed: %v", err)
}
dst := filepath.Join(dir, "install")
if err := packages.Install(tmpFile, dst); err != nil {
return 0, fmt.Errorf("packages.Install failed: %v", err)
}
return packageSize(dst), nil
}
func (mdi *mockDeviceInvoker) Install(ctx *context.T, _ rpc.ServerCall, appName string, config device.Config, packages application.Packages) (string, error) {
is := InstallStimulus{"Install", appName, config, packages, application.Envelope{}, nil}
if appName != appNameNoFetch {
// Fetch the envelope and record it in the stimulus.
envelope, err := repository.ApplicationClient(appName).Match(ctx, []string{"test"})
if err != nil {
return "", err
}
binaryName := envelope.Binary.File
envelope.Binary.File = binaryNameAfterFetch
is.appName = appNameAfterFetch
is.files = make(map[string]int64)
// Fetch the binary and record its size in the stimulus.
data, mediaInfo, err := binarylib.Download(ctx, binaryName)
if err != nil {
return "", err
}
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 {
size, err := fetchPackageSize(ctx, pkgVON.File)
if err != nil {
return "", err
}
is.files[naming.Join("packages", pkgLocalName)] = size
}
envelope.Packages = nil
for pkgLocalName, pkg := range packages {
size, err := fetchPackageSize(ctx, pkg.File)
if err != nil {
return "", err
}
is.files[naming.Join("overridepackages", pkgLocalName)] = size
}
is.packages = nil
is.envelope = envelope
}
r := mdi.tape.Record(is).(InstallResponse)
return r.appId, r.err
}
func (mdi *mockDeviceInvoker) Run(*context.T, rpc.ServerCall) error {
return mdi.simpleCore("Run", "Run")
}
func (mdi *mockDeviceInvoker) Revert(*context.T, rpc.ServerCall) error {
return mdi.simpleCore("Revert", "Revert")
}
type InstantiateResponse struct {
err error
instanceID string
}
func (mdi *mockDeviceInvoker) Instantiate(*context.T, rpc.StreamServerCall) (string, error) {
ir := mdi.tape.Record("Instantiate")
r := ir.(InstantiateResponse)
return r.instanceID, r.err
}
type KillStimulus struct {
fun string
delta time.Duration
}
func (mdi *mockDeviceInvoker) Kill(_ *context.T, _ rpc.ServerCall, delta time.Duration) error {
return mdi.simpleCore(KillStimulus{"Kill", delta}, "Kill")
}
func (mdi *mockDeviceInvoker) Delete(*context.T, rpc.ServerCall) error {
return mdi.simpleCore("Delete", "Delete")
}
func (*mockDeviceInvoker) Uninstall(*context.T, rpc.ServerCall) error { return nil }
func (mdi *mockDeviceInvoker) Update(*context.T, rpc.ServerCall) error {
return mdi.simpleCore("Update", "Update")
}
func (*mockDeviceInvoker) UpdateTo(*context.T, rpc.ServerCall, string) error { return nil }
// Mock Permissions getting and setting
type GetPermissionsResponse struct {
perms access.Permissions
version string
err error
}
type SetPermissionsStimulus struct {
fun string
perms access.Permissions
version string
}
func (mdi *mockDeviceInvoker) SetPermissions(_ *context.T, _ rpc.ServerCall, perms access.Permissions, version string) error {
return mdi.simpleCore(SetPermissionsStimulus{"SetPermissions", perms, version}, "SetPermissions")
}
func (mdi *mockDeviceInvoker) GetPermissions(*context.T, rpc.ServerCall) (access.Permissions, string, error) {
ir := mdi.tape.Record("GetPermissions")
r := ir.(GetPermissionsResponse)
return r.perms, r.version, r.err
}
func (mdi *mockDeviceInvoker) Debug(*context.T, rpc.ServerCall) (string, error) {
ir := mdi.tape.Record("Debug")
r := ir.(string)
return r, nil
}
func (mdi *mockDeviceInvoker) Status(*context.T, rpc.ServerCall) (device.Status, error) {
ir := mdi.tape.Record("Status")
switch r := ir.(type) {
case device.Status:
return r, nil
case error:
return nil, r
default:
log.Fatalf("Status (mock) response %v is of bad type", ir)
return nil, nil
}
}
type GlobStimulus struct {
pattern string
}
type GlobResponse struct {
results []string
err error
}
func (mdi *mockDeviceInvoker) Glob__(_ *context.T, call rpc.GlobServerCall, g *glob.Glob) error {
gs := GlobStimulus{g.String()}
gr := mdi.tape.Record(gs).(GlobResponse)
for _, r := range gr.results {
call.SendStream().Send(naming.GlobReplyEntry{Value: naming.MountEntry{Name: r}})
}
return gr.err
}
type dispatcher struct {
tapes *servicetest.TapeMap
t *testing.T
}
func newDispatcher(t *testing.T, tapes *servicetest.TapeMap) rpc.Dispatcher {
return &dispatcher{tapes: tapes, t: t}
}
func (d *dispatcher) Lookup(_ *context.T, suffix string) (interface{}, security.Authorizer, error) {
return &mockDeviceInvoker{tape: d.tapes.ForSuffix(suffix), t: d.t}, nil, nil
}