Merge "ref: Added lame duck timeout option"
diff --git a/cmd/sb/doc.go b/cmd/sb/doc.go
index 691b2fe..76a0dc5 100644
--- a/cmd/sb/doc.go
+++ b/cmd/sb/doc.go
@@ -10,8 +10,9 @@
// The user can then enter the following at the command line:
// 1. dump - to get a dump of the database
// 2. a syncbase select statement - which is executed and results printed to stdout
-// 3. make-demo - to create demo tables in the database to experiment with, equivalent to -make-demo flag
-// 4. exit (or quit) - to exit the program
+// 3. a syncbase delete statement - which is executed to delete k/v pairs from a table
+// 4. make-demo - to create demo tables in the database to experiment with, equivalent to -make-demo flag
+// 5. exit (or quit) - to exit the program
//
// When the shell is running non-interactively (stdin not connected to a tty),
// errors cause the shell to exit with a non-zero status.
@@ -50,6 +51,12 @@
// 002002,"{CustId: 2, InvoiceNum: 1002, Amount: 243, ShipTo: {Street: ""888 Any St."", City: ""Collins"", State: ""IA"", Zip: ""50055""}}"
// 002003,"{CustId: 2, InvoiceNum: 1004, Amount: 787, ShipTo: {Street: ""999 Any St."", City: ""Collins"", State: ""IA"", Zip: ""50055""}}"
// 002004,"{CustId: 2, InvoiceNum: 1006, Amount: 88, ShipTo: {Street: ""101010 Any St."", City: ""Collins"", State: ""IA"", Zip: ""50055""}}"
+// ? delete from Customers where k = "001002";
+// +-------+
+// | Count |
+// +-------+
+// | 1 |
+// +-------+
// ? exit;
// >
package main
diff --git a/cmd/sb/shell.go b/cmd/sb/shell.go
index 1469503..e768e84 100644
--- a/cmd/sb/shell.go
+++ b/cmd/sb/shell.go
@@ -127,7 +127,7 @@
case "make-demo":
// TODO(jkline): add an "Are you sure prompt" to give the user a 2nd chance.
err = makeDemoDB(ctx, env.Stdout, d)
- case "select":
+ case "select", "delete":
err = queryExec(ctx, env.Stdout, d, q)
case "destroy":
if len(tq) == 3 {
diff --git a/cmd/servicerunner/servicerunner_test.go b/cmd/servicerunner/servicerunner_test.go
index c3d3a31..c6a0e0a 100644
--- a/cmd/servicerunner/servicerunner_test.go
+++ b/cmd/servicerunner/servicerunner_test.go
@@ -23,12 +23,6 @@
"v.io/x/ref"
)
-// We provide our own TestMain, rather than allowing jiri test generate to
-// create one for us, to ensure all files require the "wspr" build tag.
-func TestMain(m *testing.M) {
- os.Exit(m.Run())
-}
-
func TestServiceRunner(t *testing.T) {
ref.EnvClearCredentials()
tmpdir, err := ioutil.TempDir("", "servicerunner_test")
diff --git a/examples/rps/internal/common.go b/examples/rps/internal/common.go
index c777878..ca771a7 100644
--- a/examples/rps/internal/common.go
+++ b/examples/rps/internal/common.go
@@ -30,8 +30,8 @@
}
// FindJudge returns a random rock-paper-scissors judge from the mount table.
-func FindJudge(ctx *context.T) (string, error) {
- judges, err := findAll(ctx, "judge")
+func FindJudge(ctx *context.T, prefix string) (string, error) {
+ judges, err := findAll(ctx, prefix, "judge")
if err != nil {
return "", err
}
@@ -42,8 +42,8 @@
}
// FindPlayer returns a random rock-paper-scissors player from the mount table.
-func FindPlayer(ctx *context.T) (string, error) {
- players, err := findAll(ctx, "player")
+func FindPlayer(ctx *context.T, prefix string) (string, error) {
+ players, err := findAll(ctx, prefix, "player")
if err != nil {
return "", err
}
@@ -55,18 +55,18 @@
// FindScoreKeepers returns all the rock-paper-scissors score keepers from the
// mount table.
-func FindScoreKeepers(ctx *context.T) ([]string, error) {
- sKeepers, err := findAll(ctx, "scorekeeper")
+func FindScoreKeepers(ctx *context.T, prefix string) ([]string, error) {
+ sKeepers, err := findAll(ctx, prefix, "scorekeeper")
if err != nil {
return nil, err
}
return sKeepers, nil
}
-func findAll(ctx *context.T, t string) ([]string, error) {
+func findAll(ctx *context.T, prefix, t string) ([]string, error) {
start := time.Now()
ns := v23.GetNamespace(ctx)
- c, err := ns.Glob(ctx, "rps/"+t+"/*")
+ c, err := ns.Glob(ctx, naming.Join(prefix, "rps", t, "*"))
if err != nil {
ctx.Infof("mt.Glob failed: %v", err)
return nil, err
@@ -77,7 +77,8 @@
case *naming.GlobReplyError:
ctx.VI(1).Infof("findAll(%q) error for %q: %v", t, v.Value.Name, v.Value.Error)
case *naming.GlobReplyEntry:
- servers = append(servers, v.Value.Name)
+
+ servers = append(servers, strings.TrimPrefix(v.Value.Name, naming.Clean(prefix)+"/"))
}
}
ctx.VI(1).Infof("findAll(%q) elapsed: %s", t, time.Now().Sub(start))
diff --git a/examples/rps/rpsbot/doc.go b/examples/rps/rpsbot/doc.go
index 2a663a1..8d7e8ee 100644
--- a/examples/rps/rpsbot/doc.go
+++ b/examples/rps/rpsbot/doc.go
@@ -16,6 +16,10 @@
The rpsbot flags are:
-acl-file=
File containing JSON-encoded Permissions.
+ -mount-prefix=vlab
+ The mount prefix to use. The published names will be
+ <mount-prefix>/rps/player/<name>, <mount-prefix>/rps/judge/<name>, and
+ <mount-prefix>/rps/scorekeeper/<name>.
-name=
Identifier to publish as (defaults to principal's blessing names).
-num-games=-1
diff --git a/examples/rps/rpsbot/judge.go b/examples/rps/rpsbot/judge.go
index f63e15f..78d855d 100644
--- a/examples/rps/rpsbot/judge.go
+++ b/examples/rps/rpsbot/judge.go
@@ -12,6 +12,7 @@
"time"
"v.io/v23/context"
+ "v.io/v23/naming"
"v.io/x/ref/examples/rps"
"v.io/x/ref/examples/rps/internal"
@@ -230,7 +231,7 @@
// Send the score card to the score keepers.
scoreCtx, cancel := context.WithTimeout(ctx, 30*time.Second)
defer cancel()
- keepers, err := internal.FindScoreKeepers(scoreCtx)
+ keepers, err := internal.FindScoreKeepers(scoreCtx, mountPrefix)
if err != nil || len(keepers) == 0 {
ctx.Infof("No score keepers: %v", err)
return
@@ -310,8 +311,7 @@
func (j *Judge) sendScore(ctx *context.T, address string, score rps.ScoreCard, wg *sync.WaitGroup) error {
defer wg.Done()
- k := rps.RockPaperScissorsClient(address)
- if err := k.Record(ctx, score); err != nil {
+ if err := rps.ScoreKeeperClient(naming.Join(mountPrefix, address)).Record(ctx, score); err != nil {
logger.Global().Infof("Record: %v", err)
return err
}
diff --git a/examples/rps/rpsbot/main.go b/examples/rps/rpsbot/main.go
index b1283f4..8a493b0 100644
--- a/examples/rps/rpsbot/main.go
+++ b/examples/rps/rpsbot/main.go
@@ -14,9 +14,10 @@
"v.io/x/lib/cmdline"
- "v.io/v23/context"
-
"v.io/v23"
+ "v.io/v23/context"
+ "v.io/v23/naming"
+
"v.io/x/ref/examples/rps"
"v.io/x/ref/examples/rps/internal"
"v.io/x/ref/lib/signals"
@@ -28,12 +29,14 @@
var (
name, aclFile string
numGames int
+ mountPrefix string
)
func main() {
cmdRoot.Flags.StringVar(&name, "name", "", "Identifier to publish as (defaults to principal's blessing names).")
cmdRoot.Flags.StringVar(&aclFile, "acl-file", "", "File containing JSON-encoded Permissions.")
cmdRoot.Flags.IntVar(&numGames, "num-games", -1, "Number of games to play (-1 means unlimited).")
+ cmdRoot.Flags.StringVar(&mountPrefix, "mount-prefix", "vlab", "The mount prefix to use. The published names will be <mount-prefix>/rps/player/<name>, <mount-prefix>/rps/judge/<name>, and <mount-prefix>/rps/scorekeeper/<name>.")
cmdline.HideGlobalFlagsExcept()
cmdline.Main(cmdRoot)
}
@@ -57,9 +60,9 @@
name = internal.CreateName(ctx)
}
names := []string{
- fmt.Sprintf("rps/judge/%s", name),
- fmt.Sprintf("rps/player/%s", name),
- fmt.Sprintf("rps/scorekeeper/%s", name),
+ naming.Join(mountPrefix, "rps", "judge", name),
+ naming.Join(mountPrefix, "rps", "player", name),
+ naming.Join(mountPrefix, "rps", "scorekeeper", name),
}
ctx, server, err := v23.WithNewServer(ctx, names[0], rps.RockPaperScissorsServer(rpsService), auth)
if err != nil {
diff --git a/examples/rps/rpsbot/player.go b/examples/rps/rpsbot/player.go
index 592ffcb..aaa4488 100644
--- a/examples/rps/rpsbot/player.go
+++ b/examples/rps/rpsbot/player.go
@@ -9,6 +9,7 @@
"time"
"v.io/v23/context"
+ "v.io/v23/naming"
"v.io/x/ref/examples/rps"
"v.io/x/ref/examples/rps/internal"
@@ -44,7 +45,7 @@
}
func (p *Player) InitiateGame(ctx *context.T) error {
- judge, err := internal.FindJudge(ctx)
+ judge, err := internal.FindJudge(ctx, mountPrefix)
if err != nil {
ctx.Infof("FindJudge: %v", err)
return err
@@ -57,7 +58,7 @@
ctx.VI(1).Infof("Created gameID %q on %q", gameID, judge)
for {
- opponent, err := internal.FindPlayer(ctx)
+ opponent, err := internal.FindPlayer(ctx, mountPrefix)
if err != nil {
ctx.Infof("FindPlayer: %v", err)
return err
@@ -81,21 +82,19 @@
return nil
}
-func (p *Player) createGame(ctx *context.T, server string) (rps.GameId, rps.GameOptions, error) {
- j := rps.RockPaperScissorsClient(server)
+func (p *Player) createGame(ctx *context.T, judge string) (rps.GameId, rps.GameOptions, error) {
numRounds := 3 + rand.Intn(3)
gameType := rps.Classic
if rand.Intn(2) == 1 {
gameType = rps.LizardSpock
}
gameOpts := rps.GameOptions{NumRounds: int32(numRounds), GameType: gameType}
- gameId, err := j.CreateGame(ctx, gameOpts)
+ gameId, err := rps.JudgeClient(naming.Join(mountPrefix, judge)).CreateGame(ctx, gameOpts)
return gameId, gameOpts, err
}
func (p *Player) sendChallenge(ctx *context.T, opponent, judge string, gameID rps.GameId, gameOpts rps.GameOptions) error {
- o := rps.RockPaperScissorsClient(opponent)
- return o.Challenge(ctx, judge, gameID, gameOpts)
+ return rps.PlayerClient(naming.Join(mountPrefix, opponent)).Challenge(ctx, judge, gameID, gameOpts)
}
// challenge receives an incoming challenge and starts to play a new game.
@@ -113,8 +112,7 @@
defer cancel()
p.gamesInProgress.Incr(1)
defer p.gamesInProgress.Incr(-1)
- j := rps.RockPaperScissorsClient(judge)
- game, err := j.Play(ctx, gameID)
+ game, err := rps.JudgeClient(naming.Join(mountPrefix, judge)).Play(ctx, gameID)
if err != nil {
return rps.PlayResult{}, err
}
diff --git a/examples/rps/rpsplayer/doc.go b/examples/rps/rpsplayer/doc.go
index 18d1d1a..e25f7de 100644
--- a/examples/rps/rpsplayer/doc.go
+++ b/examples/rps/rpsplayer/doc.go
@@ -15,6 +15,9 @@
The rpsplayer flags are:
-acl-file=
File containing JSON-encoded Permissions.
+ -mount-prefix=vlab
+ The mount prefix to use. The published name will be
+ <mount-prefix>/rps/player/<name>.
-name=
Identifier to publish as (defaults to principal's blessing names).
diff --git a/examples/rps/rpsplayer/main.go b/examples/rps/rpsplayer/main.go
index d388699..76551ec 100644
--- a/examples/rps/rpsplayer/main.go
+++ b/examples/rps/rpsplayer/main.go
@@ -29,11 +29,12 @@
_ "v.io/x/ref/runtime/factories/roaming"
)
-var name, aclFile string
+var name, aclFile, mountPrefix string
func main() {
cmdRoot.Flags.StringVar(&name, "name", "", "Identifier to publish as (defaults to principal's blessing names).")
cmdRoot.Flags.StringVar(&aclFile, "acl-file", "", "File containing JSON-encoded Permissions.")
+ cmdRoot.Flags.StringVar(&mountPrefix, "mount-prefix", "vlab", "The mount prefix to use. The published name will be <mount-prefix>/rps/player/<name>.")
cmdline.HideGlobalFlagsExcept()
cmdline.Main(cmdRoot)
}
@@ -125,7 +126,7 @@
if name == "" {
name = internal.CreateName(ctx)
}
- fullname := fmt.Sprintf("rps/player/%s", name)
+ fullname := naming.Join(mountPrefix, "rps", "player", name)
service := rps.PlayerServer(&impl{ch: ch})
auth := internal.NewAuthorizer(aclFile)
ctx, server, err := v23.WithNewServer(ctx, fullname, service, auth)
@@ -188,22 +189,19 @@
return nil
}
-func createGame(ctx *context.T, server string, opts rps.GameOptions) (rps.GameId, error) {
- j := rps.RockPaperScissorsClient(server)
- return j.CreateGame(ctx, opts)
+func createGame(ctx *context.T, judge string, opts rps.GameOptions) (rps.GameId, error) {
+ return rps.JudgeClient(naming.Join(mountPrefix, judge)).CreateGame(ctx, opts)
}
func sendChallenge(ctx *context.T, opponent, judge string, gameID rps.GameId, gameOpts rps.GameOptions) error {
- o := rps.RockPaperScissorsClient(opponent)
- return o.Challenge(ctx, judge, gameID, gameOpts)
+ return rps.PlayerClient(naming.Join(mountPrefix, opponent)).Challenge(ctx, judge, gameID, gameOpts)
}
func playGame(outer *context.T, judge string, gameID rps.GameId) (rps.PlayResult, error) {
ctx, cancel := context.WithTimeout(outer, 10*time.Minute)
defer cancel()
fmt.Println()
- j := rps.RockPaperScissorsClient(judge)
- game, err := j.Play(ctx, gameID)
+ game, err := rps.JudgeClient(naming.Join(mountPrefix, judge)).Play(ctx, gameID)
if err != nil {
return rps.PlayResult{}, err
}
@@ -294,7 +292,7 @@
func findAll(ctx *context.T, t string, out chan []string) {
ns := v23.GetNamespace(ctx)
var result []string
- c, err := ns.Glob(ctx, "rps/"+t+"/*")
+ c, err := ns.Glob(ctx, naming.Join(mountPrefix, "rps", t, "*"))
if err != nil {
ctx.Infof("ns.Glob failed: %v", err)
out <- result
@@ -306,7 +304,7 @@
fmt.Print("E")
case *naming.GlobReplyEntry:
fmt.Print(".")
- result = append(result, v.Value.Name)
+ result = append(result, strings.TrimPrefix(v.Value.Name, naming.Clean(mountPrefix)+"/"))
}
}
if len(result) == 0 {
diff --git a/examples/rps/rpsscorekeeper/doc.go b/examples/rps/rpsscorekeeper/doc.go
index 88ad4c1..0e9a6ea 100644
--- a/examples/rps/rpsscorekeeper/doc.go
+++ b/examples/rps/rpsscorekeeper/doc.go
@@ -16,6 +16,9 @@
The rpsscorekeeper flags are:
-acl-file=
File containing JSON-encoded Permissions.
+ -mount-prefix=vlab
+ The mount prefix to use. The published name will be
+ <mount-prefix>/rps/scorekeeper/<name>.
The global flags are:
-alsologtostderr=true
diff --git a/examples/rps/rpsscorekeeper/main.go b/examples/rps/rpsscorekeeper/main.go
index 3f4ae27..a35bb15 100644
--- a/examples/rps/rpsscorekeeper/main.go
+++ b/examples/rps/rpsscorekeeper/main.go
@@ -12,11 +12,12 @@
"v.io/x/lib/cmdline"
+ "v.io/v23"
"v.io/v23/context"
+ "v.io/v23/naming"
"v.io/v23/rpc"
"v.io/v23/security"
- "v.io/v23"
"v.io/x/ref/examples/rps"
"v.io/x/ref/examples/rps/internal"
"v.io/x/ref/lib/v23cmd"
@@ -24,10 +25,11 @@
_ "v.io/x/ref/runtime/factories/roaming"
)
-var aclFile string
+var aclFile, mountPrefix string
func main() {
cmdRoot.Flags.StringVar(&aclFile, "acl-file", "", "File containing JSON-encoded Permissions.")
+ cmdRoot.Flags.StringVar(&mountPrefix, "mount-prefix", "vlab", "The mount prefix to use. The published name will be <mount-prefix>/rps/scorekeeper/<name>.")
cmdline.HideGlobalFlagsExcept()
cmdline.Main(cmdRoot)
}
@@ -57,7 +59,7 @@
func runScoreKeeper(ctx *context.T, env *cmdline.Env, args []string) error {
ch := make(chan rps.ScoreCard)
rpsService := &impl{ch}
- name := fmt.Sprintf("rps/scorekeeper/%s", internal.CreateName(ctx))
+ name := naming.Join(mountPrefix, "rps", "scorekeeper", internal.CreateName(ctx))
service := rps.ScoreKeeperServer(rpsService)
authorizer := internal.NewAuthorizer(aclFile)
ctx, server, err := v23.WithNewServer(ctx, name, service, authorizer)
diff --git a/lib/security/serialization/serialization_test.go b/lib/security/serialization/serialization_test.go
index 4b7b58e..6b99196 100644
--- a/lib/security/serialization/serialization_test.go
+++ b/lib/security/serialization/serialization_test.go
@@ -13,7 +13,6 @@
"io"
"io/ioutil"
mrand "math/rand"
- "os"
"reflect"
"strings"
"testing"
@@ -23,12 +22,6 @@
"v.io/x/ref/test/testutil"
)
-// We call our own TestMain here because jiri test generate causes an import cycle
-// in this package.
-func TestMain(m *testing.M) {
- os.Exit(m.Run())
-}
-
type bufferCloser struct {
bytes.Buffer
}
diff --git a/lib/signals/signals_test.go b/lib/signals/signals_test.go
index 085a7f4..6e2c125 100644
--- a/lib/signals/signals_test.go
+++ b/lib/signals/signals_test.go
@@ -116,7 +116,7 @@
cmd, stdinPipe := startFn(t, sh, handleDefaults, false)
cmd.S.Expect("ready")
checkSignalIsDefault(t, syscall.SIGINT)
- syscall.Kill(cmd.Process().Pid, syscall.SIGINT)
+ cmd.Signal(syscall.SIGINT)
cmd.S.Expectf("received signal %s", syscall.SIGINT)
fmt.Fprintf(stdinPipe, "close\n")
cmd.Wait()
@@ -182,10 +182,10 @@
cmd, _ := startFn(t, sh, handleDefaults, true)
cmd.S.Expect("ready")
checkSignalIsDefault(t, syscall.SIGTERM)
- syscall.Kill(cmd.Process().Pid, syscall.SIGTERM)
+ cmd.Signal(syscall.SIGTERM)
cmd.S.Expectf("received signal %s", syscall.SIGTERM)
checkSignalIsDefault(t, syscall.SIGINT)
- syscall.Kill(cmd.Process().Pid, syscall.SIGINT)
+ cmd.Signal(syscall.SIGINT)
cmd.Wait()
checkExitStatus(t, cmd, DoubleStopExitCode)
}
@@ -200,7 +200,7 @@
cmd, stdinPipe := startFn(t, sh, handleDefaults, true)
cmd.S.Expect("ready")
checkSignalIsDefault(t, syscall.SIGTERM)
- syscall.Kill(cmd.Process().Pid, syscall.SIGTERM)
+ cmd.Signal(syscall.SIGTERM)
cmd.S.Expectf("received signal %s", syscall.SIGTERM)
fmt.Fprintf(stdinPipe, "stop\n")
cmd.Wait()
@@ -232,7 +232,7 @@
cmd, _ := startFn(t, sh, handleDefaults, true)
cmd.S.Expect("ready")
checkSignalIsNotDefault(t, syscall.SIGABRT)
- syscall.Kill(cmd.Process().Pid, syscall.SIGABRT)
+ cmd.Signal(syscall.SIGABRT)
cmd.Wait()
checkExitStatus(t, cmd, 2)
}
@@ -250,9 +250,9 @@
// Even if we ignore the channel that ShutdownOnSignals returns,
// sending two signals should still cause the process to exit.
checkSignalIsDefault(t, syscall.SIGTERM)
- syscall.Kill(cmd.Process().Pid, syscall.SIGTERM)
+ cmd.Signal(syscall.SIGTERM)
checkSignalIsDefault(t, syscall.SIGINT)
- syscall.Kill(cmd.Process().Pid, syscall.SIGINT)
+ cmd.Signal(syscall.SIGINT)
cmd.Wait()
checkExitStatus(t, cmd, DoubleStopExitCode)
}
@@ -266,7 +266,7 @@
cmd, stdinPipe := startFn(t, sh, handleCustom, true)
cmd.S.Expect("ready")
checkSignalIsNotDefault(t, syscall.SIGABRT)
- syscall.Kill(cmd.Process().Pid, syscall.SIGABRT)
+ cmd.Signal(syscall.SIGABRT)
cmd.S.Expectf("received signal %s", syscall.SIGABRT)
fmt.Fprintf(stdinPipe, "stop\n")
cmd.Wait()
@@ -284,7 +284,7 @@
cmd, stdinPipe := startFn(t, sh, handleCustomWithStop, true)
cmd.S.Expect("ready")
checkSignalIsNotDefault(t, signal)
- syscall.Kill(cmd.Process().Pid, signal)
+ cmd.Signal(signal)
cmd.S.Expectf("received signal %s", signal)
fmt.Fprintf(stdinPipe, "close\n")
cmd.Wait()
diff --git a/lib/v23test/v23test.go b/lib/v23test/v23test.go
index 2f97adf..fa1408e 100644
--- a/lib/v23test/v23test.go
+++ b/lib/v23test/v23test.go
@@ -7,8 +7,6 @@
// StartRootMountTable, and StartSyncbase.
package v23test
-// TODO(sadovsky): Add DebugSystemShell command.
-
import (
"errors"
"fmt"
@@ -18,12 +16,14 @@
"runtime"
"runtime/debug"
"strings"
+ "syscall"
"testing"
"time"
"v.io/v23"
"v.io/v23/context"
"v.io/v23/rpc"
+ "v.io/x/lib/envvar"
"v.io/x/lib/gosh"
"v.io/x/ref"
"v.io/x/ref/test"
@@ -42,7 +42,7 @@
// - Eliminate test.V23Init() and either add v23test.Init() or have v23.Init()
// check for an env var and perform test-specific configuration.
// - Switch to using the testing package's -test.short flag and eliminate
-// SkipUnlessRunningIntegrationTests, the -v23tests flag, and the "jiri test"
+// SkipUnlessRunningIntegrationTests, the -v23.tests flag, and the "jiri test"
// implementation that parses test code to identify integration tests.
// Cmd wraps gosh.Cmd and provides Vanadium-specific functionality.
@@ -92,12 +92,9 @@
func NewShell(t *testing.T, opts Opts) *Shell {
fillDefaults(t, &opts)
- if t != nil {
- if !calledRun {
- t.Fatal("must call v23test.Run(m.Run) from TestMain")
- return nil
- }
-
+ if t != nil && !calledRun {
+ t.Fatal("must call v23test.Run(m.Run) from TestMain")
+ return nil
}
// Note: On error, NewShell returns a *Shell with Opts.Fatalf initialized to
@@ -157,6 +154,16 @@
return ctx
}
+// Cleanup cleans up all resources associated with this Shell.
+// See gosh.Shell.Cleanup for detailed description.
+func (sh *Shell) Cleanup() {
+ // Run sh.Shell.Cleanup even if DebugSystemShell panics.
+ defer sh.Shell.Cleanup()
+ if sh.t != nil && sh.t.Failed() && test.IntegrationTestsDebugShellOnError {
+ sh.DebugSystemShell()
+ }
+}
+
// Run does some initialization work, then returns run(). Exported so that
// TestMain functions can simply call os.Exit(v23test.Run(m.Run)).
func Run(run func() int) int {
@@ -229,10 +236,7 @@
}
// Main returns a Cmd for an invocation of the given registered main() function.
-// Intended usage: Have your program's main() call RealMain, then write a parent
-// program that uses Shell.Main to run RealMain in a child process. With this
-// approach, RealMain can be compiled into the parent program's binary. Caveat:
-// potential flag collisions.
+// See gosh.Shell.Main for detailed description.
func (sh *Shell) Main(fn *gosh.Fn, args ...string) *Cmd {
c := sh.Shell.Main(fn, args...)
if sh.Err != nil {
@@ -242,6 +246,74 @@
}
////////////////////////////////////////////////////////////////////////////////
+// DebugSystemShell
+
+// DebugSystemShell drops the user into a debug system shell (e.g. bash) that
+// includes all environment variables from sh, and sets V23_BIN_DIR to
+// sh.Opts.BinDir. If there is no controlling TTY, DebugSystemShell does
+// nothing.
+func (sh *Shell) DebugSystemShell() {
+ // Make sure we have non-nil Fatalf and Logf functions.
+ opts := Opts{Fatalf: sh.Opts.Fatalf, Logf: sh.Opts.Logf}
+ fillDefaults(sh.t, &opts)
+ fatalf, logf := opts.Fatalf, opts.Logf
+
+ cwd, err := os.Getwd()
+ if err != nil {
+ fatalf("Getwd() failed: %v\n", err)
+ return
+ }
+
+ // Transfer stdin, stdout, and stderr to the new process, and set target
+ // directory for the system shell to start in.
+ devtty := "/dev/tty"
+ fd, err := syscall.Open(devtty, syscall.O_RDWR, 0)
+ if err != nil {
+ logf("WARNING: Open(%q) failed: %v\n", devtty, err)
+ return
+ }
+
+ file := os.NewFile(uintptr(fd), devtty)
+ attr := os.ProcAttr{
+ Files: []*os.File{file, file, file},
+ Dir: cwd,
+ }
+ env := envvar.MergeMaps(envvar.SliceToMap(os.Environ()), sh.Vars)
+ env[ref.EnvCredentials] = sh.ForkCredentials("debug").Handle
+ env[envBinDir] = sh.Opts.BinDir
+ attr.Env = envvar.MapToSlice(env)
+
+ write := func(s string) {
+ if _, err := file.WriteString(s); err != nil {
+ fatalf("WriteString(%q) failed: %v\n", s, err)
+ return
+ }
+ }
+
+ write(">> Starting a new interactive shell\n")
+ write(">> Hit CTRL-D to resume the test\n")
+
+ shellPath := "/bin/sh"
+ if shellPathFromEnv := os.Getenv("SHELL"); shellPathFromEnv != "" {
+ shellPath = shellPathFromEnv
+ }
+ proc, err := os.StartProcess(shellPath, []string{}, &attr)
+ if err != nil {
+ fatalf("StartProcess(%q) failed: %v\n", shellPath, err)
+ return
+ }
+
+ // Wait until the user exits the shell.
+ state, err := proc.Wait()
+ if err != nil {
+ fatalf("Wait() failed: %v\n", err)
+ return
+ }
+
+ write(fmt.Sprintf(">> Exited shell: %s\n", state.String()))
+}
+
+////////////////////////////////////////////////////////////////////////////////
// Internals
func callerName() (string, error) {
diff --git a/runtime/factories/android/android.go b/runtime/factories/android/android.go
index e09c89d..deabcd4 100644
--- a/runtime/factories/android/android.go
+++ b/runtime/factories/android/android.go
@@ -28,25 +28,18 @@
"v.io/x/ref/runtime/internal"
"v.io/x/ref/runtime/internal/lib/appcycle"
"v.io/x/ref/runtime/internal/lib/roaming"
- "v.io/x/ref/runtime/internal/lib/websocket"
"v.io/x/ref/runtime/internal/lib/xwebsocket"
"v.io/x/ref/runtime/internal/rt"
_ "v.io/x/ref/runtime/protocols/tcp"
_ "v.io/x/ref/runtime/protocols/ws"
_ "v.io/x/ref/runtime/protocols/wsh"
"v.io/x/ref/services/debug/debuglib"
-
- // TODO(suharshs): Remove these once we switch to the flow protocols.
- _ "v.io/x/ref/runtime/internal/rpc/protocols/tcp"
- _ "v.io/x/ref/runtime/internal/rpc/protocols/ws"
- _ "v.io/x/ref/runtime/internal/rpc/protocols/wsh"
)
var commonFlags *flags.Flags
func init() {
v23.RegisterRuntimeFactory(Init)
- rpc.RegisterUnknownProtocol("wsh", websocket.HybridDial, websocket.HybridResolve, websocket.HybridListener)
flow.RegisterUnknownProtocol("wsh", xwebsocket.WSH{})
commonFlags = flags.CreateAndRegister(flag.CommandLine, flags.Runtime, flags.Listen)
}
diff --git a/runtime/factories/chrome/chrome.go b/runtime/factories/chrome/chrome.go
index 9408d82..f40d965 100644
--- a/runtime/factories/chrome/chrome.go
+++ b/runtime/factories/chrome/chrome.go
@@ -16,22 +16,16 @@
"v.io/x/ref/lib/flags"
"v.io/x/ref/runtime/internal"
- "v.io/x/ref/runtime/internal/lib/websocket"
"v.io/x/ref/runtime/internal/lib/xwebsocket"
grt "v.io/x/ref/runtime/internal/rt"
_ "v.io/x/ref/runtime/protocols/ws"
_ "v.io/x/ref/runtime/protocols/wsh_nacl"
-
- // TODO(suharshs): Remove this after we switch to the flow protocols.
- _ "v.io/x/ref/runtime/internal/rpc/protocols/ws"
- _ "v.io/x/ref/runtime/internal/rpc/protocols/wsh_nacl"
)
var commonFlags *flags.Flags
func init() {
v23.RegisterRuntimeFactory(Init)
- rpc.RegisterUnknownProtocol("wsh", websocket.Dial, websocket.Resolve, websocket.Listener)
flow.RegisterUnknownProtocol("wsh", xwebsocket.WS{})
commonFlags = flags.CreateAndRegister(flag.CommandLine, flags.Runtime)
}
diff --git a/runtime/factories/fake/fake.go b/runtime/factories/fake/fake.go
index 4fd788e..89ec1d7 100644
--- a/runtime/factories/fake/fake.go
+++ b/runtime/factories/fake/fake.go
@@ -14,20 +14,13 @@
"v.io/v23"
"v.io/v23/context"
"v.io/v23/flow"
- "v.io/v23/rpc"
"v.io/x/ref/runtime/internal"
- "v.io/x/ref/runtime/internal/lib/websocket"
"v.io/x/ref/runtime/internal/lib/xwebsocket"
+ _ "v.io/x/ref/runtime/protocols/local"
_ "v.io/x/ref/runtime/protocols/tcp"
_ "v.io/x/ref/runtime/protocols/ws"
_ "v.io/x/ref/runtime/protocols/wsh"
-
- // TODO(suharshs): Remove these once we switch to the flow protocols.
- _ "v.io/x/ref/runtime/internal/rpc/protocols/tcp"
- _ "v.io/x/ref/runtime/internal/rpc/protocols/ws"
- _ "v.io/x/ref/runtime/internal/rpc/protocols/wsh"
- _ "v.io/x/ref/runtime/protocols/local"
)
var (
@@ -41,7 +34,6 @@
func init() {
v23.RegisterRuntimeFactory(Init)
- rpc.RegisterUnknownProtocol("wsh", websocket.HybridDial, websocket.HybridResolve, websocket.HybridListener)
flow.RegisterUnknownProtocol("wsh", xwebsocket.WSH{})
}
diff --git a/runtime/factories/gce/gce.go b/runtime/factories/gce/gce.go
index eb892c3..aee5a41 100644
--- a/runtime/factories/gce/gce.go
+++ b/runtime/factories/gce/gce.go
@@ -23,24 +23,17 @@
"v.io/x/ref/runtime/internal"
"v.io/x/ref/runtime/internal/gce"
"v.io/x/ref/runtime/internal/lib/appcycle"
- "v.io/x/ref/runtime/internal/lib/websocket"
"v.io/x/ref/runtime/internal/lib/xwebsocket"
grt "v.io/x/ref/runtime/internal/rt"
_ "v.io/x/ref/runtime/protocols/tcp"
_ "v.io/x/ref/runtime/protocols/ws"
_ "v.io/x/ref/runtime/protocols/wsh"
-
- // TODO(suharshs): Remove these once we switch to the flow protocols.
- _ "v.io/x/ref/runtime/internal/rpc/protocols/tcp"
- _ "v.io/x/ref/runtime/internal/rpc/protocols/ws"
- _ "v.io/x/ref/runtime/internal/rpc/protocols/wsh"
)
var commonFlags *flags.Flags
func init() {
v23.RegisterRuntimeFactory(Init)
- rpc.RegisterUnknownProtocol("wsh", websocket.HybridDial, websocket.HybridResolve, websocket.HybridListener)
flow.RegisterUnknownProtocol("wsh", xwebsocket.WSH{})
commonFlags = flags.CreateAndRegister(flag.CommandLine, flags.Runtime, flags.Listen)
}
diff --git a/runtime/factories/generic/generic.go b/runtime/factories/generic/generic.go
index d6513dc..cf4b404 100644
--- a/runtime/factories/generic/generic.go
+++ b/runtime/factories/generic/generic.go
@@ -18,24 +18,17 @@
"v.io/x/ref/lib/flags"
"v.io/x/ref/runtime/internal"
"v.io/x/ref/runtime/internal/lib/appcycle"
- "v.io/x/ref/runtime/internal/lib/websocket"
"v.io/x/ref/runtime/internal/lib/xwebsocket"
grt "v.io/x/ref/runtime/internal/rt"
_ "v.io/x/ref/runtime/protocols/tcp"
_ "v.io/x/ref/runtime/protocols/ws"
_ "v.io/x/ref/runtime/protocols/wsh"
-
- // TODO(suharshs): Remove these once we switch to the flow protocols.
- _ "v.io/x/ref/runtime/internal/rpc/protocols/tcp"
- _ "v.io/x/ref/runtime/internal/rpc/protocols/ws"
- _ "v.io/x/ref/runtime/internal/rpc/protocols/wsh"
)
var commonFlags *flags.Flags
func init() {
v23.RegisterRuntimeFactory(Init)
- rpc.RegisterUnknownProtocol("wsh", websocket.HybridDial, websocket.HybridResolve, websocket.HybridListener)
flow.RegisterUnknownProtocol("wsh", xwebsocket.WSH{})
flags.SetDefaultHostPort(":0")
commonFlags = flags.CreateAndRegister(flag.CommandLine, flags.Runtime, flags.Listen)
diff --git a/runtime/factories/roaming/roaming.go b/runtime/factories/roaming/roaming.go
index c6c0fe3..5d0ce6b 100644
--- a/runtime/factories/roaming/roaming.go
+++ b/runtime/factories/roaming/roaming.go
@@ -29,25 +29,18 @@
"v.io/x/ref/runtime/internal"
"v.io/x/ref/runtime/internal/lib/appcycle"
"v.io/x/ref/runtime/internal/lib/roaming"
- "v.io/x/ref/runtime/internal/lib/websocket"
"v.io/x/ref/runtime/internal/lib/xwebsocket"
"v.io/x/ref/runtime/internal/rt"
_ "v.io/x/ref/runtime/protocols/tcp"
_ "v.io/x/ref/runtime/protocols/ws"
_ "v.io/x/ref/runtime/protocols/wsh"
"v.io/x/ref/services/debug/debuglib"
-
- // TODO(suharshs): Remove these once we switch to the flow protocols.
- _ "v.io/x/ref/runtime/internal/rpc/protocols/tcp"
- _ "v.io/x/ref/runtime/internal/rpc/protocols/ws"
- _ "v.io/x/ref/runtime/internal/rpc/protocols/wsh"
)
var commonFlags *flags.Flags
func init() {
v23.RegisterRuntimeFactory(Init)
- rpc.RegisterUnknownProtocol("wsh", websocket.HybridDial, websocket.HybridResolve, websocket.HybridListener)
flow.RegisterUnknownProtocol("wsh", xwebsocket.WSH{})
commonFlags = flags.CreateAndRegister(flag.CommandLine, flags.Runtime, flags.Listen)
}
diff --git a/runtime/internal/flow/conn/auth.go b/runtime/internal/flow/conn/auth.go
index 910cbc9..4e8363e 100644
--- a/runtime/internal/flow/conn/auth.go
+++ b/runtime/internal/flow/conn/auth.go
@@ -32,7 +32,7 @@
)
func (c *Conn) dialHandshake(ctx *context.T, versions version.RPCVersionRange, auth flow.PeerAuthorizer) error {
- binding, remoteEndpoint, err := c.setup(ctx, versions)
+ binding, remoteEndpoint, err := c.setup(ctx, versions, true)
if err != nil {
return err
}
@@ -83,7 +83,7 @@
}
func (c *Conn) acceptHandshake(ctx *context.T, versions version.RPCVersionRange, authorizedPeers []security.BlessingPattern) error {
- binding, remoteEndpoint, err := c.setup(ctx, versions)
+ binding, remoteEndpoint, err := c.setup(ctx, versions, false)
if err != nil {
return err
}
@@ -110,7 +110,7 @@
return err
}
-func (c *Conn) setup(ctx *context.T, versions version.RPCVersionRange) ([]byte, naming.Endpoint, error) {
+func (c *Conn) setup(ctx *context.T, versions version.RPCVersionRange, dialer bool) ([]byte, naming.Endpoint, error) {
pk, sk, err := box.GenerateKey(rand.Reader)
if err != nil {
return nil, nil, err
@@ -119,6 +119,8 @@
Versions: versions,
PeerLocalEndpoint: c.local,
PeerNaClPublicKey: pk,
+ Mtu: defaultMtu,
+ SharedTokens: DefaultBytesBufferedPerFlow,
}
if c.remote != nil {
lSetup.PeerRemoteEndpoint = c.remote
@@ -152,10 +154,41 @@
if c.local == nil {
c.local = rSetup.PeerRemoteEndpoint
}
+ if rSetup.Mtu != 0 {
+ c.mtu = rSetup.Mtu
+ } else {
+ c.mtu = defaultMtu
+ }
+ c.lshared = lSetup.SharedTokens
+ if rSetup.SharedTokens != 0 && rSetup.SharedTokens < c.lshared {
+ c.lshared = rSetup.SharedTokens
+ }
if rSetup.PeerNaClPublicKey == nil {
return nil, nil, NewErrMissingSetupOption(ctx, "peerNaClPublicKey")
}
binding := c.mp.setupEncryption(ctx, pk, sk, rSetup.PeerNaClPublicKey)
+ if c.version >= version.RPCVersion14 {
+ // We include the setup messages in the channel binding to prevent attacks
+ // where a man in the middle changes fields in the Setup message (e.g. a
+ // downgrade attack wherein a MITM attacker changes the Version field of
+ // the Setup message to a lower-security version.)
+ // We always put the dialer first in the binding.
+ if dialer {
+ if binding, err = message.Append(ctx, lSetup, nil); err != nil {
+ return nil, nil, err
+ }
+ if binding, err = message.Append(ctx, rSetup, binding); err != nil {
+ return nil, nil, err
+ }
+ } else {
+ if binding, err = message.Append(ctx, rSetup, nil); err != nil {
+ return nil, nil, err
+ }
+ if binding, err = message.Append(ctx, lSetup, binding); err != nil {
+ return nil, nil, err
+ }
+ }
+ }
// if we're encapsulated in another flow, tell that flow to stop
// encrypting now that we've started.
if f, ok := c.mp.rw.(*flw); ok {
@@ -169,7 +202,10 @@
if dialer {
tag = authAcceptorTag
}
- var rauth *message.Auth
+ var (
+ rauth *message.Auth
+ err error
+ )
for {
msg, err := c.mp.readMsg(ctx)
if err != nil {
@@ -188,7 +224,6 @@
var rBlessings security.Blessings
var rDischarges map[string]security.Discharge
if rauth.BlessingsKey != 0 {
- var err error
// TODO(mattr): Make sure we cancel out of this at some point.
rBlessings, rDischarges, err = c.blessingsFlow.getRemote(ctx, rauth.BlessingsKey, rauth.DischargeKey)
if err != nil {
diff --git a/runtime/internal/flow/conn/conn.go b/runtime/internal/flow/conn/conn.go
index dd9f70a..55a8197 100644
--- a/runtime/internal/flow/conn/conn.go
+++ b/runtime/internal/flow/conn/conn.go
@@ -38,7 +38,7 @@
)
const (
- mtu = 1 << 16
+ defaultMtu = 1 << 16
defaultChannelTimeout = 30 * time.Minute
DefaultBytesBufferedPerFlow = 1 << 20
proxyOverhead = 32
@@ -88,6 +88,7 @@
unopenedFlows sync.WaitGroup
cancel context.CancelFunc
handler FlowHandler
+ mtu uint64
mu sync.Mutex // All the variables below here are protected by mu.
@@ -158,22 +159,19 @@
channelTimeout = defaultChannelTimeout
}
c := &Conn{
- mp: newMessagePipe(conn),
- handler: handler,
- lBlessings: lBlessings,
- local: endpointCopy(local),
- remote: endpointCopy(remote),
- closed: make(chan struct{}),
- lameDucked: make(chan struct{}),
- nextFid: reservedFlows,
- flows: map[uint64]*flw{},
- lastUsedTime: time.Now(),
- toRelease: map[uint64]uint64{},
- borrowing: map[uint64]bool{},
- cancel: cancel,
- // TODO(mattr): We should negotiate the shared counter pool size with the
- // other end.
- lshared: DefaultBytesBufferedPerFlow,
+ mp: newMessagePipe(conn),
+ handler: handler,
+ lBlessings: lBlessings,
+ local: endpointCopy(local),
+ remote: endpointCopy(remote),
+ closed: make(chan struct{}),
+ lameDucked: make(chan struct{}),
+ nextFid: reservedFlows,
+ flows: map[uint64]*flw{},
+ lastUsedTime: time.Now(),
+ toRelease: map[uint64]uint64{},
+ borrowing: map[uint64]bool{},
+ cancel: cancel,
outstandingBorrowed: make(map[uint64]uint64),
activeWriters: make([]writer, numPriorities),
acceptChannelTimeout: channelTimeout,
@@ -250,7 +248,6 @@
toRelease: map[uint64]uint64{},
borrowing: map[uint64]bool{},
cancel: cancel,
- lshared: DefaultBytesBufferedPerFlow,
outstandingBorrowed: make(map[uint64]uint64),
activeWriters: make([]writer, numPriorities),
acceptChannelTimeout: channelTimeout,
@@ -406,6 +403,12 @@
return blessings
}
+func (c *Conn) RemoteDischarges() map[string]security.Discharge {
+ // Its safe to ignore this error. It means that this conn is closed.
+ _, discharges, _ := c.blessingsFlow.getLatestRemote(nil, c.rBKey)
+ return discharges
+}
+
// CommonVersion returns the RPCVersion negotiated between the local and remote endpoints.
func (c *Conn) CommonVersion() version.RPCVersion { return c.version }
diff --git a/runtime/internal/flow/conn/flow.go b/runtime/internal/flow/conn/flow.go
index 96bfb8e..60236a6 100644
--- a/runtime/internal/flow/conn/flow.go
+++ b/runtime/internal/flow/conn/flow.go
@@ -142,7 +142,7 @@
// the number of shared counters for the conn if we are sending on a just
// dialed flow.
func (f *flw) tokensLocked() (int, func(int)) {
- max := uint64(mtu)
+ max := f.conn.mtu
// When our flow is proxied (i.e. encapsulated), the proxy has added overhead
// when forwarding the message. This means we must reduce our mtu to ensure
// that dialer framing reaches the acceptor without being truncated by the
diff --git a/runtime/internal/flow/conn/flowcontrol_test.go b/runtime/internal/flow/conn/flowcontrol_test.go
index 81af175..5529940 100644
--- a/runtime/internal/flow/conn/flowcontrol_test.go
+++ b/runtime/internal/flow/conn/flowcontrol_test.go
@@ -116,18 +116,18 @@
defer wg.Wait()
for _, f := range flows {
go func(fl flow.Flow) {
- if _, err := fl.WriteMsg(randData[:mtu*nmessages]); err != nil {
+ if _, err := fl.WriteMsg(randData[:defaultMtu*nmessages]); err != nil {
panic(err)
}
wg.Done()
}(f)
go func() {
fl := <-accept
- buf := make([]byte, mtu*nmessages)
+ buf := make([]byte, defaultMtu*nmessages)
if _, err := io.ReadFull(fl, buf); err != nil {
panic(err)
}
- if !bytes.Equal(buf, randData[:mtu*nmessages]) {
+ if !bytes.Equal(buf, randData[:defaultMtu*nmessages]) {
t.Fatal("unequal data")
}
wg.Done()
diff --git a/runtime/internal/flow/conn/message.go b/runtime/internal/flow/conn/message.go
index 529079f..8e50e12 100644
--- a/runtime/internal/flow/conn/message.go
+++ b/runtime/internal/flow/conn/message.go
@@ -30,7 +30,7 @@
func newMessagePipe(rw flow.MsgReadWriteCloser) *messagePipe {
return &messagePipe{
rw: rw,
- writeBuf: make([]byte, mtu),
+ writeBuf: make([]byte, defaultMtu),
cipher: &crypto.NullControlCipher{},
}
}
diff --git a/runtime/internal/flow/flowtest/flowtest.go b/runtime/internal/flow/flowtest/flowtest.go
index 5c3a996..5685a03 100644
--- a/runtime/internal/flow/flowtest/flowtest.go
+++ b/runtime/internal/flow/flowtest/flowtest.go
@@ -5,6 +5,7 @@
package flowtest
import (
+ "fmt"
"testing"
"time"
@@ -53,3 +54,42 @@
security.Blessings, map[string]security.Discharge, error) {
return v23.GetPrincipal(ctx).BlessingStore().Default(), nil, nil
}
+
+type peersAuthorizer []string
+
+func NewPeerAuthorizer(names []string) flow.PeerAuthorizer {
+ if len(names) == 0 {
+ return AllowAllPeersAuthorizer{}
+ }
+ return peersAuthorizer(names)
+}
+
+func (p peersAuthorizer) AuthorizePeer(
+ ctx *context.T,
+ localEndpoint, remoteEndpoint naming.Endpoint,
+ remoteBlessings security.Blessings,
+ remoteDischarges map[string]security.Discharge,
+) ([]string, []security.RejectedBlessing, error) {
+ call := security.NewCall(&security.CallParams{
+ Timestamp: time.Now(),
+ LocalPrincipal: v23.GetPrincipal(ctx),
+ LocalEndpoint: localEndpoint,
+ RemoteBlessings: remoteBlessings,
+ RemoteDischarges: remoteDischarges,
+ RemoteEndpoint: remoteEndpoint,
+ })
+ peerNames, rejectedPeerNames := security.RemoteBlessingNames(ctx, call)
+ ctx.Infof("validating against %v", peerNames)
+ for _, pattern := range p {
+ if security.BlessingPattern(pattern).MatchedBy(peerNames...) {
+ return peerNames, rejectedPeerNames, nil
+ }
+ ctx.Infof("no match %v", pattern)
+ }
+ return peerNames, rejectedPeerNames, fmt.Errorf("not authorized")
+}
+
+func (peersAuthorizer) BlessingsForPeer(ctx *context.T, _ []string) (
+ security.Blessings, map[string]security.Discharge, error) {
+ return v23.GetPrincipal(ctx).BlessingStore().Default(), nil, nil
+}
diff --git a/runtime/internal/flow/manager/conncache.go b/runtime/internal/flow/manager/conncache.go
index 78a5365..0e4467d 100644
--- a/runtime/internal/flow/manager/conncache.go
+++ b/runtime/internal/flow/manager/conncache.go
@@ -5,10 +5,13 @@
package manager
import (
+ "bytes"
+ "fmt"
"sort"
"sync"
"v.io/v23/context"
+ "v.io/v23/flow"
"v.io/v23/naming"
"v.io/x/ref/runtime/internal/flow/conn"
)
@@ -26,11 +29,10 @@
}
type connEntry struct {
- conn *conn.Conn
- rid naming.RoutingID
- addrKey string
- blessingNames []string
- proxy bool
+ conn *conn.Conn
+ rid naming.RoutingID
+ addrKey string
+ proxy bool
}
func NewConnCache() *ConnCache {
@@ -46,6 +48,19 @@
}
}
+func (c *ConnCache) String() string {
+ buf := &bytes.Buffer{}
+ fmt.Fprintln(buf, "AddressCache:")
+ for k, v := range c.addrCache {
+ fmt.Fprintf(buf, "%v: %p\n", k, v.conn)
+ }
+ fmt.Fprintln(buf, "RIDCache:")
+ for k, v := range c.ridCache {
+ fmt.Fprintf(buf, "%v: %p\n", k, v.conn)
+ }
+ return buf.String()
+}
+
// Insert adds conn to the cache, keyed by both (protocol, address) and (routingID)
// An error will be returned iff the cache has been closed.
func (c *ConnCache) Insert(conn *conn.Conn, protocol, address string, proxy bool) error {
@@ -62,9 +77,6 @@
addrKey: k,
proxy: proxy,
}
- if !entry.proxy {
- entry.blessingNames = ep.BlessingNames()
- }
if old := c.ridCache[entry.rid]; old != nil {
c.unmappedConns[old] = true
}
@@ -86,9 +98,6 @@
rid: ep.RoutingID(),
proxy: proxy,
}
- if !entry.proxy {
- entry.blessingNames = ep.BlessingNames()
- }
if old := c.ridCache[entry.rid]; old != nil {
c.unmappedConns[old] = true
}
@@ -96,19 +105,19 @@
return nil
}
-func (c *ConnCache) Find(protocol, address string, rid naming.RoutingID, blessingNames []string) (*conn.Conn, error) {
+func (c *ConnCache) Find(ctx *context.T, protocol, address string, rid naming.RoutingID, auth flow.PeerAuthorizer) (*conn.Conn, error) {
defer c.mu.Unlock()
c.mu.Lock()
if c.addrCache == nil {
return nil, NewErrCacheClosed(nil)
}
if rid != naming.NullRoutingID {
- if entry := c.removeUndialable(c.ridCache[rid], blessingNames); entry != nil {
+ if entry := c.removeUndialable(ctx, c.ridCache[rid], auth); entry != nil {
return entry, nil
}
}
k := key(protocol, address)
- return c.removeUndialable(c.addrCache[k], blessingNames), nil
+ return c.removeUndialable(ctx, c.addrCache[k], auth), nil
}
// ReservedFind returns a Conn based on the input remoteEndpoint.
@@ -120,14 +129,14 @@
// the arguments provided to ReservedFind.
// All new ReservedFind calls for the (protocol, address) will Block
// until the corresponding Unreserve call is made.
-func (c *ConnCache) ReservedFind(protocol, address string, rid naming.RoutingID, blessingNames []string) (*conn.Conn, error) {
+func (c *ConnCache) ReservedFind(ctx *context.T, protocol, address string, rid naming.RoutingID, auth flow.PeerAuthorizer) (*conn.Conn, error) {
defer c.mu.Unlock()
c.mu.Lock()
if c.addrCache == nil {
return nil, NewErrCacheClosed(nil)
}
if rid != naming.NullRoutingID {
- if entry := c.removeUndialable(c.ridCache[rid], blessingNames); entry != nil {
+ if entry := c.removeUndialable(ctx, c.ridCache[rid], auth); entry != nil {
return entry, nil
}
}
@@ -139,7 +148,7 @@
}
}
c.started[k] = true
- return c.removeUndialable(c.addrCache[k], blessingNames), nil
+ return c.removeUndialable(ctx, c.addrCache[k], auth), nil
}
// Unreserve marks the status of the (protocol, address) as no longer started, and
@@ -166,8 +175,8 @@
}
// removeUndialable filters connections that are closed, lameducked, or non-proxied
-// connections whose blessings dont match blessingNames.
-func (c *ConnCache) removeUndialable(e *connEntry, blessingNames []string) *conn.Conn {
+// connections that do not authorize.
+func (c *ConnCache) removeUndialable(ctx *context.T, e *connEntry, auth flow.PeerAuthorizer) *conn.Conn {
if e == nil {
return nil
}
@@ -179,8 +188,15 @@
}
return nil
}
- if !e.proxy && len(blessingNames) > 0 && !matchBlessings(e.blessingNames, blessingNames) {
- return nil
+ if !e.proxy && auth != nil {
+ _, _, err := auth.AuthorizePeer(ctx,
+ e.conn.LocalEndpoint(),
+ e.conn.RemoteEndpoint(),
+ e.conn.RemoteBlessings(),
+ e.conn.RemoteDischarges())
+ if err != nil {
+ return nil
+ }
}
return e.conn
}
@@ -200,7 +216,7 @@
err := NewErrConnKilledToFreeResources(ctx)
pq := make(connEntries, 0, len(c.ridCache))
for _, e := range c.ridCache {
- if c.removeUndialable(e, nil) == nil {
+ if c.removeUndialable(ctx, e, nil) == nil {
continue
}
if e.conn.IsEncapsulated() {
diff --git a/runtime/internal/flow/manager/conncache_test.go b/runtime/internal/flow/manager/conncache_test.go
index bf1a739..260a4a4 100644
--- a/runtime/internal/flow/manager/conncache_test.go
+++ b/runtime/internal/flow/manager/conncache_test.go
@@ -14,16 +14,18 @@
"v.io/v23/flow"
"v.io/v23/naming"
"v.io/v23/rpc/version"
+ "v.io/v23/security"
connpackage "v.io/x/ref/runtime/internal/flow/conn"
"v.io/x/ref/runtime/internal/flow/flowtest"
inaming "v.io/x/ref/runtime/internal/naming"
_ "v.io/x/ref/runtime/protocols/local"
+ "v.io/x/ref/test"
"v.io/x/ref/test/goroutines"
)
func TestCache(t *testing.T) {
defer goroutines.NoLeaks(t, leakWaitTime)()
- ctx, shutdown := v23.Init()
+ ctx, shutdown := test.V23Init()
defer shutdown()
c := NewConnCache()
@@ -31,8 +33,10 @@
Protocol: "tcp",
Address: "127.0.0.1:1111",
RID: naming.FixedRoutingID(0x5555),
- Blessings: []string{"A", "B", "C"},
+ Blessings: unionBlessing(ctx, "A", "B", "C"),
}
+
+ auth := flowtest.NewPeerAuthorizer(remote.Blessings)
caf := makeConnAndFlow(t, ctx, remote)
defer caf.stop(ctx)
conn := caf.c
@@ -40,32 +44,32 @@
t.Fatal(err)
}
// We should be able to find the conn in the cache.
- if got, err := c.ReservedFind(remote.Protocol, remote.Address, naming.NullRoutingID, remote.Blessings); err != nil || got != conn {
+ if got, err := c.ReservedFind(ctx, remote.Protocol, remote.Address, naming.NullRoutingID, auth); err != nil || got != conn {
t.Errorf("got %v, want %v, err: %v", got, conn, err)
}
c.Unreserve(remote.Protocol, remote.Address)
// Changing the protocol should fail.
- if got, err := c.ReservedFind("wrong", remote.Address, naming.NullRoutingID, remote.Blessings); err != nil || got != nil {
+ if got, err := c.ReservedFind(ctx, "wrong", remote.Address, naming.NullRoutingID, auth); err != nil || got != nil {
t.Errorf("got %v, want <nil>, err: %v", got, err)
}
c.Unreserve("wrong", remote.Address)
// Changing the address should fail.
- if got, err := c.ReservedFind(remote.Protocol, "wrong", naming.NullRoutingID, remote.Blessings); err != nil || got != nil {
+ if got, err := c.ReservedFind(ctx, remote.Protocol, "wrong", naming.NullRoutingID, auth); err != nil || got != nil {
t.Errorf("got %v, want <nil>, err: %v", got, err)
}
c.Unreserve(remote.Protocol, "wrong")
// Changing the blessingNames should fail.
- if got, err := c.ReservedFind(remote.Protocol, remote.Address, naming.NullRoutingID, []string{"wrong"}); err != nil || got != nil {
+ if got, err := c.ReservedFind(ctx, remote.Protocol, remote.Address, naming.NullRoutingID, flowtest.NewPeerAuthorizer([]string{"wrong"})); err != nil || got != nil {
t.Errorf("got %v, want <nil>, err: %v", got, err)
}
c.Unreserve(remote.Protocol, remote.Address)
- // But finding a set of blessings that has at least on blessings in remote.Blessings should succeed.
- if got, err := c.ReservedFind(remote.Protocol, remote.Address, naming.NullRoutingID, []string{"foo", "A"}); err != nil || got != conn {
+ // But finding a set of blessings that has at least one blessings in remote.Blessings should succeed.
+ if got, err := c.ReservedFind(ctx, remote.Protocol, remote.Address, naming.NullRoutingID, flowtest.NewPeerAuthorizer([]string{"foo", remote.Blessings[0]})); err != nil || got != conn {
t.Errorf("got %v, want %v, err: %v", got, conn, err)
}
c.Unreserve(remote.Protocol, remote.Address)
// Finding by routing ID should work.
- if got, err := c.ReservedFind("wrong", "wrong", remote.RID, remote.Blessings); err != nil || got != conn {
+ if got, err := c.ReservedFind(ctx, "wrong", "wrong", remote.RID, auth); err != nil || got != conn {
t.Errorf("got %v, want %v, err: %v", got, conn, err)
}
c.Unreserve("wrong", "wrong")
@@ -76,7 +80,7 @@
Protocol: "tcp",
Address: "127.0.0.1:2222",
RID: naming.FixedRoutingID(0x5555),
- Blessings: []string{"A", "B", "C"},
+ Blessings: unionBlessing(ctx, "A", "B", "C"),
}
caf = makeConnAndFlow(t, ctx, proxyep)
defer caf.stop(ctx)
@@ -85,7 +89,7 @@
t.Fatal(err)
}
// Wrong blessingNames should still work
- if got, err := c.ReservedFind(proxyep.Protocol, proxyep.Address, naming.NullRoutingID, []string{"wrong"}); err != nil || got != proxyConn {
+ if got, err := c.ReservedFind(ctx, proxyep.Protocol, proxyep.Address, naming.NullRoutingID, flowtest.NewPeerAuthorizer([]string{"wrong"})); err != nil || got != proxyConn {
t.Errorf("got %v, want %v, err: %v", got, proxyConn, err)
}
c.Unreserve(proxyep.Protocol, proxyep.Address)
@@ -95,20 +99,21 @@
Protocol: "ridonly",
Address: "ridonly",
RID: naming.FixedRoutingID(0x1111),
- Blessings: []string{"ridonly"},
+ Blessings: unionBlessing(ctx, "ridonly"),
}
+ ridauth := flowtest.NewPeerAuthorizer(ridEP.Blessings)
caf = makeConnAndFlow(t, ctx, ridEP)
defer caf.stop(ctx)
ridConn := caf.c
if err := c.InsertWithRoutingID(ridConn, false); err != nil {
t.Fatal(err)
}
- if got, err := c.ReservedFind(ridEP.Protocol, ridEP.Address, naming.NullRoutingID, ridEP.Blessings); err != nil || got != nil {
+ if got, err := c.ReservedFind(ctx, ridEP.Protocol, ridEP.Address, naming.NullRoutingID, ridauth); err != nil || got != nil {
t.Errorf("got %v, want <nil>, err: %v", got, err)
}
c.Unreserve(ridEP.Protocol, ridEP.Address)
// Finding by routing ID should work.
- if got, err := c.ReservedFind("wrong", "wrong", ridEP.RID, ridEP.Blessings); err != nil || got != ridConn {
+ if got, err := c.ReservedFind(ctx, "wrong", "wrong", ridEP.RID, ridauth); err != nil || got != ridConn {
t.Errorf("got %v, want %v, err: %v", got, ridConn, err)
}
c.Unreserve("wrong", "wrong")
@@ -117,20 +122,21 @@
Protocol: "other",
Address: "other",
RID: naming.FixedRoutingID(0x2222),
- Blessings: []string{"other"},
+ Blessings: unionBlessing(ctx, "other"),
}
+ otherAuth := flowtest.NewPeerAuthorizer(otherEP.Blessings)
caf = makeConnAndFlow(t, ctx, otherEP)
defer caf.stop(ctx)
otherConn := caf.c
// Looking up a not yet inserted endpoint should fail.
- if got, err := c.ReservedFind(otherEP.Protocol, otherEP.Address, naming.NullRoutingID, otherEP.Blessings); err != nil || got != nil {
+ if got, err := c.ReservedFind(ctx, otherEP.Protocol, otherEP.Address, naming.NullRoutingID, otherAuth); err != nil || got != nil {
t.Errorf("got %v, want <nil>, err: %v", got, err)
}
// Looking it up again should block until a matching Unreserve call is made.
ch := make(chan *connpackage.Conn, 1)
go func(ch chan *connpackage.Conn) {
- conn, err := c.ReservedFind(otherEP.Protocol, otherEP.Address, naming.NullRoutingID, otherEP.Blessings)
+ conn, err := c.ReservedFind(ctx, otherEP.Protocol, otherEP.Address, naming.NullRoutingID, otherAuth)
if err != nil {
t.Fatal(err)
}
@@ -177,7 +183,7 @@
func TestLRU(t *testing.T) {
defer goroutines.NoLeaks(t, leakWaitTime)()
- ctx, shutdown := v23.Init()
+ ctx, shutdown := test.V23Init()
defer shutdown()
// Ensure that the least recently created conns are killed by KillConnections.
@@ -202,13 +208,14 @@
if status := conn.c.Status(); status == connpackage.Closed {
t.Errorf("conn %v should not have been closed", conn)
}
- if !isInCache(t, c, conn.c) {
- t.Errorf("conn %v should still be in cache", conn)
+ if !isInCache(t, ctx, c, conn.c) {
+ t.Errorf("conn %v(%p) should still be in cache:\n%s",
+ conn.c.RemoteEndpoint(), conn.c, c)
}
}
for _, conn := range conns[:3] {
<-conn.c.Closed()
- if isInCache(t, c, conn.c) {
+ if isInCache(t, ctx, c, conn.c) {
t.Errorf("conn %v should not be in cache", conn)
}
}
@@ -238,13 +245,13 @@
if status := conn.c.Status(); status == connpackage.Closed {
t.Errorf("conn %v should not have been closed", conn)
}
- if !isInCache(t, c, conn.c) {
+ if !isInCache(t, ctx, c, conn.c) {
t.Errorf("conn %v should still be in cache", conn)
}
}
for _, conn := range conns[7:] {
<-conn.c.Closed()
- if isInCache(t, c, conn.c) {
+ if isInCache(t, ctx, c, conn.c) {
t.Errorf("conn %v should not be in cache", conn)
}
}
@@ -274,21 +281,22 @@
if status := conn.c.Status(); status == connpackage.Closed {
t.Errorf("conn %v should not have been closed", conn)
}
- if !isInCache(t, c, conn.c) {
- t.Errorf("conn %v should still be in cache", conn)
+ if !isInCache(t, ctx, c, conn.c) {
+ t.Errorf("conn %v(%p) should still be in cache:\n%s",
+ conn.c.RemoteEndpoint(), conn.c, c)
}
}
for _, conn := range conns[7:] {
<-conn.c.Closed()
- if isInCache(t, c, conn.c) {
+ if isInCache(t, ctx, c, conn.c) {
t.Errorf("conn %v should not be in cache", conn)
}
}
}
-func isInCache(t *testing.T, c *ConnCache, conn *connpackage.Conn) bool {
+func isInCache(t *testing.T, ctx *context.T, c *ConnCache, conn *connpackage.Conn) bool {
rep := conn.RemoteEndpoint()
- rfconn, err := c.ReservedFind(rep.Addr().Network(), rep.Addr().String(), rep.RoutingID(), rep.BlessingNames())
+ rfconn, err := c.ReservedFind(ctx, rep.Addr().Network(), rep.Addr().String(), rep.RoutingID(), flowtest.NewPeerAuthorizer(rep.BlessingNames()))
if err != nil {
t.Error(err)
}
@@ -392,3 +400,25 @@
}()
return nil
}
+
+func unionBlessing(ctx *context.T, names ...string) []string {
+ principal := v23.GetPrincipal(ctx)
+ blessings := make([]security.Blessings, len(names))
+ for i, name := range names {
+ var err error
+ if blessings[i], err = principal.BlessSelf(name); err != nil {
+ panic(err)
+ }
+ }
+ union, err := security.UnionOfBlessings(blessings...)
+ if err != nil {
+ panic(err)
+ }
+ if err := security.AddToRoots(principal, union); err != nil {
+ panic(err)
+ }
+ if err := principal.BlessingStore().SetDefault(union); err != nil {
+ panic(err)
+ }
+ return security.BlessingNames(principal, principal.BlessingStore().Default())
+}
diff --git a/runtime/internal/flow/manager/manager.go b/runtime/internal/flow/manager/manager.go
index 8ac1742..e35abc8 100644
--- a/runtime/internal/flow/manager/manager.go
+++ b/runtime/internal/flow/manager/manager.go
@@ -657,8 +657,8 @@
func (m *manager) internalDial(ctx *context.T, remote naming.Endpoint, auth flow.PeerAuthorizer, channelTimeout time.Duration, proxy bool) (flow.Flow, error) {
// Fast path, look for the conn based on unresolved network, address, and routingId first.
- addr, rid, blessingNames := remote.Addr(), remote.RoutingID(), remote.BlessingNames()
- c, err := m.cache.Find(addr.Network(), addr.String(), rid, blessingNames)
+ addr, rid := remote.Addr(), remote.RoutingID()
+ c, err := m.cache.Find(ctx, addr.Network(), addr.String(), rid, auth)
if err != nil {
return nil, iflow.MaybeWrapError(flow.ErrBadState, ctx, err)
}
@@ -680,7 +680,7 @@
if err != nil {
return nil, iflow.MaybeWrapError(flow.ErrResolveFailed, ctx, err)
}
- c, err = m.cache.ReservedFind(network, address, rid, blessingNames)
+ c, err = m.cache.ReservedFind(ctx, network, address, rid, auth)
if err != nil {
return nil, iflow.MaybeWrapError(flow.ErrBadState, ctx, err)
}
diff --git a/runtime/internal/lib/tcputil/tcputil.go b/runtime/internal/lib/tcputil/tcputil.go
index c6891f1..c0c8f78 100644
--- a/runtime/internal/lib/tcputil/tcputil.go
+++ b/runtime/internal/lib/tcputil/tcputil.go
@@ -13,7 +13,7 @@
"v.io/v23/context"
"v.io/v23/flow"
- "v.io/x/ref/runtime/internal/lib/framer"
+ "v.io/x/ref/runtime/protocols/lib/framer"
)
const keepAlivePeriod = 30 * time.Second
diff --git a/runtime/internal/lib/websocket/conn.go b/runtime/internal/lib/websocket/conn.go
deleted file mode 100644
index 77c7e99..0000000
--- a/runtime/internal/lib/websocket/conn.go
+++ /dev/null
@@ -1,171 +0,0 @@
-// 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.
-
-// +build !nacl
-
-package websocket
-
-import (
- "fmt"
- "io"
- "net"
- "sync"
- "time"
-
- "github.com/gorilla/websocket"
-)
-
-// WebsocketConn provides a net.Conn interface for a websocket connection.
-func WebsocketConn(ws *websocket.Conn) net.Conn {
- return &wrappedConn{ws: ws}
-}
-
-// wrappedConn provides a net.Conn interface to a websocket.
-// The underlying websocket connection needs regular calls to Read to make sure
-// websocket control messages (such as pings) are processed by the websocket
-// library.
-type wrappedConn struct {
- ws *websocket.Conn
- currReader io.Reader
-
- // The gorilla docs aren't explicit about reading and writing from
- // different goroutines. It is explicit that only one goroutine can
- // do a write at any given time and only one goroutine can do a read
- // at any given time. Based on inspection it seems that using a reader
- // and writer simultaneously is safe, but this might change with
- // future changes. We can't actually share the lock, because this means
- // that we can't write while we are waiting for a message, causing some
- // deadlocks where a write is need to unblock a read.
- writeLock sync.Mutex
- readLock sync.Mutex
-}
-
-func (c *wrappedConn) readFromCurrReader(b []byte) (int, error) {
- n, err := c.currReader.Read(b)
- if err == io.EOF {
- err = nil
- c.currReader = nil
- }
- return n, err
-}
-
-func (c *wrappedConn) Read(b []byte) (int, error) {
- c.readLock.Lock()
- defer c.readLock.Unlock()
- var n int
- var err error
-
- // TODO(bjornick): It would be nice to be able to read multiple messages at
- // a time in case the first message is not big enough to fill b and another
- // message is ready.
- // Loop until we either get data or an error. This exists
- // mostly to avoid return 0, nil.
- for n == 0 && err == nil {
- if c.currReader == nil {
- t, r, err := c.ws.NextReader()
- if err != nil {
- return 0, err
- }
- if t != websocket.BinaryMessage {
- return 0, fmt.Errorf("Unexpected message type %d", t)
- }
- c.currReader = r
- }
- n, err = c.readFromCurrReader(b)
- }
- return n, err
-}
-
-func (c *wrappedConn) Write(b []byte) (int, error) {
- c.writeLock.Lock()
- defer c.writeLock.Unlock()
- if err := c.ws.WriteMessage(websocket.BinaryMessage, b); err != nil {
- return 0, err
- }
- return len(b), nil
-}
-
-func (c *wrappedConn) Close() error {
- c.writeLock.Lock()
- defer c.writeLock.Unlock()
- // Send an EOF control message to the remote end so that it can
- // handle the close gracefully.
- msg := websocket.FormatCloseMessage(websocket.CloseGoingAway, "EOF")
- c.ws.WriteControl(websocket.CloseMessage, msg, time.Now().Add(time.Second))
- return c.ws.Close()
-}
-
-func (c *wrappedConn) LocalAddr() net.Addr {
- return &addr{"ws", c.ws.LocalAddr().String()}
-}
-
-func (c *wrappedConn) RemoteAddr() net.Addr {
- return &addr{"ws", c.ws.RemoteAddr().String()}
-}
-
-func (c *wrappedConn) SetDeadline(t time.Time) error {
- if err := c.SetReadDeadline(t); err != nil {
- return err
- }
- return c.SetWriteDeadline(t)
-}
-
-func (c *wrappedConn) SetReadDeadline(t time.Time) error {
- return c.ws.SetReadDeadline(t)
-}
-
-func (c *wrappedConn) SetWriteDeadline(t time.Time) error {
- return c.ws.SetWriteDeadline(t)
-}
-
-// hybridConn is used by the 'hybrid' protocol that can accept
-// either 'tcp' or 'websocket' connections. In particular, it allows
-// for the reader to peek and buffer the first n bytes of a stream
-// in order to determine what the connection type is.
-type hybridConn struct {
- conn net.Conn
- buffered []byte
-}
-
-func (wc *hybridConn) Read(b []byte) (int, error) {
- lbuf := len(wc.buffered)
- if lbuf == 0 {
- return wc.conn.Read(b)
- }
- copyn := copy(b, wc.buffered)
- wc.buffered = wc.buffered[copyn:]
- if len(b) > copyn {
- n, err := wc.conn.Read(b[copyn:])
- return copyn + n, err
- }
- return copyn, nil
-}
-
-func (wc *hybridConn) Write(b []byte) (n int, err error) {
- return wc.conn.Write(b)
-}
-
-func (wc *hybridConn) Close() error {
- return wc.conn.Close()
-}
-
-func (wc *hybridConn) LocalAddr() net.Addr {
- return &addr{"wsh", wc.conn.LocalAddr().String()}
-}
-
-func (wc *hybridConn) RemoteAddr() net.Addr {
- return &addr{"wsh", wc.conn.RemoteAddr().String()}
-}
-
-func (wc *hybridConn) SetDeadline(t time.Time) error {
- return wc.conn.SetDeadline(t)
-}
-
-func (wc *hybridConn) SetReadDeadline(t time.Time) error {
- return wc.conn.SetReadDeadline(t)
-}
-
-func (wc *hybridConn) SetWriteDeadline(t time.Time) error {
- return wc.conn.SetWriteDeadline(t)
-}
diff --git a/runtime/internal/lib/websocket/conn_nacl.go b/runtime/internal/lib/websocket/conn_nacl.go
deleted file mode 100644
index ca7e16d..0000000
--- a/runtime/internal/lib/websocket/conn_nacl.go
+++ /dev/null
@@ -1,115 +0,0 @@
-// 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.
-
-// +build nacl
-
-package websocket
-
-import (
- "net"
- "net/url"
- "runtime/ppapi"
- "sync"
- "time"
-
- "v.io/v23/context"
-)
-
-// Ppapi instance which must be set before the Dial is called.
-var PpapiInstance ppapi.Instance
-
-func WebsocketConn(address string, ws *ppapi.WebsocketConn) net.Conn {
- return &wrappedConn{
- address: address,
- ws: ws,
- }
-}
-
-type wrappedConn struct {
- address string
- ws *ppapi.WebsocketConn
- readLock sync.Mutex
- writeLock sync.Mutex
- currBuffer []byte
-}
-
-func Dial(ctx *context.T, protocol, address string, timeout time.Duration) (net.Conn, error) {
- inst := PpapiInstance
- u, err := url.Parse("ws://" + address)
- if err != nil {
- return nil, err
- }
-
- ws, err := inst.DialWebsocket(u.String())
- if err != nil {
- return nil, err
- }
- return WebsocketConn(address, ws), nil
-}
-
-func Resolve(ctx *context.T, protocol, address string) (string, string, error) {
- return "ws", address, nil
-}
-
-func (c *wrappedConn) Read(b []byte) (int, error) {
- c.readLock.Lock()
- defer c.readLock.Unlock()
-
- var err error
- if len(c.currBuffer) == 0 {
- c.currBuffer, err = c.ws.ReceiveMessage()
- if err != nil {
- return 0, err
- }
- }
-
- n := copy(b, c.currBuffer)
- c.currBuffer = c.currBuffer[n:]
- return n, nil
-}
-
-func (c *wrappedConn) Write(b []byte) (int, error) {
- c.writeLock.Lock()
- defer c.writeLock.Unlock()
- if err := c.ws.SendMessage(b); err != nil {
- return 0, err
- }
- return len(b), nil
-}
-
-func (c *wrappedConn) Close() error {
- return c.ws.Close()
-}
-
-func (c *wrappedConn) LocalAddr() net.Addr {
- return websocketAddr{s: c.address}
-}
-
-func (c *wrappedConn) RemoteAddr() net.Addr {
- return websocketAddr{s: c.address}
-}
-
-func (c *wrappedConn) SetDeadline(t time.Time) error {
- panic("SetDeadline not implemented.")
-}
-
-func (c *wrappedConn) SetReadDeadline(t time.Time) error {
- panic("SetReadDeadline not implemented.")
-}
-
-func (c *wrappedConn) SetWriteDeadline(t time.Time) error {
- panic("SetWriteDeadline not implemented.")
-}
-
-type websocketAddr struct {
- s string
-}
-
-func (websocketAddr) Network() string {
- return "ws"
-}
-
-func (w websocketAddr) String() string {
- return w.s
-}
diff --git a/runtime/internal/lib/websocket/conn_test.go b/runtime/internal/lib/websocket/conn_test.go
deleted file mode 100644
index 292f616..0000000
--- a/runtime/internal/lib/websocket/conn_test.go
+++ /dev/null
@@ -1,120 +0,0 @@
-// 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.
-
-// +build !nacl
-
-package websocket
-
-import (
- "bytes"
- "net"
- "net/http"
- "sync"
- "testing"
- "time"
-
- "github.com/gorilla/websocket"
-
- "v.io/v23/context"
-)
-
-func writer(c net.Conn, data []byte, times int, wg *sync.WaitGroup) {
- defer wg.Done()
- b := []byte{byte(len(data))}
- b = append(b, data...)
- for i := 0; i < times; i++ {
- c.Write(b)
- }
-}
-
-func readMessage(c net.Conn) ([]byte, error) {
- var length [1]byte
- // Read the size
- for {
- n, err := c.Read(length[:])
- if err != nil {
- return nil, err
- }
- if n == 1 {
- break
- }
- }
- size := int(length[0])
- buf := make([]byte, size)
- n := 0
- for n < size {
- nn, err := c.Read(buf[n:])
- if err != nil {
- return buf, err
- }
- n += nn
- }
-
- return buf, nil
-}
-
-func reader(t *testing.T, c net.Conn, expected []byte, totalWrites int) {
- totalReads := 0
- for buf, err := readMessage(c); err == nil; buf, err = readMessage(c) {
- totalReads++
- if !bytes.Equal(buf, expected) {
- t.Errorf("Unexpected message %v, expected %v", buf, expected)
- }
- }
- if totalReads != totalWrites {
- t.Errorf("wrong number of messages expected %v, got %v", totalWrites, totalReads)
- }
-}
-
-func TestMultipleGoRoutines(t *testing.T) {
- l, err := net.Listen("tcp", "127.0.0.1:0")
- if err != nil {
- t.Fatalf("Failed to listen: %v", err)
- }
- addr := l.Addr()
- input := []byte("no races here")
- const numWriters int = 12
- const numWritesPerWriter int = 1000
- const totalWrites int = numWriters * numWritesPerWriter
- s := &http.Server{
- Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- if r.Method != "GET" {
- http.Error(w, "Method not allowed.", http.StatusMethodNotAllowed)
- return
- }
- ws, err := websocket.Upgrade(w, r, nil, 1024, 1024)
- if _, ok := err.(websocket.HandshakeError); ok {
- http.Error(w, "Not a websocket handshake", 400)
- return
- } else if err != nil {
- http.Error(w, "Internal Error", 500)
- return
- }
- reader(t, WebsocketConn(ws), input, totalWrites)
- }),
- }
- // Dial out in another go routine
- go func() {
- ctx, _ := context.RootContext()
- conn, err := Dial(ctx, "tcp", addr.String(), time.Second)
- numTries := 0
- for err != nil && numTries < 5 {
- numTries++
- time.Sleep(time.Second)
- }
-
- if err != nil {
- t.Fatalf("failed to connect to server: %v", err)
- }
- var writers sync.WaitGroup
- writers.Add(numWriters)
- for i := 0; i < numWriters; i++ {
- go writer(conn, input, numWritesPerWriter, &writers)
- }
- writers.Wait()
- conn.Close()
- l.Close()
- }()
- s.Serve(l)
-}
diff --git a/runtime/internal/lib/websocket/dialer.go b/runtime/internal/lib/websocket/dialer.go
deleted file mode 100644
index f7a3b21..0000000
--- a/runtime/internal/lib/websocket/dialer.go
+++ /dev/null
@@ -1,47 +0,0 @@
-// 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.
-
-// +build !nacl
-
-package websocket
-
-import (
- "net"
- "net/http"
- "net/url"
- "time"
-
- "github.com/gorilla/websocket"
-
- "v.io/x/ref/runtime/internal/lib/tcputil"
-
- "v.io/v23/context"
-)
-
-func Dial(ctx *context.T, protocol, address string, timeout time.Duration) (net.Conn, error) {
- var then time.Time
- if timeout > 0 {
- then = time.Now().Add(timeout)
- }
- tcp := mapWebSocketToTCP[protocol]
- conn, err := net.DialTimeout(tcp, address, timeout)
- if err != nil {
- return nil, err
- }
- conn.SetReadDeadline(then)
- if err := tcputil.EnableTCPKeepAlive(conn); err != nil {
- return nil, err
- }
- u, err := url.Parse("ws://" + address)
- if err != nil {
- return nil, err
- }
- ws, _, err := websocket.NewClient(conn, u, http.Header{}, 4096, 4096)
- if err != nil {
- return nil, err
- }
- var zero time.Time
- conn.SetDeadline(zero)
- return WebsocketConn(ws), nil
-}
diff --git a/runtime/internal/lib/websocket/hybrid.go b/runtime/internal/lib/websocket/hybrid.go
deleted file mode 100644
index fb92d0a..0000000
--- a/runtime/internal/lib/websocket/hybrid.go
+++ /dev/null
@@ -1,56 +0,0 @@
-// 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 websocket
-
-import (
- "net"
- "time"
-
- "v.io/x/ref/runtime/internal/lib/tcputil"
-
- "v.io/v23/context"
-)
-
-// TODO(jhahn): Figure out a way for this mapping to be shared.
-var mapWebSocketToTCP = map[string]string{"ws": "tcp", "ws4": "tcp4", "ws6": "tcp6", "wsh": "tcp", "wsh4": "tcp4", "wsh6": "tcp6", "tcp": "tcp", "tcp4": "tcp4", "tcp6": "tcp6"}
-
-// HybridDial returns net.Conn that can be used with a HybridListener but
-// always uses tcp. A client must specifically elect to use websockets by
-// calling websocket.Dialer. The returned net.Conn will report 'tcp' as its
-// Network.
-func HybridDial(ctx *context.T, network, address string, timeout time.Duration) (net.Conn, error) {
- tcp := mapWebSocketToTCP[network]
- conn, err := net.DialTimeout(tcp, address, timeout)
- if err != nil {
- return nil, err
- }
- if err := tcputil.EnableTCPKeepAlive(conn); err != nil {
- return nil, err
- }
- return conn, nil
-}
-
-// HybridResolve performs a DNS resolution on the network, address and always
-// returns tcp as its Network.
-func HybridResolve(ctx *context.T, network, address string) (string, string, error) {
- tcp := mapWebSocketToTCP[network]
- tcpAddr, err := net.ResolveTCPAddr(tcp, address)
- if err != nil {
- return "", "", err
- }
- return tcp, tcpAddr.String(), nil
-}
-
-// HybridListener returns a net.Listener that supports both tcp and
-// websockets over the same, single, port. A listen address of
-// --v23.tcp.protocol=wsh --v23.tcp.address=127.0.0.1:8101 means
-// that port 8101 can accept connections that use either tcp or websocket.
-// The listener looks at the first 4 bytes of the incoming data stream
-// to decide if it's a websocket protocol or not. These must be 'GET ' for
-// websockets, all other protocols must guarantee to not send 'GET ' as the
-// first four bytes of the payload.
-func HybridListener(ctx *context.T, protocol, address string) (net.Listener, error) {
- return listener(protocol, address, true)
-}
diff --git a/runtime/internal/lib/websocket/listener.go b/runtime/internal/lib/websocket/listener.go
deleted file mode 100644
index f31f096..0000000
--- a/runtime/internal/lib/websocket/listener.go
+++ /dev/null
@@ -1,233 +0,0 @@
-// 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.
-
-// +build !nacl
-
-package websocket
-
-import (
- "errors"
- "io"
- "net"
- "net/http"
- "sync"
- "time"
-
- "github.com/gorilla/websocket"
-
- "v.io/x/ref/internal/logger"
- "v.io/x/ref/runtime/internal/lib/tcputil"
-
- "v.io/v23/context"
-)
-
-var errListenerIsClosed = errors.New("Listener has been Closed")
-
-const (
- bufferSize = 4096
- classificationTime = 10 * time.Second
-)
-
-// A listener that is able to handle either raw tcp or websocket requests.
-type wsTCPListener struct {
- closed bool // GUARDED_BY(mu)
- mu sync.Mutex
-
- acceptQ chan interface{} // net.Conn or error returned by netLn.Accept
- httpQ chan net.Conn // Candidates for websocket upgrades before being added to acceptQ
- netLn net.Listener // The underlying listener
- httpReq sync.WaitGroup // Number of active HTTP requests
- hybrid bool // true if running in 'hybrid' mode
-}
-
-// chanListener implements net.Listener, with Accept reading from c.
-type chanListener struct {
- net.Listener // Embedded for all other net.Listener functionality.
- c <-chan net.Conn
-}
-
-func (ln *chanListener) Accept() (net.Conn, error) {
- conn, ok := <-ln.c
- if !ok {
- return nil, errListenerIsClosed
- }
- return conn, nil
-}
-
-func Listener(ctx *context.T, protocol, address string) (net.Listener, error) {
- return listener(protocol, address, false)
-}
-
-func listener(protocol, address string, hybrid bool) (net.Listener, error) {
- netLn, err := net.Listen(mapWebSocketToTCP[protocol], address)
- if err != nil {
- return nil, err
- }
- ln := &wsTCPListener{
- acceptQ: make(chan interface{}),
- httpQ: make(chan net.Conn),
- netLn: netLn,
- hybrid: hybrid,
- }
- go ln.netAcceptLoop()
- httpsrv := http.Server{Handler: ln}
- go httpsrv.Serve(&chanListener{Listener: ln, c: ln.httpQ})
- return ln, nil
-}
-
-func (ln *wsTCPListener) Accept() (net.Conn, error) {
- for {
- item, ok := <-ln.acceptQ
- if !ok {
- return nil, errListenerIsClosed
- }
- switch v := item.(type) {
- case net.Conn:
- return v, nil
- case error:
- return nil, v
- default:
- logger.Global().Errorf("Unexpected type %T in channel (%v)", v, v)
- }
- }
-}
-
-func (ln *wsTCPListener) Close() error {
- ln.mu.Lock()
- if ln.closed {
- ln.mu.Unlock()
- return errListenerIsClosed
- }
- ln.closed = true
- ln.mu.Unlock()
- addr := ln.netLn.Addr()
- err := ln.netLn.Close()
- logger.Global().VI(1).Infof("Closed net.Listener on (%q, %q): %v", addr.Network(), addr, err)
- // netAcceptLoop might be trying to push new TCP connections that
- // arrived while the listener was being closed. Drop those.
- drainChan(ln.acceptQ)
- return nil
-}
-
-func (ln *wsTCPListener) netAcceptLoop() {
- var classifications sync.WaitGroup
- defer func() {
- // This sequence of closures is carefully curated based on the
- // following invariants:
- // (1) All calls to ln.classify have been added to classifications.
- // (2) Only ln.classify sends on ln.httpQ
- // (3) All calls to ln.ServeHTTP have been added to ln.httpReq
- // (4) Sends on ln.acceptQ are done by either ln.netAcceptLoop ro ln.ServeHTTP
- classifications.Wait()
- close(ln.httpQ)
- ln.httpReq.Wait()
- close(ln.acceptQ)
- }()
- for {
- conn, err := ln.netLn.Accept()
- if err != nil {
- // If the listener has been closed, quit - otherwise
- // propagate the error.
- ln.mu.Lock()
- closed := ln.closed
- ln.mu.Unlock()
- if closed {
- return
- }
- ln.acceptQ <- err
- continue
- }
- logger.Global().VI(1).Infof("New net.Conn accepted from %s (local address: %s)", conn.RemoteAddr(), conn.LocalAddr())
- if err := tcputil.EnableTCPKeepAlive(conn); err != nil {
- logger.Global().Errorf("Failed to enable TCP keep alive: %v", err)
- }
- classifications.Add(1)
- go ln.classify(conn, &classifications)
- }
-}
-
-// classify classifies conn as either an HTTP connection or a non-HTTP one.
-//
-// If the latter, then the connection is added to ln.acceptQ.
-// If the former, then the connection is queued up for a websocket upgrade.
-func (ln *wsTCPListener) classify(conn net.Conn, done *sync.WaitGroup) {
- defer done.Done()
- isHTTP := true
- if ln.hybrid {
- conn.SetReadDeadline(time.Now().Add(classificationTime))
- defer conn.SetReadDeadline(time.Time{})
- var magic [1]byte
- n, err := io.ReadFull(conn, magic[:])
- if err != nil {
- // Unable to classify, ignore this connection.
- logger.Global().VI(1).Infof("Shutting down connection from %v since the magic bytes could not be read: %v", conn.RemoteAddr(), err)
- conn.Close()
- return
- }
- conn = &hybridConn{conn: conn, buffered: magic[:n]}
- isHTTP = magic[0] == 'G'
- }
- if isHTTP {
- ln.httpReq.Add(1)
- ln.httpQ <- conn
- return
- }
- ln.acceptQ <- conn
-}
-
-func (ln *wsTCPListener) ServeHTTP(w http.ResponseWriter, r *http.Request) {
- defer ln.httpReq.Done()
- if r.Method != "GET" {
- http.Error(w, "Method not allowed.", http.StatusMethodNotAllowed)
- return
- }
- ws, err := websocket.Upgrade(w, r, nil, bufferSize, bufferSize)
- if _, ok := err.(websocket.HandshakeError); ok {
- // Close the connection to not serve HTTP requests from this connection
- // any more. Otherwise panic from negative httpReq counter can occur.
- // Although go http.Server gracefully shutdowns the server from a panic,
- // it would be nice to avoid it.
- w.Header().Set("Connection", "close")
- http.Error(w, "Not a websocket handshake", http.StatusBadRequest)
- logger.Global().Errorf("Rejected a non-websocket request: %v", err)
- return
- }
- if err != nil {
- w.Header().Set("Connection", "close")
- http.Error(w, "Internal Error", http.StatusInternalServerError)
- logger.Global().Errorf("Rejected a non-websocket request: %v", err)
- return
- }
- ln.acceptQ <- WebsocketConn(ws)
-}
-
-type addr struct{ n, a string }
-
-func (a addr) Network() string {
- return a.n
-}
-
-func (a addr) String() string {
- return a.a
-}
-
-func (ln *wsTCPListener) Addr() net.Addr {
- protocol := "ws"
- if ln.hybrid {
- protocol = "wsh"
- }
- return addr{protocol, ln.netLn.Addr().String()}
-}
-
-func drainChan(c <-chan interface{}) {
- for {
- item, ok := <-c
- if !ok {
- return
- }
- if conn, ok := item.(net.Conn); ok {
- conn.Close()
- }
- }
-}
diff --git a/runtime/internal/lib/websocket/listener_nacl.go b/runtime/internal/lib/websocket/listener_nacl.go
deleted file mode 100644
index ebf6255..0000000
--- a/runtime/internal/lib/websocket/listener_nacl.go
+++ /dev/null
@@ -1,24 +0,0 @@
-// 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.
-
-// +build nacl
-
-package websocket
-
-import (
- "fmt"
- "net"
-
- "v.io/v23/context"
-)
-
-// Websocket listeners are not supported in NaCl.
-// This file is needed for compilation only.
-func listener(protocol, address string, hybrid bool) (net.Listener, error) {
- return nil, fmt.Errorf("Websocket Listener called in nacl code!")
-}
-
-func Listener(ctx *context.T, protocol, address string) (net.Listener, error) {
- return nil, fmt.Errorf("Websocket Listener called in nacl code!")
-}
diff --git a/runtime/internal/lib/websocket/listener_test.go b/runtime/internal/lib/websocket/listener_test.go
deleted file mode 100644
index f19ddd3..0000000
--- a/runtime/internal/lib/websocket/listener_test.go
+++ /dev/null
@@ -1,98 +0,0 @@
-// 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.
-
-// +build !nacl
-
-package websocket
-
-import (
- "bytes"
- "log"
- "net"
- "strings"
- "testing"
- "time"
-
- "v.io/v23/context"
-)
-
-func TestAcceptsAreNotSerialized(t *testing.T) {
- ctx, _ := context.RootContext()
- ln, err := HybridListener(ctx, "wsh", "127.0.0.1:0")
- if err != nil {
- t.Fatal(err)
- }
- defer func() { go ln.Close() }()
- portscan := make(chan struct{})
-
- // Goroutine that continuously accepts connections.
- go func() {
- for {
- conn, err := ln.Accept()
- if err != nil {
- return
- }
- defer conn.Close()
- }
- }()
-
- // Imagine some client was port scanning and thus opened a TCP
- // connection (but never sent the bytes)
- go func() {
- conn, err := net.Dial("tcp", ln.Addr().String())
- if err != nil {
- t.Error(err)
- }
- close(portscan)
- // Keep the connection alive by blocking on a read. (The read
- // should return once the test exits).
- conn.Read(make([]byte, 1024))
- }()
- // Another client that dials a legitimate connection should not be
- // blocked on the portscanner.
- // (Wait for the portscanner to establish the TCP connection first).
- <-portscan
- conn, err := Dial(ctx, ln.Addr().Network(), ln.Addr().String(), time.Second)
- if err != nil {
- t.Fatal(err)
- }
- conn.Close()
-}
-
-func TestNonWebsocketRequest(t *testing.T) {
- ctx, _ := context.RootContext()
- ln, err := HybridListener(ctx, "wsh", "127.0.0.1:0")
- if err != nil {
- t.Fatal(err)
- }
- defer func() { go ln.Close() }()
-
- // Goroutine that continuously accepts connections.
- go func() {
- for {
- _, err := ln.Accept()
- if err != nil {
- return
- }
- }
- }()
-
- var out bytes.Buffer
- log.SetOutput(&out)
-
- // Imagine some client keeps sending non-websocket requests.
- conn, err := net.Dial("tcp", ln.Addr().String())
- if err != nil {
- t.Error(err)
- }
- for i := 0; i < 2; i++ {
- conn.Write([]byte("GET / HTTP/1.1\r\n\r\n"))
- conn.Read(make([]byte, 1024))
- }
-
- logs := out.String()
- if strings.Contains(logs, "panic") {
- t.Errorf("Unexpected panic:\n%s", logs)
- }
-}
diff --git a/runtime/internal/lib/websocket/resolver.go b/runtime/internal/lib/websocket/resolver.go
deleted file mode 100644
index 5a99c23..0000000
--- a/runtime/internal/lib/websocket/resolver.go
+++ /dev/null
@@ -1,23 +0,0 @@
-// 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.
-
-// +build !nacl
-
-package websocket
-
-import (
- "net"
-
- "v.io/v23/context"
-)
-
-// Resolve performs a DNS resolution on the provided protocol and address.
-func Resolve(ctx *context.T, protocol, address string) (string, string, error) {
- tcp := mapWebSocketToTCP[protocol]
- tcpAddr, err := net.ResolveTCPAddr(tcp, address)
- if err != nil {
- return "", "", err
- }
- return "ws", tcpAddr.String(), nil
-}
diff --git a/runtime/internal/lib/websocket/util_test.go b/runtime/internal/lib/websocket/util_test.go
deleted file mode 100644
index f7a7c4e..0000000
--- a/runtime/internal/lib/websocket/util_test.go
+++ /dev/null
@@ -1,290 +0,0 @@
-// 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 websocket_test
-
-import (
- "encoding/gob"
- "fmt"
- "hash/crc64"
- "io"
- "math/rand"
- "net"
- "sync"
- "testing"
- "time"
-
- "v.io/v23/context"
- "v.io/v23/rpc"
-)
-
-var crcTable *crc64.Table
-
-func init() {
- crcTable = crc64.MakeTable(crc64.ISO)
-}
-
-func newSender(t *testing.T, dialer rpc.DialerFunc, protocol, address string) net.Conn {
- ctx, _ := context.RootContext()
- conn, err := dialer(ctx, protocol, address, time.Minute)
- if err != nil {
- t.Fatalf("unexpected error: %s", err)
- return nil
- }
- return conn
-}
-
-func checkProtocols(conn net.Conn, tx string) error {
- expectedProtocol := map[string]string{
- "ws": "ws", "wsh": "tcp", "tcp": "tcp",
- }
- if got, want := conn.LocalAddr().Network(), expectedProtocol[tx]; got != want {
- return fmt.Errorf("wrong local protocol: got %q, want %q", got, want)
- }
- // Can't tell that the remote protocol is really 'wsh'
- if got, want := conn.RemoteAddr().Network(), expectedProtocol[tx]; got != want {
- return fmt.Errorf("wrong remote protocol: got %q, want %q", got, want)
- }
- return nil
-}
-
-type packet struct {
- Data []byte
- Size int
- CRC64 uint64
-}
-
-func createPacket() *packet {
- p := &packet{}
- p.Size = rand.Intn(4 * 1024)
- p.Data = make([]byte, p.Size)
- for i := 0; i < p.Size; i++ {
- p.Data[i] = byte(rand.Int() & 0xff)
- }
- p.CRC64 = crc64.Checksum([]byte(p.Data), crcTable)
- return p
-}
-
-func checkPacket(p *packet) error {
- if got, want := len(p.Data), p.Size; got != want {
- return fmt.Errorf("wrong sizes: got %d, want %d", got, want)
- }
- crc := crc64.Checksum(p.Data, crcTable)
- if got, want := crc, p.CRC64; got != want {
- return fmt.Errorf("wrong crc: got %d, want %d", got, want)
- }
- return nil
-}
-
-type backChannel struct {
- crcChan chan uint64
- byteChan chan []byte
- errChan chan error
-}
-
-type bcTable struct {
- ready *sync.Cond
- sync.Mutex
- bc map[string]*backChannel
-}
-
-var globalBCTable bcTable
-
-func init() {
- globalBCTable.ready = sync.NewCond(&globalBCTable)
- globalBCTable.bc = make(map[string]*backChannel)
-}
-
-func (bt *bcTable) waitfor(key string) *backChannel {
- bt.Lock()
- defer bt.Unlock()
- for {
- bc := bt.bc[key]
- if bc != nil {
- delete(bt.bc, key)
- return bc
- }
- bt.ready.Wait()
- }
-}
-
-func (bt *bcTable) add(key string, bc *backChannel) {
- bt.Lock()
- bt.bc[key] = bc
- bt.Unlock()
- bt.ready.Broadcast()
-}
-
-func packetReceiver(t *testing.T, ln net.Listener, bc *backChannel) {
- conn, err := ln.Accept()
- if err != nil {
- close(bc.crcChan)
- close(bc.errChan)
- return
- }
-
- globalBCTable.add(conn.RemoteAddr().String(), bc)
-
- defer conn.Close()
- dec := gob.NewDecoder(conn)
- rxed := 0
- for {
- var p packet
- err := dec.Decode(&p)
- if err != nil {
- if err != io.EOF {
- bc.errChan <- fmt.Errorf("unexpected error: %s", err)
- }
- close(bc.crcChan)
- close(bc.errChan)
- return
- }
- if err := checkPacket(&p); err != nil {
- bc.errChan <- fmt.Errorf("unexpected error: %s", err)
- }
- bc.crcChan <- p.CRC64
- rxed++
- }
-}
-
-func packetSender(t *testing.T, nPackets int, conn net.Conn) {
- txCRCs := make([]uint64, nPackets)
- enc := gob.NewEncoder(conn)
- for i := 0; i < nPackets; i++ {
- p := createPacket()
- txCRCs[i] = p.CRC64
- if err := enc.Encode(p); err != nil {
- t.Fatalf("unexpected error: %s", err)
- }
- }
- conn.Close() // Close the connection so that the receiver quits.
-
- bc := globalBCTable.waitfor(conn.LocalAddr().String())
- for err := range bc.errChan {
- if err != nil {
- t.Fatalf(err.Error())
- }
- }
-
- rxed := 0
- for rxCRC := range bc.crcChan {
- if got, want := rxCRC, txCRCs[rxed]; got != want {
- t.Errorf("%s -> %s: packet %d: mismatched CRCs: got %d, want %d", conn.LocalAddr().String(), conn.RemoteAddr().String(), rxed, got, want)
- }
- rxed++
- }
- if got, want := rxed, nPackets; got != want {
- t.Fatalf("%s -> %s: got %d, want %d", conn.LocalAddr().String(), conn.RemoteAddr().String(), got, want)
- }
-}
-
-func packetRunner(t *testing.T, ln net.Listener, dialer rpc.DialerFunc, protocol, address string) {
- nPackets := 100
- go packetReceiver(t, ln, &backChannel{
- crcChan: make(chan uint64, nPackets),
- errChan: make(chan error, nPackets),
- })
-
- conn := newSender(t, dialer, protocol, address)
- if err := checkProtocols(conn, protocol); err != nil {
- t.Fatalf(err.Error())
- }
- packetSender(t, nPackets, conn)
-}
-
-func byteReceiver(t *testing.T, ln net.Listener, bc *backChannel) {
- conn, err := ln.Accept()
- if err != nil {
- close(bc.byteChan)
- close(bc.errChan)
- return
- }
- globalBCTable.add(conn.RemoteAddr().String(), bc)
-
- defer conn.Close()
- rxed := 0
- for {
- buf := make([]byte, rxed+1)
- n, err := conn.Read(buf)
- if err != nil {
- if err != io.EOF {
- bc.errChan <- fmt.Errorf("unexpected error: %s", err)
- }
- close(bc.byteChan)
- close(bc.errChan)
- return
- }
- if got, want := n, len(buf[:n]); got != want {
- bc.errChan <- fmt.Errorf("%s -> %s: got %d bytes, expected %d", conn.LocalAddr().String(), conn.RemoteAddr().String(), got, want)
- }
- if got, want := buf[0], byte(0xff); got != want {
- bc.errChan <- fmt.Errorf("%s -> %s: got %x, want %x", conn.LocalAddr().String(), conn.RemoteAddr().String(), got, want)
- }
- bc.byteChan <- buf[:n]
- rxed++
- }
-}
-
-func byteSender(t *testing.T, nIterations int, conn net.Conn) {
- txBytes := make([][]byte, nIterations+1)
- for i := 0; i < nIterations; i++ {
- p := make([]byte, i+1)
- p[0] = 0xff
- for j := 1; j <= i; j++ {
- p[j] = byte(64 + i) // start at ASCII A
- }
- txBytes[i] = p
- n, err := conn.Write(p)
- if err != nil {
- t.Fatalf("unexpected error: %s", err)
- }
- if got, want := n, i+1; got != want {
- t.Fatalf("wrote %d, not %d bytes", got, want)
- }
- }
- conn.Close()
-
- bc := globalBCTable.waitfor(conn.LocalAddr().String())
-
- for err := range bc.errChan {
- if err != nil {
- t.Fatalf(err.Error())
- }
- }
-
- addr := fmt.Sprintf("%s -> %s", conn.LocalAddr().String(), conn.RemoteAddr().String())
- rxed := 0
- for rxBytes := range bc.byteChan {
- if got, want := len(rxBytes), rxed+1; got != want {
- t.Fatalf("%s: got %d, want %d bytes", addr, got, want)
- }
- if got, want := rxBytes[0], byte(0xff); got != want {
- t.Fatalf("%s: got %x, want %x", addr, got, want)
- }
- for i := 0; i < len(rxBytes); i++ {
- if got, want := rxBytes[i], txBytes[rxed][i]; got != want {
- t.Fatalf("%s: got %c, want %c", addr, got, want)
- }
- }
- rxed++
- }
- if got, want := rxed, nIterations; got != want {
- t.Fatalf("%s: got %d, want %d", addr, got, want)
- }
-}
-
-func byteRunner(t *testing.T, ln net.Listener, dialer rpc.DialerFunc, protocol, address string) {
- nIterations := 10
- go byteReceiver(t, ln, &backChannel{
- byteChan: make(chan []byte, nIterations),
- errChan: make(chan error, nIterations),
- })
-
- conn := newSender(t, dialer, protocol, address)
- defer conn.Close()
- if err := checkProtocols(conn, protocol); err != nil {
- t.Fatalf(err.Error())
- }
- byteSender(t, nIterations, conn)
-}
diff --git a/runtime/internal/lib/websocket/ws_test.go b/runtime/internal/lib/websocket/ws_test.go
deleted file mode 100644
index a193885..0000000
--- a/runtime/internal/lib/websocket/ws_test.go
+++ /dev/null
@@ -1,106 +0,0 @@
-// 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 websocket_test
-
-import (
- "net"
- "sync"
- "testing"
- "time"
-
- "v.io/v23/context"
- "v.io/v23/rpc"
- "v.io/x/ref/runtime/internal/lib/websocket"
-)
-
-func packetTester(t *testing.T, dialer rpc.DialerFunc, listener rpc.ListenerFunc, txProtocol, rxProtocol string) {
- ctx, _ := context.RootContext()
- ln, err := listener(ctx, rxProtocol, "127.0.0.1:0")
- if err != nil {
- t.Fatalf("unexpected error: %s", err)
- }
- defer ln.Close()
- if got, want := ln.Addr().Network(), rxProtocol; got != want {
- t.Fatalf("got %q, want %q", got, want)
- }
-
- packetRunner(t, ln, dialer, txProtocol, ln.Addr().String())
- packetRunner(t, ln, dialer, txProtocol, ln.Addr().String())
-}
-
-func byteTester(t *testing.T, dialer rpc.DialerFunc, listener rpc.ListenerFunc, txProtocol, rxProtocol string) {
- ctx, _ := context.RootContext()
- ln, err := listener(ctx, rxProtocol, "127.0.0.1:0")
- if err != nil {
- t.Fatalf("unexpected error: %s", err)
- }
- defer ln.Close()
- if got, want := ln.Addr().Network(), rxProtocol; got != want {
- t.Fatalf("got %q, want %q", got, want)
- }
-
- byteRunner(t, ln, dialer, txProtocol, ln.Addr().String())
- byteRunner(t, ln, dialer, txProtocol, ln.Addr().String())
-
-}
-
-func simpleDial(ctx *context.T, p, a string, timeout time.Duration) (net.Conn, error) {
- return net.DialTimeout(p, a, timeout)
-}
-
-func TestWSToWS(t *testing.T) {
- byteTester(t, websocket.Dial, websocket.Listener, "ws", "ws")
- packetTester(t, websocket.Dial, websocket.Listener, "ws", "ws")
-}
-
-func TestWSToWSH(t *testing.T) {
- byteTester(t, websocket.Dial, websocket.HybridListener, "ws", "wsh")
- //packetTester(t, websocket.Dial, websocket.HybridListener, "ws", "wsh")
-}
-
-func TestWSHToWSH(t *testing.T) {
- byteTester(t, websocket.HybridDial, websocket.HybridListener, "wsh", "wsh")
- packetTester(t, websocket.HybridDial, websocket.HybridListener, "wsh", "wsh")
-}
-
-func TestTCPToWSH(t *testing.T) {
- byteTester(t, simpleDial, websocket.HybridListener, "tcp", "wsh")
- packetTester(t, simpleDial, websocket.HybridListener, "tcp", "wsh")
-}
-
-func TestMixed(t *testing.T) {
- ctx, _ := context.RootContext()
- ln, err := websocket.HybridListener(ctx, "wsh", "127.0.0.1:0")
- if err != nil {
- t.Fatalf("unexpected error: %s", err)
- }
- defer ln.Close()
-
- var pwg sync.WaitGroup
- packetTest := func(dialer rpc.DialerFunc, protocol string) {
- packetRunner(t, ln, dialer, protocol, ln.Addr().String())
- pwg.Done()
- }
-
- pwg.Add(4)
- go packetTest(websocket.Dial, "ws")
- go packetTest(simpleDial, "tcp")
- go packetTest(websocket.Dial, "ws")
- go packetTest(websocket.HybridDial, "wsh")
- pwg.Wait()
-
- var bwg sync.WaitGroup
- byteTest := func(dialer rpc.DialerFunc, protocol string) {
- byteRunner(t, ln, dialer, protocol, ln.Addr().String())
- bwg.Done()
- }
- bwg.Add(4)
- go byteTest(websocket.Dial, "ws")
- go byteTest(simpleDial, "tcp")
- go byteTest(websocket.Dial, "ws")
- go byteTest(websocket.HybridDial, "wsh")
-
- bwg.Wait()
-}
diff --git a/runtime/internal/rpc/benchmark/simple/AndroidManifest.xml b/runtime/internal/rpc/benchmark/simple/AndroidManifest.xml
new file mode 100644
index 0000000..41917b0
--- /dev/null
+++ b/runtime/internal/rpc/benchmark/simple/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+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.
+-->
+<manifest
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ package="io.v.x.ref.runtime.internal.rpc.benchmark.simple"
+ android:versionCode="1"
+ android:versionName="1.0">
+
+ <!-- http://developer.android.com/guide/topics/manifest/manifest-intro.html#perms -->
+ <uses-permission android:name="android.permission.INTERNET" />
+
+ <application android:label="v23rpc" android:debuggable="true">
+
+ <activity android:name="org.golang.app.GoNativeActivity"
+ android:label="v23rpc"
+ android:configChanges="orientation|keyboardHidden">
+ <meta-data android:name="android.app.lib_name" android:value="v23rpc" />
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/runtime/internal/rpc/benchmark/simple/benchmark.go b/runtime/internal/rpc/benchmark/simple/benchmark.go
new file mode 100644
index 0000000..482b0fb
--- /dev/null
+++ b/runtime/internal/rpc/benchmark/simple/benchmark.go
@@ -0,0 +1,208 @@
+// 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
+
+import (
+ "flag"
+ "fmt"
+ "runtime"
+ "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/x/lib/ibe"
+ "v.io/x/ref/lib/security/bcrypter"
+ "v.io/x/ref/lib/security/securityflag"
+ _ "v.io/x/ref/runtime/factories/roaming"
+ "v.io/x/ref/runtime/internal/flow/flowtest"
+ fmanager "v.io/x/ref/runtime/internal/flow/manager"
+ "v.io/x/ref/runtime/internal/rpc/benchmark/internal"
+ "v.io/x/ref/test"
+ "v.io/x/ref/test/benchmark"
+ "v.io/x/ref/test/testutil"
+)
+
+const (
+ payloadSize = 1000
+ chunkCnt = 10000
+
+ bulkPayloadSize = 1000000
+
+ numCPUs = 2
+ defaultBenchTime = 5 * time.Second
+)
+
+var ctx *context.T
+
+type benchFun func(b *testing.B)
+
+func newServer(ctx *context.T, opts ...rpc.ServerOpt) (*context.T, naming.Endpoint) {
+ ctx, server, err := v23.WithNewServer(ctx, "", internal.NewService(), securityflag.NewAuthorizerOrDie(), opts...)
+ if err != nil {
+ ctx.Fatalf("NewServer failed: %v", err)
+ }
+ return ctx, server.Status().Endpoints[0]
+}
+
+// Benchmark for measuring RPC connection time including authentication.
+//
+// rpc.Client doesn't export an interface for closing connection. So we
+// use the stream manager directly here.
+func benchmarkRPCConnection(b *testing.B) {
+ mp := runtime.GOMAXPROCS(numCPUs)
+ defer runtime.GOMAXPROCS(mp)
+
+ ctx, serverEP := newServer(ctx)
+
+ principal := testutil.NewPrincipal("test")
+ nctx, _ := v23.WithPrincipal(ctx, principal)
+
+ b.StopTimer()
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ mctx, cancel := context.WithCancel(nctx)
+ m := fmanager.New(mctx, naming.FixedRoutingID(0xc), nil, 0)
+ b.StartTimer()
+ _, err := m.Dial(mctx, serverEP, flowtest.AllowAllPeersAuthorizer{}, 0)
+ if err != nil {
+ ctx.Fatalf("Dial failed: %v", err)
+ }
+ b.StopTimer()
+ cancel()
+ <-m.Closed()
+ }
+}
+
+// Benchmark for measuring RPC connection time when using private mutual
+// authentication. 'serverAuth' is the authorization policy used by the
+// server while revealing its blessings, and 'clientBlessing' is the blessing
+// used by the client.
+//
+// The specific protocol being benchmarked is Protocol 3 from the doc:
+// https://docs.google.com/document/d/1FpLJSiKy4sXxRUSZh1BQrhUEn7io-dGW7y-DMszI21Q/edit
+func benchmarkPrivateRPCConnection(ctx *context.T, serverAuth []security.BlessingPattern, clientBlessing string) benchFun {
+ return func(b *testing.B) {
+ mp := runtime.GOMAXPROCS(numCPUs)
+ defer runtime.GOMAXPROCS(mp)
+
+ ctx, privateServerEP := newServer(ctx, options.ServerPeers(serverAuth))
+
+ principal := testutil.NewPrincipal(clientBlessing)
+ nctx, _ := v23.WithPrincipal(ctx, principal)
+
+ b.StopTimer()
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ mctx, cancel := context.WithCancel(nctx)
+ m := fmanager.New(mctx, naming.FixedRoutingID(0xc), nil, 0)
+ b.StartTimer()
+ _, err := m.Dial(nctx, privateServerEP, flowtest.AllowAllPeersAuthorizer{}, 0)
+ if err != nil {
+ ctx.Fatalf("Dial failed: %v", err)
+ }
+ b.StopTimer()
+ cancel()
+ <-m.Closed()
+ }
+ }
+}
+
+// Benchmark for non-streaming RPC.
+func benchmarkRPC(b *testing.B) {
+ ctx, serverEP := newServer(ctx)
+ mp := runtime.GOMAXPROCS(numCPUs)
+ defer runtime.GOMAXPROCS(mp)
+ internal.CallEcho(b, ctx, serverEP.Name(), b.N, payloadSize, benchmark.NewStats(1))
+}
+
+// Benchmark for streaming RPC.
+func benchmarkStreamingRPC(b *testing.B) {
+ ctx, serverEP := newServer(ctx)
+ mp := runtime.GOMAXPROCS(numCPUs)
+ defer runtime.GOMAXPROCS(mp)
+ internal.CallEchoStream(b, ctx, serverEP.Name(), b.N, chunkCnt, payloadSize, benchmark.NewStats(1))
+}
+
+// Benchmark for measuring throughput in streaming RPC.
+func benchmarkStreamingRPCThroughput(b *testing.B) {
+ ctx, serverEP := newServer(ctx)
+ mp := runtime.GOMAXPROCS(numCPUs)
+ defer runtime.GOMAXPROCS(mp)
+ internal.CallEchoStream(b, ctx, serverEP.Name(), 1, b.N, bulkPayloadSize, benchmark.NewStats(1))
+}
+
+func msPerRPC(r testing.BenchmarkResult) float64 {
+ return r.T.Seconds() / float64(r.N) * 1000
+}
+
+func rpcPerSec(r testing.BenchmarkResult) float64 {
+ return float64(r.N) / r.T.Seconds()
+}
+func mbPerSec(r testing.BenchmarkResult) float64 {
+ return (float64(r.Bytes) * float64(r.N) / 1e6) / r.T.Seconds()
+}
+
+func runBenchmarks() {
+ r := testing.Benchmark(benchmarkRPCConnection)
+ fmt.Printf("RPC Connection\t%.2f ms/rpc\n", msPerRPC(r))
+
+ master, err := ibe.SetupBB1()
+ if err != nil {
+ ctx.Fatalf("ibe.SetupBB1 failed: %v", err)
+ }
+ root := bcrypter.NewRoot("root", master)
+ clientBlessing := "root:alice:client"
+
+ // Attach a crypter to the context, and add a blessing private
+ // key to the for 'clientBlesing'.
+ crypter := bcrypter.NewCrypter()
+ cctx := bcrypter.WithCrypter(ctx, crypter)
+ key, err := root.Extract(ctx, clientBlessing)
+ if err != nil {
+ ctx.Fatalf("could not extract private key: %v", err)
+ }
+ if err := crypter.AddKey(cctx, key); err != nil {
+ ctx.Fatalf("could not add key to crypter: %v", err)
+ }
+
+ serverAuthPatterns := [][]security.BlessingPattern{
+ []security.BlessingPattern{"root:alice"},
+ []security.BlessingPattern{"root:bob:friend", "root:carol:friend", "root:alice:client"},
+ []security.BlessingPattern{"root:bob:spouse", "root:bob:enemy", "root:carol:spouse", "root:carol:enemy", "root:alice:client:$"},
+ }
+ for _, serverAuth := range serverAuthPatterns {
+ r = testing.Benchmark(benchmarkPrivateRPCConnection(cctx, serverAuth, clientBlessing))
+ fmt.Printf("Private RPC Connection with server authorization policy %v and client blessing %v \t%.2f ms/rpc\n", serverAuth, clientBlessing, msPerRPC(r))
+ }
+
+ // Create a connection to exclude the setup time from the following benchmarks.
+ ctx, serverEP := newServer(ctx)
+ internal.CallEcho(&testing.B{}, ctx, serverEP.Name(), 1, 0, benchmark.NewStats(1))
+
+ r = testing.Benchmark(benchmarkRPC)
+ fmt.Printf("RPC (echo %vB)\t%.2f ms/rpc (%.2f qps)\n", payloadSize, msPerRPC(r), rpcPerSec(r))
+
+ r = testing.Benchmark(benchmarkStreamingRPC)
+ fmt.Printf("RPC Streaming (echo %vB)\t%.2f ms/rpc\n", payloadSize, msPerRPC(r)/chunkCnt)
+
+ r = testing.Benchmark(benchmarkStreamingRPCThroughput)
+ fmt.Printf("RPC Streaming Throughput (echo %vMB)\t%.2f MB/s\n", bulkPayloadSize/1e6, mbPerSec(r))
+}
+
+func realMain() {
+ // Set the default benchmark time.
+ flag.Set("test.benchtime", defaultBenchTime.String())
+
+ var shutdown v23.Shutdown
+ ctx, shutdown = test.V23Init()
+ defer shutdown()
+
+ runBenchmarks()
+}
diff --git a/runtime/internal/rpc/benchmark/simple/main.go b/runtime/internal/rpc/benchmark/simple/main.go
index 9da3d9e..19e4ac1 100644
--- a/runtime/internal/rpc/benchmark/simple/main.go
+++ b/runtime/internal/rpc/benchmark/simple/main.go
@@ -2,207 +2,10 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+// +build !android
+
package main
-import (
- "flag"
- "fmt"
- "runtime"
- "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/x/lib/ibe"
- "v.io/x/ref/lib/security/bcrypter"
- "v.io/x/ref/lib/security/securityflag"
- _ "v.io/x/ref/runtime/factories/roaming"
- "v.io/x/ref/runtime/internal/flow/flowtest"
- fmanager "v.io/x/ref/runtime/internal/flow/manager"
- "v.io/x/ref/runtime/internal/rpc/benchmark/internal"
- "v.io/x/ref/test"
- "v.io/x/ref/test/benchmark"
- "v.io/x/ref/test/testutil"
-)
-
-const (
- payloadSize = 1000
- chunkCnt = 10000
-
- bulkPayloadSize = 1000000
-
- numCPUs = 2
- defaultBenchTime = 5 * time.Second
-)
-
-var ctx *context.T
-
-type benchFun func(b *testing.B)
-
-func newServer(ctx *context.T, opts ...rpc.ServerOpt) (*context.T, naming.Endpoint) {
- ctx, server, err := v23.WithNewServer(ctx, "", internal.NewService(), securityflag.NewAuthorizerOrDie(), opts...)
- if err != nil {
- ctx.Fatalf("NewServer failed: %v", err)
- }
- return ctx, server.Status().Endpoints[0]
-}
-
-// Benchmark for measuring RPC connection time including authentication.
-//
-// rpc.Client doesn't export an interface for closing connection. So we
-// use the stream manager directly here.
-func benchmarkRPCConnection(b *testing.B) {
- mp := runtime.GOMAXPROCS(numCPUs)
- defer runtime.GOMAXPROCS(mp)
-
- ctx, serverEP := newServer(ctx)
-
- principal := testutil.NewPrincipal("test")
- nctx, _ := v23.WithPrincipal(ctx, principal)
-
- b.StopTimer()
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
- mctx, cancel := context.WithCancel(nctx)
- m := fmanager.New(mctx, naming.FixedRoutingID(0xc), nil, 0)
- b.StartTimer()
- _, err := m.Dial(mctx, serverEP, flowtest.AllowAllPeersAuthorizer{}, 0)
- if err != nil {
- ctx.Fatalf("Dial failed: %v", err)
- }
- b.StopTimer()
- cancel()
- <-m.Closed()
- }
-}
-
-// Benchmark for measuring RPC connection time when using private mutual
-// authentication. 'serverAuth' is the authorization policy used by the
-// server while revealing its blessings, and 'clientBlessing' is the blessing
-// used by the client.
-//
-// The specific protocol being benchmarked is Protocol 3 from the doc:
-// https://docs.google.com/document/d/1FpLJSiKy4sXxRUSZh1BQrhUEn7io-dGW7y-DMszI21Q/edit
-func benchmarkPrivateRPCConnection(ctx *context.T, serverAuth []security.BlessingPattern, clientBlessing string) benchFun {
- return func(b *testing.B) {
- mp := runtime.GOMAXPROCS(numCPUs)
- defer runtime.GOMAXPROCS(mp)
-
- ctx, privateServerEP := newServer(ctx, options.ServerPeers(serverAuth))
-
- principal := testutil.NewPrincipal(clientBlessing)
- nctx, _ := v23.WithPrincipal(ctx, principal)
-
- b.StopTimer()
- b.ResetTimer()
- for i := 0; i < b.N; i++ {
- mctx, cancel := context.WithCancel(nctx)
- m := fmanager.New(mctx, naming.FixedRoutingID(0xc), nil, 0)
- b.StartTimer()
- _, err := m.Dial(nctx, privateServerEP, flowtest.AllowAllPeersAuthorizer{}, 0)
- if err != nil {
- ctx.Fatalf("Dial failed: %v", err)
- }
- b.StopTimer()
- cancel()
- <-m.Closed()
- }
- }
-}
-
-// Benchmark for non-streaming RPC.
-func benchmarkRPC(b *testing.B) {
- ctx, serverEP := newServer(ctx)
- mp := runtime.GOMAXPROCS(numCPUs)
- defer runtime.GOMAXPROCS(mp)
- internal.CallEcho(b, ctx, serverEP.Name(), b.N, payloadSize, benchmark.NewStats(1))
-}
-
-// Benchmark for streaming RPC.
-func benchmarkStreamingRPC(b *testing.B) {
- ctx, serverEP := newServer(ctx)
- mp := runtime.GOMAXPROCS(numCPUs)
- defer runtime.GOMAXPROCS(mp)
- internal.CallEchoStream(b, ctx, serverEP.Name(), b.N, chunkCnt, payloadSize, benchmark.NewStats(1))
-}
-
-// Benchmark for measuring throughput in streaming RPC.
-func benchmarkStreamingRPCThroughput(b *testing.B) {
- ctx, serverEP := newServer(ctx)
- mp := runtime.GOMAXPROCS(numCPUs)
- defer runtime.GOMAXPROCS(mp)
- internal.CallEchoStream(b, ctx, serverEP.Name(), 1, b.N, bulkPayloadSize, benchmark.NewStats(1))
-}
-
-func msPerRPC(r testing.BenchmarkResult) float64 {
- return r.T.Seconds() / float64(r.N) * 1000
-}
-
-func rpcPerSec(r testing.BenchmarkResult) float64 {
- return float64(r.N) / r.T.Seconds()
-}
-func mbPerSec(r testing.BenchmarkResult) float64 {
- return (float64(r.Bytes) * float64(r.N) / 1e6) / r.T.Seconds()
-}
-
-func runBenchmarks() {
- r := testing.Benchmark(benchmarkRPCConnection)
- fmt.Printf("RPC Connection\t%.2f ms/rpc\n", msPerRPC(r))
-
- master, err := ibe.SetupBB1()
- if err != nil {
- ctx.Fatalf("ibe.SetupBB1 failed: %v", err)
- }
- root := bcrypter.NewRoot("root", master)
- clientBlessing := "root:alice:client"
-
- // Attach a crypter to the context, and add a blessing private
- // key to the for 'clientBlesing'.
- crypter := bcrypter.NewCrypter()
- cctx := bcrypter.WithCrypter(ctx, crypter)
- key, err := root.Extract(ctx, clientBlessing)
- if err != nil {
- ctx.Fatalf("could not extract private key: %v", err)
- }
- if err := crypter.AddKey(cctx, key); err != nil {
- ctx.Fatalf("could not add key to crypter: %v", err)
- }
-
- serverAuthPatterns := [][]security.BlessingPattern{
- []security.BlessingPattern{"root:alice"},
- []security.BlessingPattern{"root:bob:friend", "root:carol:friend", "root:alice:client"},
- []security.BlessingPattern{"root:bob:spouse", "root:bob:enemy", "root:carol:spouse", "root:carol:enemy", "root:alice:client:$"},
- }
- for _, serverAuth := range serverAuthPatterns {
- r = testing.Benchmark(benchmarkPrivateRPCConnection(cctx, serverAuth, clientBlessing))
- fmt.Printf("Private RPC Connection with server authorization policy %v and client blessing %v \t%.2f ms/rpc\n", serverAuth, clientBlessing, msPerRPC(r))
- }
-
- // Create a connection to exclude the setup time from the following benchmarks.
- ctx, serverEP := newServer(ctx)
- internal.CallEcho(&testing.B{}, ctx, serverEP.Name(), 1, 0, benchmark.NewStats(1))
-
- r = testing.Benchmark(benchmarkRPC)
- fmt.Printf("RPC (echo %vB)\t%.2f ms/rpc (%.2f qps)\n", payloadSize, msPerRPC(r), rpcPerSec(r))
-
- r = testing.Benchmark(benchmarkStreamingRPC)
- fmt.Printf("RPC Streaming (echo %vB)\t%.2f ms/rpc\n", payloadSize, msPerRPC(r)/chunkCnt)
-
- r = testing.Benchmark(benchmarkStreamingRPCThroughput)
- fmt.Printf("RPC Streaming Throughput (echo %vMB)\t%.2f MB/s\n", bulkPayloadSize/1e6, mbPerSec(r))
-}
-
func main() {
- // Set the default benchmark time.
- flag.Set("test.benchtime", defaultBenchTime.String())
-
- var shutdown v23.Shutdown
- ctx, shutdown = test.V23Init()
- defer shutdown()
-
- runBenchmarks()
+ realMain()
}
diff --git a/runtime/internal/rpc/benchmark/simple/main_android.go b/runtime/internal/rpc/benchmark/simple/main_android.go
new file mode 100644
index 0000000..d07fee2
--- /dev/null
+++ b/runtime/internal/rpc/benchmark/simple/main_android.go
@@ -0,0 +1,61 @@
+// 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.
+
+// +build android
+
+// Android "app" to run the RPC benchmarks.
+//
+// Usage: See run-android.sh
+package main
+
+import (
+ "golang.org/x/mobile/app"
+ "golang.org/x/mobile/event/lifecycle"
+ "golang.org/x/mobile/event/paint"
+ "golang.org/x/mobile/gl"
+ "time"
+)
+
+func main() {
+ done := make(chan struct{})
+ go func() {
+ realMain()
+ close(done)
+ }()
+ app.Main(func(a app.App) {
+ var glctx gl.Context
+ ticks := time.Tick(time.Second / 2)
+ black := false
+ for {
+ select {
+ case <-done:
+ done = nil
+ a.Send(paint.Event{})
+ case <-ticks:
+ black = !black
+ a.Send(paint.Event{})
+ case e := <-a.Events():
+ switch e := a.Filter(e).(type) {
+ case lifecycle.Event:
+ glctx, _ = e.DrawContext.(gl.Context)
+ case paint.Event:
+ if glctx == nil {
+ continue
+ }
+ // solid green success
+ // flashing red/blue: working
+ if done == nil {
+ glctx.ClearColor(0, 1, 0, 1)
+ } else if black {
+ glctx.ClearColor(0, 0, 0, 1)
+ } else {
+ glctx.ClearColor(0, 0, 1, 1)
+ }
+ glctx.Clear(gl.COLOR_BUFFER_BIT)
+ a.Publish()
+ }
+ }
+ }
+ })
+}
diff --git a/runtime/internal/rpc/benchmark/simple/run-android.sh b/runtime/internal/rpc/benchmark/simple/run-android.sh
new file mode 100755
index 0000000..694d152
--- /dev/null
+++ b/runtime/internal/rpc/benchmark/simple/run-android.sh
@@ -0,0 +1,16 @@
+#!/bin/bash
+# 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.
+
+set -e
+set -x
+
+[ -z "${GOPATH}" ] && echo "Must set GOPATH, for example: export GOPATH=\$HOME/go so that gomobile can be installed there" && exit 1
+go get golang.org/x/mobile/cmd/gomobile
+GOMOBILE=${GOPATH}/bin/gomobile
+jiri run ${GOMOBILE} build v.io/x/ref/runtime/internal/rpc/benchmark/simple
+adb install -r ./simple.apk
+echo "Start the v23rpc app on the phone connected to adb"
+adb logcat *:S GoLog:*
+
diff --git a/runtime/internal/rpc/protocols/tcp/init.go b/runtime/internal/rpc/protocols/tcp/init.go
deleted file mode 100644
index a78117f..0000000
--- a/runtime/internal/rpc/protocols/tcp/init.go
+++ /dev/null
@@ -1,76 +0,0 @@
-// 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 tcp
-
-import (
- "fmt"
- "net"
- "time"
-
- "v.io/v23/context"
- "v.io/v23/rpc"
-
- "v.io/x/ref/runtime/internal/lib/tcputil"
-)
-
-func init() {
- rpc.RegisterProtocol("tcp", tcpDial, tcpResolve, tcpListen, "tcp4", "tcp6")
- rpc.RegisterProtocol("tcp4", tcpDial, tcpResolve, tcpListen)
- rpc.RegisterProtocol("tcp6", tcpDial, tcpResolve, tcpListen)
-}
-
-func tcpDial(ctx *context.T, network, address string, timeout time.Duration) (net.Conn, error) {
- conn, err := net.DialTimeout(network, address, timeout)
- if err != nil {
- return nil, err
- }
- if err := tcputil.EnableTCPKeepAlive(conn); err != nil {
- return nil, err
- }
- return conn, nil
-}
-
-// tcpResolve performs a DNS resolution on the provided network and address.
-func tcpResolve(ctx *context.T, network, address string) (string, string, error) {
- tcpAddr, err := net.ResolveTCPAddr(network, address)
- if err != nil {
- return "", "", err
- }
- return tcpAddr.Network(), tcpAddr.String(), nil
-}
-
-// tcpListen returns a listener that sets KeepAlive on all accepted connections.
-func tcpListen(ctx *context.T, network, laddr string) (net.Listener, error) {
- ln, err := net.Listen(network, laddr)
- if err != nil {
- return nil, err
- }
- return &tcpListener{ln}, nil
-}
-
-// tcpListener is a wrapper around net.Listener that sets KeepAlive on all
-// accepted connections.
-type tcpListener struct {
- netLn net.Listener
-}
-
-func (ln *tcpListener) Accept() (net.Conn, error) {
- conn, err := ln.netLn.Accept()
- if err != nil {
- return nil, err
- }
- if err := tcputil.EnableTCPKeepAlive(conn); err != nil {
- return nil, fmt.Errorf("Failed to enable TCP keep alive: %v", err)
- }
- return conn, nil
-}
-
-func (ln *tcpListener) Close() error {
- return ln.netLn.Close()
-}
-
-func (ln *tcpListener) Addr() net.Addr {
- return ln.netLn.Addr()
-}
diff --git a/runtime/internal/rpc/protocols/ws/init.go b/runtime/internal/rpc/protocols/ws/init.go
deleted file mode 100644
index 5aac575..0000000
--- a/runtime/internal/rpc/protocols/ws/init.go
+++ /dev/null
@@ -1,18 +0,0 @@
-// 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 websocket
-
-import (
- "v.io/v23/rpc"
-
- "v.io/x/ref/runtime/internal/lib/websocket"
-)
-
-func init() {
- // ws, ws4, ws6 represent websocket protocol instances.
- rpc.RegisterProtocol("ws", websocket.Dial, websocket.Resolve, websocket.Listener, "ws4", "ws6")
- rpc.RegisterProtocol("ws4", websocket.Dial, websocket.Resolve, websocket.Listener)
- rpc.RegisterProtocol("ws6", websocket.Dial, websocket.Resolve, websocket.Listener)
-}
diff --git a/runtime/internal/rpc/protocols/wsh/init.go b/runtime/internal/rpc/protocols/wsh/init.go
deleted file mode 100644
index 26cc680..0000000
--- a/runtime/internal/rpc/protocols/wsh/init.go
+++ /dev/null
@@ -1,19 +0,0 @@
-// 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 wsh registers the websocket 'hybrid' protocol.
-// We prefer to use tcp whenever we can to avoid the overhead of websockets.
-package wsh
-
-import (
- "v.io/v23/rpc"
-
- "v.io/x/ref/runtime/internal/lib/websocket"
-)
-
-func init() {
- rpc.RegisterProtocol("wsh", websocket.HybridDial, websocket.HybridResolve, websocket.HybridListener, "tcp4", "tcp6", "ws4", "ws6")
- rpc.RegisterProtocol("wsh4", websocket.HybridDial, websocket.HybridResolve, websocket.HybridListener, "tcp4", "ws4")
- rpc.RegisterProtocol("wsh6", websocket.HybridDial, websocket.HybridResolve, websocket.HybridListener, "tcp6", "ws6")
-}
diff --git a/runtime/internal/rpc/protocols/wsh_nacl/init.go b/runtime/internal/rpc/protocols/wsh_nacl/init.go
deleted file mode 100644
index 276a567..0000000
--- a/runtime/internal/rpc/protocols/wsh_nacl/init.go
+++ /dev/null
@@ -1,21 +0,0 @@
-// 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 wsh_nacl registers the websocket 'hybrid' protocol for nacl
-// architectures.
-package wsh_nacl
-
-import (
- "v.io/v23/rpc"
-
- "v.io/x/ref/runtime/internal/lib/websocket"
-)
-
-func init() {
- // We limit wsh to ws since in general nacl does not allow direct access
- // to TCP/UDP networking.
- rpc.RegisterProtocol("wsh", websocket.Dial, websocket.Resolve, websocket.Listener, "ws4", "ws6")
- rpc.RegisterProtocol("wsh4", websocket.Dial, websocket.Resolve, websocket.Listener, "ws4")
- rpc.RegisterProtocol("wsh6", websocket.Dial, websocket.Resolve, websocket.Listener, "ws6")
-}
diff --git a/runtime/internal/rpc/test/cancel_test.go b/runtime/internal/rpc/test/cancel_test.go
index 69edbcb..9a97291 100644
--- a/runtime/internal/rpc/test/cancel_test.go
+++ b/runtime/internal/rpc/test/cancel_test.go
@@ -299,21 +299,6 @@
}
func registerDisProtocol(wrap string, conns chan disconnect) {
- dial, resolve, listen, protonames := rpc.RegisteredProtocol(wrap)
- rpc.RegisterProtocol("dis", func(ctx *context.T, p, a string, t time.Duration) (net.Conn, error) {
- conn, err := dial(ctx, protonames[0], a, t)
- if err == nil {
- dc := &disConn{Conn: conn}
- conns <- dc
- conn = dc
- }
- return conn, err
- }, func(ctx *context.T, protocol, address string) (string, string, error) {
- _, a, err := resolve(ctx, protonames[0], address)
- return "dis", a, err
- }, func(ctx *context.T, protocol, address string) (net.Listener, error) {
- return listen(ctx, protonames[0], address)
- })
// We only register this flow protocol to make the test work in xclients mode.
protocol, _ := flow.RegisteredProtocol("tcp")
flow.RegisterProtocol("dis", &flowdis{base: protocol})
diff --git a/runtime/internal/rpc/test/full_test.go b/runtime/internal/rpc/test/full_test.go
index 855c601..85d9c47 100644
--- a/runtime/internal/rpc/test/full_test.go
+++ b/runtime/internal/rpc/test/full_test.go
@@ -19,11 +19,13 @@
"v.io/v23"
"v.io/v23/context"
+ "v.io/v23/flow"
"v.io/v23/flow/message"
"v.io/v23/i18n"
"v.io/v23/naming"
"v.io/v23/options"
"v.io/v23/rpc"
+ "v.io/v23/rpc/version"
"v.io/v23/security"
"v.io/v23/security/access"
"v.io/v23/vdl"
@@ -36,6 +38,7 @@
"v.io/x/ref/runtime/internal/lib/tcputil"
inaming "v.io/x/ref/runtime/internal/naming"
"v.io/x/ref/runtime/internal/rpc/stream/crypto"
+ "v.io/x/ref/runtime/protocols/debug"
"v.io/x/ref/test"
"v.io/x/ref/test/testutil"
)
@@ -1262,3 +1265,48 @@
time.Sleep(50 * time.Millisecond)
}
}
+
+type manInMiddleConn struct {
+ flow.Conn
+ ctx *context.T
+}
+
+// manInMiddleConn changes the versions in any setup message sent over the wire.
+func (c *manInMiddleConn) ReadMsg() ([]byte, error) {
+ b, err := c.Conn.ReadMsg()
+ if len(b) > 0 {
+ m, _ := message.Read(c.ctx, b)
+ switch msg := m.(type) {
+ case *message.Setup:
+ // The malicious man in the middle changes the max version to a bad version.
+ msg.Versions = version.RPCVersionRange{Min: version.RPCVersion10, Max: 100}
+ b, err = message.Append(c.ctx, msg, nil)
+ }
+ }
+ return b, err
+}
+
+func TestSetupAttack(t *testing.T) {
+ ctx, shutdown := test.V23InitWithMounttable()
+ defer shutdown()
+
+ ctx = debug.WithFilter(ctx, func(c flow.Conn) flow.Conn {
+ return &manInMiddleConn{c, ctx}
+ })
+
+ ctx = v23.WithListenSpec(ctx, rpc.ListenSpec{
+ Addrs: rpc.ListenAddrs{{Protocol: "debug", Address: "tcp/127.0.0.1:0"}},
+ })
+ ctx, cancel := context.WithCancel(ctx)
+ _, server, err := v23.WithNewServer(ctx, "mountpoint/server", &testServer{}, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer func() { <-server.Closed() }()
+ defer cancel()
+ // Connection establishment should fail during the RPC because the channel binding
+ // check should fail since the Setup message has been altered.
+ if err := v23.GetClient(ctx).Call(ctx, "mountpoint/server", "Closure", nil, nil, options.NoRetry{}); err == nil {
+ t.Errorf("expected error but got <nil>")
+ }
+}
diff --git a/runtime/internal/rpc/version/version.go b/runtime/internal/rpc/version/version.go
index f08e1ef..25d02ef 100644
--- a/runtime/internal/rpc/version/version.go
+++ b/runtime/internal/rpc/version/version.go
@@ -8,16 +8,10 @@
"fmt"
"v.io/v23/rpc/version"
- "v.io/v23/verror"
"v.io/x/lib/metadata"
)
-// Range represents a range of RPC versions.
-type Range struct {
- Min, Max version.RPCVersion
-}
-
-// SupportedRange represents the range of protocol verions supported by this
+// Supported represents the range of protocol verions supported by this
// implementation.
//
// Max is incremented whenever we make a protocol change that's not both forward
@@ -25,77 +19,9 @@
//
// Min is incremented whenever we want to remove support for old protocol
// versions.
-var SupportedRange = &Range{Min: version.RPCVersion10, Max: version.RPCVersion12}
-var Supported = version.RPCVersionRange{Min: version.RPCVersion10, Max: version.RPCVersion13}
+var Supported = version.RPCVersionRange{Min: version.RPCVersion10, Max: version.RPCVersion14}
func init() {
- metadata.Insert("v23.RPCVersionMax", fmt.Sprint(SupportedRange.Max))
- metadata.Insert("v23.RPCVersionMin", fmt.Sprint(SupportedRange.Min))
-}
-
-const pkgPath = "v.io/x/ref/runtime/internal/rpc/version"
-
-func reg(id, msg string) verror.IDAction {
- return verror.Register(verror.ID(pkgPath+id), verror.NoRetry, msg)
-}
-
-var (
- // These errors are intended to be used as arguments to higher
- // level errors and hence {1}{2} is omitted from their format
- // strings to avoid repeating these n-times in the final error
- // message visible to the user.
- ErrNoCompatibleVersion = reg(".errNoCompatibleVersionErr", "no compatible RPC version available{:3} not in range {4}..{5}")
- ErrUnknownVersion = reg(".errUnknownVersionErr", "there was not enough information to determine a version")
- ErrDeprecatedVersion = reg(".errDeprecatedVersionError", "some of the provided version information is deprecated")
-)
-
-// IsVersionError returns true if err is a versioning related error.
-func IsVersionError(err error) bool {
- id := verror.ErrorID(err)
- return id == ErrNoCompatibleVersion.ID || id == ErrUnknownVersion.ID || id == ErrDeprecatedVersion.ID
-}
-
-// intersectRanges finds the intersection between ranges
-// supported by two endpoints. We make an assumption here that if one
-// of the endpoints has an UnknownVersion we assume it has the same
-// extent as the other endpoint. If both endpoints have Unknown for a
-// version number, an error is produced.
-// For example:
-// a == (2, 4) and b == (Unknown, Unknown), intersect(a,b) == (2, 4)
-// a == (2, Unknown) and b == (3, 4), intersect(a,b) == (3, 4)
-func intersectRanges(amin, amax, bmin, bmax version.RPCVersion) (min, max version.RPCVersion, err error) {
- // TODO(mattr): this may be incorrect. Ensure that when we talk to a server who
- // advertises (5,8) and we support (5, 9) but v5 EPs (so we may get d, d here) that
- // we use v8 and don't send setupVC.
- d := version.DeprecatedRPCVersion
- if amin == d || amax == d || bmin == d || bmax == d {
- return d, d, verror.New(ErrDeprecatedVersion, nil)
- }
-
- u := version.UnknownRPCVersion
-
- min = amin
- if min == u || (bmin != u && bmin > min) {
- min = bmin
- }
- max = amax
- if max == u || (bmax != u && bmax < max) {
- max = bmax
- }
-
- if min == u || max == u {
- err = verror.New(ErrUnknownVersion, nil)
- } else if min > max {
- err = verror.New(ErrNoCompatibleVersion, nil, u, min, max)
- }
- return
-}
-
-func (r1 *Range) Intersect(r2 *Range) (*Range, error) {
- min, max, err := intersectRanges(r1.Min, r1.Max, r2.Min, r2.Max)
- if err != nil {
- return nil, err
- }
- r := &Range{Min: min, Max: max}
- return r, nil
+ metadata.Insert("v23.RPCVersionMax", fmt.Sprint(Supported.Max))
+ metadata.Insert("v23.RPCVersionMin", fmt.Sprint(Supported.Min))
}
diff --git a/runtime/internal/rpc/version/version_test.go b/runtime/internal/rpc/version/version_test.go
deleted file mode 100644
index 652baf8..0000000
--- a/runtime/internal/rpc/version/version_test.go
+++ /dev/null
@@ -1,51 +0,0 @@
-// 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 version
-
-import (
- "testing"
-
- "v.io/v23/rpc/version"
- "v.io/v23/verror"
-)
-
-func TestIntersect(t *testing.T) {
- type testCase struct {
- localMin, localMax version.RPCVersion
- remoteMin, remoteMax version.RPCVersion
- expected *Range
- expectedErr verror.IDAction
- }
- tests := []testCase{
- {0, 0, 0, 0, nil, ErrUnknownVersion},
- {0, 2, 3, 4, nil, ErrNoCompatibleVersion},
- {3, 4, 0, 2, nil, ErrNoCompatibleVersion},
- {0, 6, 6, 7, nil, ErrNoCompatibleVersion},
- {0, 3, 3, 5, &Range{3, 3}, verror.ErrUnknown},
- {0, 3, 2, 4, &Range{2, 3}, verror.ErrUnknown},
- {2, 4, 2, 4, &Range{2, 4}, verror.ErrUnknown},
- {4, 4, 4, 4, &Range{4, 4}, verror.ErrUnknown},
- }
- for _, tc := range tests {
- local := &Range{
- Min: tc.localMin,
- Max: tc.localMax,
- }
- remote := &Range{
- Min: tc.remoteMin,
- Max: tc.remoteMax,
- }
- intersection, err := local.Intersect(remote)
-
- if (tc.expected != nil && *tc.expected != *intersection) ||
- (err != nil && verror.ErrorID(err) != tc.expectedErr.ID) {
- t.Errorf("Unexpected result for local: %v, remote: %v. Got (%v, %v) wanted (%v, %v)",
- local, remote, intersection, err,
- tc.expected, tc.expectedErr)
- }
- if err != nil {
- t.Logf("%s", err)
- }
- }
-}
diff --git a/runtime/internal/lib/framer/errors.vdl b/runtime/protocols/lib/framer/errors.vdl
similarity index 100%
rename from runtime/internal/lib/framer/errors.vdl
rename to runtime/protocols/lib/framer/errors.vdl
diff --git a/runtime/internal/lib/framer/errors.vdl.go b/runtime/protocols/lib/framer/errors.vdl.go
similarity index 79%
rename from runtime/internal/lib/framer/errors.vdl.go
rename to runtime/protocols/lib/framer/errors.vdl.go
index 2da6252..1c077bf 100644
--- a/runtime/internal/lib/framer/errors.vdl.go
+++ b/runtime/protocols/lib/framer/errors.vdl.go
@@ -15,7 +15,7 @@
)
var (
- ErrLargerThan3ByteUInt = verror.Register("v.io/x/ref/runtime/internal/lib/framer.LargerThan3ByteUInt", verror.NoRetry, "{1:}{2:} integer too large to represent in 3 bytes")
+ ErrLargerThan3ByteUInt = verror.Register("v.io/x/ref/runtime/protocols/lib/framer.LargerThan3ByteUInt", verror.NoRetry, "{1:}{2:} integer too large to represent in 3 bytes")
)
func init() {
diff --git a/runtime/internal/lib/framer/framer.go b/runtime/protocols/lib/framer/framer.go
similarity index 100%
rename from runtime/internal/lib/framer/framer.go
rename to runtime/protocols/lib/framer/framer.go
diff --git a/runtime/internal/lib/framer/framer_test.go b/runtime/protocols/lib/framer/framer_test.go
similarity index 100%
rename from runtime/internal/lib/framer/framer_test.go
rename to runtime/protocols/lib/framer/framer_test.go
diff --git a/services/cluster/vkube/vkube_v23_test.go b/services/cluster/vkube/vkube_v23_test.go
index 8a1a0ae..9dec1fb 100644
--- a/services/cluster/vkube/vkube_v23_test.go
+++ b/services/cluster/vkube/vkube_v23_test.go
@@ -5,19 +5,19 @@
package main_test
import (
+ "bytes"
"flag"
"fmt"
- "io"
"io/ioutil"
"math/rand"
"os"
"os/exec"
"path/filepath"
"strings"
+ "sync"
"testing"
"text/template"
- "v.io/x/lib/textutil"
"v.io/x/ref/lib/v23test"
"v.io/x/ref/test/testutil"
)
@@ -65,16 +65,16 @@
// Note, creds do not affect non-Vanadium commands.
c := sh.Cmd(name, args...).WithCredentials(creds)
c.ExitErrorIsOk = true
- // Wrap os.Stdout in a MultiWriter so that PrefixLineWriter.Close
- // doesn't close it.
- c.AddStdoutWriter(textutil.PrefixLineWriter(io.MultiWriter(os.Stdout), filepath.Base(name)+"> "))
- stdout := c.Stdout()
+ w := &writer{name: filepath.Base(name)}
+ c.AddStdoutWriter(w)
+ c.AddStderrWriter(w)
+ c.Run()
if expectSuccess && c.Err != nil {
t.Error(testutil.FormatLogLine(2, "Unexpected failure: %s %s :%v", name, strings.Join(args, " "), c.Err))
} else if !expectSuccess && c.Err == nil {
t.Error(testutil.FormatLogLine(2, "Unexpected success %d: %s %s", name, strings.Join(args, " ")))
}
- return stdout
+ return w.output()
}
}
gsutil = cmd("gsutil", true)
@@ -145,7 +145,13 @@
// Find the pod running tunneld, get the server's addr from its stdout.
podName := kubectlOK("get", "pod", "-l", "application=tunneld", "--template={{range .items}}{{.metadata.name}}{{end}}")
- addr := strings.TrimPrefix(strings.TrimSpace(kubectlOK("logs", podName, "-c", "tunneld")), "NAME=")
+ var addr string
+ for _, log := range strings.Split(kubectlOK("logs", podName, "-c", "tunneld"), "\n") {
+ if strings.HasPrefix(log, "NAME=") {
+ addr = strings.TrimPrefix(log, "NAME=")
+ break
+ }
+ }
if got, expected := vshOK(addr, "echo", "hello", "world"), "hello world\n"; got != expected {
t.Errorf("Unexpected output. Got %q, expected %q", got, expected)
}
@@ -255,6 +261,51 @@
}`)).Execute(f, params)
}
+// writer is an io.Writer that sends everything to stdout, each line prefixed
+// with "name> ".
+type writer struct {
+ sync.Mutex
+ name string
+ line bytes.Buffer
+ out bytes.Buffer
+}
+
+func (w *writer) Write(p []byte) (n int, err error) {
+ w.Lock()
+ defer w.Unlock()
+ n = len(p)
+ w.out.Write(p)
+ for len(p) > 0 {
+ if w.line.Len() == 0 {
+ fmt.Fprintf(&w.line, "%s> ", w.name)
+ }
+ if off := bytes.IndexByte(p, '\n'); off != -1 {
+ off += 1
+ w.line.Write(p[:off])
+ w.line.WriteTo(os.Stdout)
+ p = p[off:]
+ continue
+ }
+ w.line.Write(p)
+ break
+ }
+ return
+}
+
+func (w *writer) Close() error {
+ return nil
+}
+
+func (w *writer) output() string {
+ w.Lock()
+ defer w.Unlock()
+ if w.line.Len() != 0 {
+ w.line.WriteString(" [no \\n at EOL]\n")
+ w.line.WriteTo(os.Stdout)
+ }
+ return w.out.String()
+}
+
func TestMain(m *testing.M) {
os.Exit(v23test.Run(m.Run))
}
diff --git a/services/device/deviced/internal/impl/restart_policy_test.go b/services/device/deviced/internal/impl/restart_policy_test.go
index 046377e..0ccf7ad 100644
--- a/services/device/deviced/internal/impl/restart_policy_test.go
+++ b/services/device/deviced/internal/impl/restart_policy_test.go
@@ -23,6 +23,8 @@
decision bool
}
+ testNow := time.Now()
+
testVectors := []tV{
// -1 means always restart.
{
@@ -73,11 +75,11 @@
},
&instanceInfo{
Restarts: 1,
- RestartWindowBegan: time.Date(2015, time.December, 25, 12, 0, 0, 0, time.UTC),
+ RestartWindowBegan: testNow,
},
&instanceInfo{
Restarts: 1,
- RestartWindowBegan: time.Date(2015, time.December, 25, 12, 0, 0, 0, time.UTC),
+ RestartWindowBegan: testNow,
},
false,
},
diff --git a/services/device/mgmt_v23_test.go b/services/device/mgmt_v23_test.go
index ef6e4e4..9ffb6b7 100644
--- a/services/device/mgmt_v23_test.go
+++ b/services/device/mgmt_v23_test.go
@@ -50,6 +50,7 @@
"testing"
"time"
+ "v.io/x/lib/gosh"
"v.io/x/ref"
"v.io/x/ref/lib/v23test"
_ "v.io/x/ref/runtime/factories/generic"
@@ -281,7 +282,7 @@
// if the name doesn't exist.
c := withArgs(namespaceBin, "resolve", name)
c.ExitErrorIsOk = true
- c.AddStderrWriter(os.Stderr)
+ c.AddStderrWriter(gosh.NopWriteCloser(os.Stderr))
if res = tr(c.Stdout()); len(res) > 0 {
return nil
}
@@ -473,7 +474,7 @@
// if the name doesn't exist.
c := withArgs(namespaceBin, "resolve", name)
c.ExitErrorIsOk = true
- c.AddStderrWriter(os.Stderr)
+ c.AddStderrWriter(gosh.NopWriteCloser(os.Stderr))
switch res = tr(c.Stdout()); {
case res == "":
return testutil.TryAgain(errors.New("resolve returned nothing"))
@@ -535,7 +536,7 @@
// if the name doesn't exist.
c := withArgs(namespaceBin, "resolve", name)
c.ExitErrorIsOk = true
- c.AddStderrWriter(os.Stderr)
+ c.AddStderrWriter(gosh.NopWriteCloser(os.Stderr))
if res = tr(c.Stdout()); len(res) == 0 {
return nil
}
diff --git a/services/profile/profiled/impl_test.go b/services/profile/profiled/impl_test.go
index c7d00e3..6150dfb 100644
--- a/services/profile/profiled/impl_test.go
+++ b/services/profile/profiled/impl_test.go
@@ -30,8 +30,6 @@
}
)
-//go:generate jiri test generate
-
// TestInterface tests that the implementation correctly implements
// the Profile interface.
func TestInterface(t *testing.T) {
diff --git a/services/syncbase/syncbased/main.go b/services/syncbase/syncbased/main.go
index d78abfa..06d4cf6 100644
--- a/services/syncbase/syncbased/main.go
+++ b/services/syncbase/syncbased/main.go
@@ -67,8 +67,8 @@
vlog.Info("Mounted at: ", *name)
}
if eps := s.Status().Endpoints; len(eps) > 0 {
- // Our v23tests will wait for this to be printed before trying to access
- // the service.
+ // Integration tests wait for this to be printed before trying to access the
+ // service.
fmt.Printf("ENDPOINT=%s\n", eps[0].Name())
}
diff --git a/services/syncbase/testutil/util.go b/services/syncbase/testutil/util.go
index b7f3594..7866602 100644
--- a/services/syncbase/testutil/util.go
+++ b/services/syncbase/testutil/util.go
@@ -70,6 +70,7 @@
return
}
+// TODO(sadovsky): Switch unit tests to v23test.Shell, then delete this.
func SetupOrDieCustom(clientSuffix, serverSuffix string, perms access.Permissions) (ctx, clientCtx *context.T, serverName string, rootp security.Principal, cleanup func()) {
ctx, shutdown := test.V23Init()
rootp = tsecurity.NewPrincipal("root")
@@ -252,6 +253,7 @@
// Creates a new context object with blessing "root:<suffix>", configured to
// present this blessing when acting as a server as well as when acting as a
// client and talking to a server that presents a blessing rooted at "root".
+// TODO(sadovsky): Switch unit tests to v23test.Shell, then delete this.
func NewCtx(ctx *context.T, rootp security.Principal, suffix string) *context.T {
// Principal for the new context.
p := tsecurity.NewPrincipal(suffix)
diff --git a/services/syncbase/testutil/v23util.go b/services/syncbase/testutil/v23util.go
deleted file mode 100644
index bd35c65..0000000
--- a/services/syncbase/testutil/v23util.go
+++ /dev/null
@@ -1,107 +0,0 @@
-// 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 testutil
-
-import (
- "bytes"
- "io/ioutil"
- "log"
- "os"
- "runtime/debug"
- "syscall"
- "time"
-
- "v.io/x/ref/test/modules"
- "v.io/x/ref/test/v23tests"
-)
-
-// StartSyncbased starts a syncbased process, intended to be accessed from an
-// integration test (run using --v23.tests). The returned cleanup function
-// should be called once the syncbased process is no longer needed. See
-// StartKillableSyncbased for killing the syncbase with an arbitrary signal.
-func StartSyncbased(t *v23tests.T, creds *modules.CustomCredentials, name, rootDir, permsLiteral string) (cleanup func()) {
- f := StartKillableSyncbased(t, creds, name, rootDir, permsLiteral)
- return func() {
- f(syscall.SIGINT)
- }
-}
-
-// StartKillableSyncbased starts a syncbased process, intended to be accessed
-// from an integration test (run using --v23.tests). The returned cleanup
-// function should be called once the syncbased process is no longer needed.
-func StartKillableSyncbased(t *v23tests.T, creds *modules.CustomCredentials,
- name, rootDir, permsLiteral string) (cleanup func(signal syscall.Signal)) {
-
- syncbased := t.BuildV23Pkg("v.io/x/ref/services/syncbase/syncbased")
- // Create root dir for the store.
- rmRootDir := false
- if rootDir == "" {
- var err error
- rootDir, err = ioutil.TempDir("", "syncbase_leveldb")
- if err != nil {
- V23Fatalf(t, "can't create temp dir: %v", err)
- }
- rmRootDir = true
- }
-
- // Start syncbased. Run with --dev to enable development mode methods such as
- // DevModeUpdateVClock.
- invocation := syncbased.WithStartOpts(syncbased.StartOpts().WithCustomCredentials(creds).WithSessions(t, 5*time.Second)).Start(
- //"--v=5",
- //"--vpath=vsync*=5",
- //"--alsologtostderr=true",
- "--v23.tcp.address=127.0.0.1:0",
- "--v23.permissions.literal", permsLiteral,
- "--name="+name,
- "--root-dir="+rootDir,
- "--dev")
-
- cleanup = func(signal syscall.Signal) {
- go invocation.Kill(signal)
- stdout, stderr := bytes.NewBuffer(nil), bytes.NewBuffer(nil)
- if err := invocation.Shutdown(stdout, stderr); err != nil {
- log.Printf("syncbased terminated with an error: %v\nstdout: %v\nstderr: %v\n", err, stdout, stderr)
- } else {
- // To debug sync (for example), uncomment this line as well as the logging
- // flags in the invocation above.
- //log.Printf("syncbased terminated cleanly\nstdout: %v\nstderr: %v\n", stdout, stderr)
- }
- if rmRootDir {
- if err := os.RemoveAll(rootDir); err != nil {
- V23Fatalf(t, "failed to remove dir %v: %v", rootDir, err)
- }
- }
- }
-
- // Wait for syncbased to start. If syncbased fails to start, this will time
- // out after 5 seconds and return "".
- endpoint := invocation.ExpectVar("ENDPOINT")
- if endpoint == "" {
- cleanup(syscall.SIGKILL)
- t.Fatalf("syncbased failed to start")
- }
- return cleanup
-}
-
-// RunClient runs the given program and waits for it to terminate.
-// TODO(sadovsky): This function will soon go away. Do not depend on it.
-func RunClient(t *v23tests.T, creds *modules.CustomCredentials, program modules.Program, args ...string) {
- client, err := t.Shell().StartWithOpts(
- t.Shell().DefaultStartOpts().WithCustomCredentials(creds),
- nil,
- program, args...)
- if err != nil {
- V23Fatalf(t, "unable to start the client: %v", err)
- }
- stdout, stderr := bytes.NewBuffer(nil), bytes.NewBuffer(nil)
- if err := client.Shutdown(stdout, stderr); err != nil {
- V23Fatalf(t, "client failed: %v\nstdout: %v\nstderr: %v\n", err, stdout, stderr)
- }
-}
-
-func V23Fatalf(t *v23tests.T, format string, args ...interface{}) {
- debug.PrintStack()
- t.Fatalf(format, args...)
-}
diff --git a/services/syncbase/vsync/watcher.go b/services/syncbase/vsync/watcher.go
index cf15573..f1aeb3e 100644
--- a/services/syncbase/vsync/watcher.go
+++ b/services/syncbase/vsync/watcher.go
@@ -121,7 +121,12 @@
// Get a batch of watch log entries, if any, after this resume marker.
logs, nextResmark, err := watchable.ReadBatchFromLog(st, resMark)
if err != nil {
- vlog.Fatalf("sync: processDatabase: %s, %s: cannot get watch log batch: %v", appName, dbName, verror.DebugString(err))
+ // An error here (scan stream cancelled) is possible when the
+ // watcher is in the middle of processing a database and the
+ // app/db is detroyed. Hence, we just ignore this database and
+ // proceed.
+ vlog.Errorf("sync: processDatabase: %s, %s: cannot get watch log batch: %v", appName, dbName, verror.DebugString(err))
+ return false
}
if logs != nil {
s.processWatchLogBatch(ctx, appName, dbName, st, logs, nextResmark)
diff --git a/services/wspr/browsprd/main_nacl.go b/services/wspr/browsprd/main_nacl.go
index 81c7865..b4b705b 100644
--- a/services/wspr/browsprd/main_nacl.go
+++ b/services/wspr/browsprd/main_nacl.go
@@ -22,7 +22,6 @@
"v.io/x/ref/internal/logger"
vsecurity "v.io/x/ref/lib/security"
_ "v.io/x/ref/runtime/factories/chrome"
- "v.io/x/ref/runtime/internal/lib/websocket"
"v.io/x/ref/runtime/internal/lib/xwebsocket"
"v.io/x/ref/services/wspr/internal/app"
"v.io/x/ref/services/wspr/internal/browspr"
@@ -63,7 +62,6 @@
browsprInst.initFileSystem()
// Give the websocket interface the ppapi instance.
- websocket.PpapiInstance = inst
xwebsocket.PpapiInstance = inst
// Set up the channel and register start rpc handler.
diff --git a/test/doc.go b/test/doc.go
index 35bee24..f9a5d7f 100644
--- a/test/doc.go
+++ b/test/doc.go
@@ -14,31 +14,18 @@
// ...
// }
//
-// This package also defines flags for enabling and controlling
-// the v23 integration tests in package v23tests:
-// --v23.tests - run the integration tests
-// --v23.tests.shell-on-fail - drop into a debug shell if the test fails.
+// This package also defines flags for enabling and controlling the Vanadium
+// integration tests in package v.io/x/ref/lib/v23test:
+// --v23.tests - run the integration tests
+// --v23.tests.shell-on-fail - drop into a debug shell if the test fails
//
// Typical usage is:
// $ jiri go test . --v23.tests
//
-// Note that, like all flags not recognised by the go testing package, the
-// v23.tests flags must follow the package spec.
+// Note that, like all flags not recognized by the go testing package, the
+// --v23.tests flags must follow the package spec.
//
-// The sub-directories of this package provide either functionality that
-// can be used within traditional go tests, or support for the v23 integration
-// test framework. The jiri command is able to generate boilerplate code
-// to support these tests. In summary, 'jiri test generate' will generate
-// go files to be checked in that include appropriate TestMain functions,
-// registration calls for modules commands and wrapper functions for v23test
-// tests. More detailed documentation is available via:
-//
-// $ jiri test generate --help
-//
-// Vanadium tests often need to run subprocesses to provide either common
-// services that they depend (e.g. a mount table) and/or services that are
-// specific to the tests. The modules and v23tests subdirectories contain
-// packages that simplify this process.
+// Subdirectories provide utilities for unit and integration tests.
//
// The subdirectories are:
// benchmark - support for writing benchmarks.
@@ -46,10 +33,9 @@
// security - security related utility functions used in tests.
// timekeeper - an implementation of the timekeeper interface for use within
// tests.
-// modules - support for running subprocesses using a single binary
-// v23tests - support for integration tests, including compiling and running
-// arbirtrary go code and pre-existing system commands.
-// expect - support for testing the contents of input streams. The methods
-// provided are embedded in the types used by modules and v23tests
-// so this package is generally not used directly.
+// modules - deprecated, use v.io/x/ref/lib/v23test or v.io/x/lib/gosh to run
+// and manage subprocesses.
+// expect - support for testing the contents of of an input stream (an
+// io.Reader). v23test.Cmd contains an expect.Session, so this
+// package is generally not used directly.
package test
diff --git a/test/hello/hello_v23_test.go b/test/hello/hello_v23_test.go
index 0409586..dd65494 100644
--- a/test/hello/hello_v23_test.go
+++ b/test/hello/hello_v23_test.go
@@ -5,43 +5,34 @@
package hello_test
import (
- "fmt"
"os"
- "time"
+ "testing"
"v.io/x/ref"
"v.io/x/ref/lib/security"
+ "v.io/x/ref/lib/v23test"
_ "v.io/x/ref/runtime/factories/generic"
- "v.io/x/ref/test/modules"
"v.io/x/ref/test/testutil"
- "v.io/x/ref/test/v23tests"
)
-//go:generate jiri test generate
-
func init() {
ref.EnvClearCredentials()
}
-var opts = modules.StartOpts{
- StartTimeout: 20 * time.Second,
- ShutdownTimeout: 20 * time.Second,
- ExpectTimeout: 20 * time.Second,
- ExecProtocol: false,
- External: true,
+func withCreds(dir string, c *v23test.Cmd) *v23test.Cmd {
+ c.Vars[ref.EnvCredentials] = dir
+ return c
}
-// setupCredentials makes a bunch of credentials directories.
-// Note that I do this myself instead of allowing the test framework
-// to do it because I really want to use the agentd binary, not
-// the agent that is locally hosted inside v23Tests.T.
-// This is important for regression tests where we want to test against
-// old agent binaries.
-func setupCredentials(i *v23tests.T, names ...string) (map[string]string, error) {
+// setupCreds makes a bunch of credentials directories.
+// We do this ourselves instead of using v23test's credentials APIs because we
+// want to use the actual agentd binary, so that for regression tests we can
+// test against old agents.
+func setupCreds(sh *v23test.Shell, names ...string) (map[string]string, error) {
idp := testutil.NewIDProvider("root")
out := make(map[string]string, len(names))
for _, name := range names {
- dir := i.NewTempDir("")
+ dir := sh.MakeTempDir()
p, err := security.CreatePersistentPrincipal(dir, nil)
if err != nil {
return nil, err
@@ -49,94 +40,107 @@
if err := idp.Bless(p, name); err != nil {
return nil, err
}
- out[name] = fmt.Sprintf("%s=%s", ref.EnvCredentials, dir)
+ out[name] = dir
}
return out, nil
}
-func V23TestHelloDirect(i *v23tests.T) {
- creds, err := setupCredentials(i, "helloclient", "helloserver")
+func TestV23HelloDirect(t *testing.T) {
+ v23test.SkipUnlessRunningIntegrationTests(t)
+ sh := v23test.NewShell(t, v23test.Opts{})
+ defer sh.Cleanup()
+
+ creds, err := setupCreds(sh, "helloclient", "helloserver")
if err != nil {
- i.Fatalf("Could not create credentials: %v", err)
+ t.Fatalf("Could not create credentials: %v", err)
}
- clientbin := i.BuildGoPkg("v.io/x/ref/test/hello/helloclient")
- serverbin := i.BuildGoPkg("v.io/x/ref/test/hello/helloserver")
- server := serverbin.WithStartOpts(opts).WithEnv(creds["helloserver"]).Start()
- name := server.ExpectVar("SERVER_NAME")
- if server.Failed() {
- server.Wait(os.Stdout, os.Stderr)
- i.Fatalf("Could not get SERVER_NAME: %v", server.Error())
+ clientbin := sh.BuildGoPkg("v.io/x/ref/test/hello/helloclient")
+ serverbin := sh.BuildGoPkg("v.io/x/ref/test/hello/helloserver")
+
+ server := withCreds(creds["helloserver"], sh.Cmd(serverbin))
+ server.Start()
+ name := server.S.ExpectVar("SERVER_NAME")
+ if server.S.Failed() {
+ t.Fatalf("Could not get SERVER_NAME: %v", server.S.Error())
}
- clientbin.WithEnv(creds["helloclient"]).WithStartOpts(opts).Run("--name", name)
+ withCreds(creds["helloclient"], sh.Cmd(clientbin, "--name", name)).Run()
}
-func V23TestHelloAgentd(i *v23tests.T) {
- creds, err := setupCredentials(i, "helloclient", "helloserver")
+func TestV23HelloAgentd(t *testing.T) {
+ v23test.SkipUnlessRunningIntegrationTests(t)
+ sh := v23test.NewShell(t, v23test.Opts{})
+ defer sh.Cleanup()
+
+ creds, err := setupCreds(sh, "helloclient", "helloserver")
if err != nil {
- i.Fatalf("Could not create credentials: %v", err)
+ t.Fatalf("Could not create credentials: %v", err)
}
- agentdbin := i.BuildGoPkg("v.io/x/ref/services/agent/agentd").WithStartOpts(opts)
- serverbin := i.BuildGoPkg("v.io/x/ref/test/hello/helloserver")
- clientbin := i.BuildGoPkg("v.io/x/ref/test/hello/helloclient")
- server := agentdbin.WithEnv(creds["helloserver"]).Start(serverbin.Path())
- name := server.ExpectVar("SERVER_NAME")
- if server.Failed() {
- server.Wait(os.Stdout, os.Stderr)
- i.Fatalf("Could not get SERVER_NAME: %v", server.Error())
+ agentdbin := sh.BuildGoPkg("v.io/x/ref/services/agent/agentd")
+ serverbin := sh.BuildGoPkg("v.io/x/ref/test/hello/helloserver")
+ clientbin := sh.BuildGoPkg("v.io/x/ref/test/hello/helloclient")
+
+ server := withCreds(creds["helloserver"], sh.Cmd(serverbin))
+ server.Start()
+ name := server.S.ExpectVar("SERVER_NAME")
+ if server.S.Failed() {
+ t.Fatalf("Could not get SERVER_NAME: %v", server.S.Error())
}
- agentdbin.WithEnv(creds["helloclient"]).Run(clientbin.Path(), "--name", name)
+ withCreds(creds["helloclient"], sh.Cmd(agentdbin, clientbin, "--name", name)).Run()
}
-func V23TestHelloMounttabled(i *v23tests.T) {
- creds, err := setupCredentials(i, "helloclient", "helloserver", "mounttabled")
+func TestV23HelloMounttabled(t *testing.T) {
+ v23test.SkipUnlessRunningIntegrationTests(t)
+ sh := v23test.NewShell(t, v23test.Opts{})
+ defer sh.Cleanup()
+
+ creds, err := setupCreds(sh, "helloclient", "helloserver", "mounttabled")
if err != nil {
- i.Fatalf("Could not create credentials: %v", err)
+ t.Fatalf("Could not create credentials: %v", err)
}
- agentdbin := i.BuildGoPkg("v.io/x/ref/services/agent/agentd").WithStartOpts(opts)
- mounttabledbin := i.BuildGoPkg("v.io/x/ref/services/mounttable/mounttabled")
- serverbin := i.BuildGoPkg("v.io/x/ref/test/hello/helloserver")
- clientbin := i.BuildGoPkg("v.io/x/ref/test/hello/helloclient")
+ agentdbin := sh.BuildGoPkg("v.io/x/ref/services/agent/agentd")
+ mounttabledbin := sh.BuildGoPkg("v.io/x/ref/services/mounttable/mounttabled")
+ serverbin := sh.BuildGoPkg("v.io/x/ref/test/hello/helloserver")
+ clientbin := sh.BuildGoPkg("v.io/x/ref/test/hello/helloclient")
+
name := "hello"
- mounttabled := agentdbin.WithEnv(creds["mounttabled"]).Start(mounttabledbin.Path(),
- "--v23.tcp.address", "127.0.0.1:0")
- mtname := mounttabled.ExpectVar("NAME")
- if mounttabled.Failed() {
- mounttabled.Wait(os.Stdout, os.Stderr)
- i.Fatalf("Could not get NAME: %v", mounttabled.Error())
+ mounttabled := withCreds(creds["mounttabled"], sh.Cmd(agentdbin, mounttabledbin, "--v23.tcp.address", "127.0.0.1:0"))
+ mounttabled.Start()
+ mtname := mounttabled.S.ExpectVar("NAME")
+ if mounttabled.S.Failed() {
+ t.Fatalf("Could not get NAME: %v", mounttabled.S.Error())
}
- agentdbin.WithEnv(creds["helloserver"]).Start(serverbin.Path(), "--name", name,
- "--v23.namespace.root", mtname)
- agentdbin.WithEnv(creds["helloclient"]).Run(clientbin.Path(), "--name", name,
- "--v23.namespace.root", mtname)
+ withCreds(creds["helloserver"], sh.Cmd(agentdbin, serverbin, "--name", name, "--v23.namespace.root", mtname)).Start()
+ withCreds(creds["helloclient"], sh.Cmd(agentdbin, clientbin, "--name", name, "--v23.namespace.root", mtname)).Run()
}
-func V23TestHelloProxy(i *v23tests.T) {
- creds, err := setupCredentials(i, "helloclient", "helloserver",
- "mounttabled", "proxyd", "xproxyd")
+func TestV23HelloProxy(t *testing.T) {
+ v23test.SkipUnlessRunningIntegrationTests(t)
+ sh := v23test.NewShell(t, v23test.Opts{})
+ defer sh.Cleanup()
+
+ creds, err := setupCreds(sh, "helloclient", "helloserver", "mounttabled", "proxyd", "xproxyd")
if err != nil {
- i.Fatalf("Could not create credentials: %v", err)
+ t.Fatalf("Could not create credentials: %v", err)
}
- agentdbin := i.BuildGoPkg("v.io/x/ref/services/agent/agentd").WithStartOpts(opts)
- mounttabledbin := i.BuildGoPkg("v.io/x/ref/services/mounttable/mounttabled")
- xproxydbin := i.BuildGoPkg("v.io/x/ref/services/xproxy/xproxyd")
- serverbin := i.BuildGoPkg("v.io/x/ref/test/hello/helloserver")
- clientbin := i.BuildGoPkg("v.io/x/ref/test/hello/helloclient")
+ agentdbin := sh.BuildGoPkg("v.io/x/ref/services/agent/agentd")
+ mounttabledbin := sh.BuildGoPkg("v.io/x/ref/services/mounttable/mounttabled")
+ xproxydbin := sh.BuildGoPkg("v.io/x/ref/services/xproxy/xproxyd")
+ serverbin := sh.BuildGoPkg("v.io/x/ref/test/hello/helloserver")
+ clientbin := sh.BuildGoPkg("v.io/x/ref/test/hello/helloclient")
+
+ name := "hello"
+ mounttabled := withCreds(creds["mounttabled"], sh.Cmd(agentdbin, mounttabledbin, "--v23.tcp.address", "127.0.0.1:0"))
+ mounttabled.Start()
+ mtname := mounttabled.S.ExpectVar("NAME")
+ if mounttabled.S.Failed() {
+ t.Fatalf("Could not get NAME: %v", mounttabled.S.Error())
+ }
proxyname := "proxy"
- name := "hello"
- mounttabled := agentdbin.WithEnv(creds["mounttabled"]).Start(mounttabledbin.Path(),
- "--v23.tcp.address", "127.0.0.1:0")
- mtname := mounttabled.ExpectVar("NAME")
- if mounttabled.Failed() {
- mounttabled.Wait(os.Stdout, os.Stderr)
- i.Fatalf("Could not get NAME: %v", mounttabled.Error())
- }
- agentdbin.WithEnv(creds["xproxyd"]).Start(xproxydbin.Path(),
- "--name", proxyname, "--v23.tcp.address", "127.0.0.1:0",
- "--v23.namespace.root", mtname,
- "--access-list", "{\"In\":[\"root\"]}")
- agentdbin.WithEnv(creds["helloserver"]).Start(serverbin.Path(),
- "--name", name, "--v23.proxy", proxyname, "--v23.tcp.address", "",
- "--v23.namespace.root", mtname)
- agentdbin.WithEnv(creds["helloclient"]).Run(clientbin.Path(), "--name", name,
- "--v23.namespace.root", mtname)
+ withCreds(creds["xproxyd"], sh.Cmd(agentdbin, xproxydbin, "--name", proxyname, "--v23.tcp.address", "127.0.0.1:0", "--v23.namespace.root", mtname, "--access-list", "{\"In\":[\"root\"]}")).Start()
+ withCreds(creds["helloserver"], sh.Cmd(agentdbin, serverbin, "--name", name, "--v23.proxy", proxyname, "--v23.tcp.address", "", "--v23.namespace.root", mtname)).Start()
+ withCreds(creds["helloclient"], sh.Cmd(agentdbin, clientbin, "--name", name, "--v23.proxy", proxyname, "--v23.tcp.address", "", "--v23.namespace.root", mtname)).Run()
+}
+
+func TestMain(m *testing.M) {
+ os.Exit(v23test.Run(m.Run))
}
diff --git a/test/hello/v23_test.go b/test/hello/v23_test.go
deleted file mode 100644
index 4429031..0000000
--- a/test/hello/v23_test.go
+++ /dev/null
@@ -1,40 +0,0 @@
-// 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.
-
-// This file was auto-generated via go generate.
-// DO NOT UPDATE MANUALLY
-
-package hello_test
-
-import (
- "os"
- "testing"
-
- "v.io/x/ref/test/modules"
- "v.io/x/ref/test/v23tests"
-)
-
-func TestMain(m *testing.M) {
- modules.DispatchAndExitIfChild()
- cleanup := v23tests.UseSharedBinDir()
- r := m.Run()
- cleanup()
- os.Exit(r)
-}
-
-func TestV23HelloDirect(t *testing.T) {
- v23tests.RunTest(t, V23TestHelloDirect)
-}
-
-func TestV23HelloAgentd(t *testing.T) {
- v23tests.RunTest(t, V23TestHelloAgentd)
-}
-
-func TestV23HelloMounttabled(t *testing.T) {
- v23tests.RunTest(t, V23TestHelloMounttabled)
-}
-
-func TestV23HelloProxy(t *testing.T) {
- v23tests.RunTest(t, V23TestHelloProxy)
-}
diff --git a/test/modules/modules_test.go b/test/modules/modules_test.go
index b6243ac..4344359 100644
--- a/test/modules/modules_test.go
+++ b/test/modules/modules_test.go
@@ -32,8 +32,6 @@
_ "v.io/x/ref/runtime/factories/generic"
)
-// We must call TestMain ourselves because using jiri test generate
-// creates an import cycle for this package.
func TestMain(m *testing.M) {
modules.DispatchAndExitIfChild()
os.Exit(m.Run())
diff --git a/test/modules/shell.go b/test/modules/shell.go
index 77d259b..c34d3b1 100644
--- a/test/modules/shell.go
+++ b/test/modules/shell.go
@@ -41,13 +41,6 @@
// Dispatch: must be called in the child process to lookup and invoke the
// requested function. Typically called from TestMain.
//
-// The jiri tool can automate generation of TestMain. Adding the comment below
-// to a test file will generate the appropriate code.
-//
-// //go:generate jiri test generate .
-//
-// Use 'jiri test generate --help' to get a complete explanation.
-//
// In all cases programs are started by invoking the StartWithOpts method on the
// Shell with the name of the program to run. An instance of the Handle
// interface is returned which can be used to interact with the function or
diff --git a/test/v23tests/binary.go b/test/v23tests/binary.go
deleted file mode 100644
index 915a6ac..0000000
--- a/test/v23tests/binary.go
+++ /dev/null
@@ -1,151 +0,0 @@
-// Copyright 2015 The Vanadium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package v23tests
-
-import (
- "bytes"
- "io"
- "os"
- "strings"
-
- "v.io/x/ref/test/modules"
-)
-
-// Binary represents an executable program that will be executed during a
-// test. A binary may be invoked multiple times by calling Start, each call
-// will return a new Invocation.
-//
-// Binary instances are typically obtained from a T by calling BuldV23Pkg,
-// BuildGoPkg (for Vanadium and other Go binaries) or BinaryFromPath (to
-// start binaries that are already present on the system).
-type Binary struct {
- // The environment to which this binary belongs.
- env *T
-
- // The path to the binary.
- path string
-
- // StartOpts
- opts modules.StartOpts
-
- // Environment variables that will be used when creating invocations
- // via Start.
- envVars []string
-
- // Optional prefix arguments are added to each invocation.
- prefixArgs []string
-}
-
-// StartOpts returns the current the StartOpts
-func (b *Binary) StartOpts() modules.StartOpts {
- return b.opts
-}
-
-// Path returns the path to the binary.
-func (b *Binary) Path() string {
- return b.path
-}
-
-// Start starts the given binary with the given arguments.
-func (b *Binary) Start(args ...string) *Invocation {
- return b.start(1, args...)
-}
-
-func (b *Binary) start(skip int, oargs ...string) *Invocation {
- args := make([]string, len(b.prefixArgs), len(oargs)+len(b.prefixArgs))
- copy(args, b.prefixArgs)
- args = append(args, oargs...)
- b.env.ctx.Infof("%s: starting %s %s", Caller(skip+1), b.Path(), strings.Join(args, " "))
- opts := b.opts
- if opts.ExecProtocol && opts.Credentials == nil {
- opts.Credentials, opts.Error = b.env.shell.NewChildCredentials("child")
- }
- opts.ExpectTesting = b.env.TB
- handle, err := b.env.shell.StartWithOpts(opts, b.envVars, modules.Program(b.Path()), args...)
- if err != nil {
- if handle != nil {
- b.env.ctx.Infof("%s: start failed", Caller(skip+1))
- handle.Shutdown(nil, os.Stderr)
- }
- // TODO(cnicolaou): calling Fatalf etc from a goroutine often leads
- // to deadlock. Need to make sure that we handle this here. Maybe
- // it's best to just return an error? Or provide a StartWithError
- // call for use from goroutines.
- b.env.Fatalf("%s: StartWithOpts(%v, %v) failed: %v", Caller(skip+1), b.Path(), strings.Join(args, ", "), err)
- }
- b.env.ctx.Infof("started PID %d\n", handle.Pid())
- inv := &Invocation{
- env: b.env,
- path: b.path,
- args: args,
- shutdownErr: errNotShutdown,
- privateHandle: handle,
- }
- b.env.appendInvocation(inv)
- return inv
-}
-
-func (b *Binary) run(args ...string) string {
- inv := b.start(2, args...)
- var stdout, stderr bytes.Buffer
- err := inv.Wait(&stdout, &stderr)
- if err != nil {
- a := strings.Join(args, ", ")
- b.env.Fatalf("%s: Run(%s): failed: %v: \n%s\n", Caller(2), a, err, stderr.String())
- }
- return strings.TrimRight(stdout.String(), "\n")
-}
-
-// Run runs the binary with the specified arguments to completion. On
-// success it returns the contents of stdout, on failure it terminates the
-// test with an error message containing the error and the contents of
-// stderr.
-func (b *Binary) Run(args ...string) string {
- return b.run(args...)
-}
-
-// WithStdin returns a copy of this binary that, when Start is called,
-// will read its input from the given reader. Once the reader returns
-// EOF, the returned invocation's standard input will be closed (see
-// Invocation.CloseStdin).
-func (b *Binary) WithStdin(r io.Reader) *Binary {
- opts := b.opts
- opts.Stdin = r
- return b.WithStartOpts(opts)
-}
-
-// WithEnv returns a copy of this binary that, when Start is called, will use
-// the given environment variables. Each environment variable should be
-// in "key=value" form. For example:
-//
-// bin.WithEnv("EXAMPLE_ENV=/tmp/something").Start(...)
-func (b *Binary) WithEnv(env ...string) *Binary {
- newBin := *b
- newBin.envVars = env
- return &newBin
-}
-
-// WithStartOpts eturns a copy of this binary that, when Start is called, will
-// use the given StartOpts.
-//
-// bin.WithStartOpts(opts).Start(...)
-// or
-// bin.WithStartOpts().Run(...)
-func (b *Binary) WithStartOpts(opts modules.StartOpts) *Binary {
- newBin := *b
- newBin.opts = opts
- return &newBin
-}
-
-// WithPrefixArgs returns a copy of this binary that, when Start or Run
-// is called, will use the given additional arguments. For example: given
-// a Binary b built from "git", then b2 := WithPrefixArgs("checkout")
-// will let one run git checkout a; git checkout b with b2.Run("a"),
-// b2.Run("b").
-func (b *Binary) WithPrefixArgs(prefixArgs ...string) *Binary {
- newBin := *b
- newBin.prefixArgs = prefixArgs
- return &newBin
-}
diff --git a/test/v23tests/doc.go b/test/v23tests/doc.go
deleted file mode 100644
index 3ca7429..0000000
--- a/test/v23tests/doc.go
+++ /dev/null
@@ -1,75 +0,0 @@
-// Copyright 2015 The Vanadium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-// Package v23tests implements support for writing writing end-to-end
-// 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 generated test functions names always start with TestV23. Thus:
-//
-// v23 go test -v <pkgs> --v23.tests // runs both unit and integration tests
-// v23 go test -v -run=TestV23 <pkgs> --v23.tests // runs just integration tests
-//
-// The go generate mechanism is used to generate the test code, thus the
-// comment:
-//
-// //go:generate jiri test generate
-//
-// will generate the files v23_test.go and internal_v23_test.go for the
-// package in which it occurs. Run v23 test 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 *v23tests.T)
-//
-// will be invoked as integration test if the --v23.tests flag is used.
-//
-// The generated code makes use of the RunTest function.
-//
-// The test environment is implemented by an instance of T.
-// It is constructed with an instance of another interface TB, a mirror
-// of testing.TB. Thus, the integration test environment can be used
-// 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 in the test.
-//
-// 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
diff --git a/test/v23tests/internal/cached_test.go b/test/v23tests/internal/cached_test.go
deleted file mode 100644
index 99951d7..0000000
--- a/test/v23tests/internal/cached_test.go
+++ /dev/null
@@ -1,97 +0,0 @@
-// 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 internal_test
-
-import (
- "fmt"
- "io/ioutil"
- "os"
- "path/filepath"
- "runtime"
- "testing"
- "time"
-
- _ "v.io/x/ref/runtime/factories/generic"
- "v.io/x/ref/test/v23tests"
-)
-
-//go:generate jiri test generate
-
-func init() {
- dir, err := ioutil.TempDir("./", "v23test-internal")
- if err != nil {
- panic(err.Error())
- }
- os.Setenv("V23_BIN_DIR", dir)
- tmpDir = dir
-}
-
-var tmpDir string
-var modTimes []time.Time
-
-// build build's a binary and appends it's modtime to the
-// global slice modTimes
-func build(i *v23tests.T) {
- nsBin := i.BuildGoPkg("v.io/x/ref/cmd/namespace")
- fi, err := os.Stat(nsBin.Path())
- if err != nil {
- i.Fatal(err)
- }
- modTimes = append(modTimes, fi.ModTime())
-}
-
-func fmtTimes() string {
- r := ""
- for _, t := range modTimes {
- r += t.String() + "\n"
- }
- return r
-}
-
-// checkTimes returns true if modTimes has at least one value
-// and the values are all the same.
-func checkTimes(i *v23tests.T) bool {
- _, file, line, _ := runtime.Caller(1)
- loc := fmt.Sprintf("%s:%d", filepath.Dir(file), line)
- if len(modTimes) == 0 {
- i.Fatalf("%s: nothing has been built", loc)
- }
- first := modTimes[0]
- for _, t := range modTimes[1:] {
- if t != first {
- i.Fatalf("%s: binary not cached: build times: %s", loc, fmtTimes())
- }
- }
- i.Logf("%d entries", len(modTimes))
- return true
-}
-
-func V23TestOne(i *v23tests.T) {
- build(i)
- checkTimes(i)
-}
-
-func V23TestTwo(i *v23tests.T) {
- build(i)
- checkTimes(i)
-}
-
-func V23TestThree(i *v23tests.T) {
- build(i)
- checkTimes(i)
-}
-
-func V23TestFour(i *v23tests.T) {
- build(i)
- checkTimes(i)
-}
-
-func TestMain(m *testing.M) {
- r := m.Run()
- if len(tmpDir) > 0 {
- os.RemoveAll(tmpDir)
- }
- os.Exit(r)
-}
diff --git a/test/v23tests/internal/dummy.go b/test/v23tests/internal/dummy.go
deleted file mode 100644
index 9272cb6..0000000
--- a/test/v23tests/internal/dummy.go
+++ /dev/null
@@ -1,5 +0,0 @@
-// 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 internal
diff --git a/test/v23tests/internal/v23_test.go b/test/v23tests/internal/v23_test.go
deleted file mode 100644
index aad6a7a..0000000
--- a/test/v23tests/internal/v23_test.go
+++ /dev/null
@@ -1,30 +0,0 @@
-// 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.
-
-// This file was auto-generated via go generate.
-// DO NOT UPDATE MANUALLY
-
-package internal_test
-
-import (
- "testing"
-
- "v.io/x/ref/test/v23tests"
-)
-
-func TestV23One(t *testing.T) {
- v23tests.RunTest(t, V23TestOne)
-}
-
-func TestV23Two(t *testing.T) {
- v23tests.RunTest(t, V23TestTwo)
-}
-
-func TestV23Three(t *testing.T) {
- v23tests.RunTest(t, V23TestThree)
-}
-
-func TestV23Four(t *testing.T) {
- v23tests.RunTest(t, V23TestFour)
-}
diff --git a/test/v23tests/invocation.go b/test/v23tests/invocation.go
deleted file mode 100644
index c5201c9..0000000
--- a/test/v23tests/invocation.go
+++ /dev/null
@@ -1,118 +0,0 @@
-// Copyright 2015 The Vanadium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package v23tests
-
-import (
- "bytes"
- "container/list"
- "io"
- "syscall"
-
- "v.io/x/ref/test/modules"
-)
-
-type privateHandle modules.Handle
-
-// Invocation represents a single invocation of a Binary.
-//
-// 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 struct {
- // The handle to the process that was run when this invocation was started.
- privateHandle
-
- // The environment to which this invocation belongs.
- env *T
-
- // 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
-}
-
-// Path returns the path to the binary that was used for this invocation.
-func (i *Invocation) Path() string {
- return i.path
-}
-
-// Exists returns true if the invocation still exists.
-func (i *Invocation) Exists() bool {
- return syscall.Kill(i.privateHandle.Pid(), 0) == nil
-}
-
-// Kill 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.
-func (i *Invocation) Kill(sig syscall.Signal) error {
- pid := i.privateHandle.Pid()
- i.env.ctx.VI(1).Infof("sending signal %v to PID %d", sig, pid)
- return syscall.Kill(pid, sig)
-}
-
-// Output reads the invocation's stdout until EOF and then returns what
-// was read as a string.
-func (i *Invocation) Output() string {
- buf := bytes.Buffer{}
- _, err := buf.ReadFrom(i.Stdout())
- if err != nil {
- i.env.Fatalf("%s: ReadFrom() failed: %v", Caller(1), err)
- }
- return buf.String()
-}
-
-// 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.
-func (i *Invocation) Wait(stdout, stderr io.Writer) error {
- err := i.privateHandle.Shutdown(stdout, stderr)
- i.hasShutdown = true
- i.shutdownErr = err
- return err
-}
-
-// Shutdown is the same as Wait, but hides the Shutdown method on
-// the embedded modules.Handle.
-func (i *Invocation) Shutdown(stdout, stderr io.Writer) error {
- return i.Wait(stdout, stderr)
-}
-
-// WaitOrDie 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.
-func (i *Invocation) WaitOrDie(stdout, stderr io.Writer) {
- if err := i.Wait(stdout, stderr); err != nil {
- i.env.Fatalf("%s: FATAL: Wait() for pid %d failed: %v", Caller(1), i.privateHandle.Pid(), err)
- }
-}
-
-// Environment returns the instance of the test environment that this
-// invocation was from.
-func (i *Invocation) Environment() *T {
- return i.env
-}
diff --git a/test/v23tests/v23tests.go b/test/v23tests/v23tests.go
deleted file mode 100644
index 46473c7..0000000
--- a/test/v23tests/v23tests.go
+++ /dev/null
@@ -1,658 +0,0 @@
-// Copyright 2015 The Vanadium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package v23tests
-
-import (
- "errors"
- "fmt"
- "io/ioutil"
- "math/rand"
- "os"
- "os/exec"
- "path"
- "path/filepath"
- "runtime"
- "strings"
- "syscall"
- "testing"
- "time"
-
- "v.io/v23"
- "v.io/v23/context"
-
- "v.io/x/ref"
- "v.io/x/ref/test"
- "v.io/x/ref/test/modules"
- "v.io/x/ref/test/testutil"
-)
-
-// TB is an exact mirror of testing.TB. It is provided to allow for testing
-// of this package using a mock implementation. As per testing.TB, it is not
-// intended to be implemented outside of this package.
-type TB 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 struct {
- // The embedded TB
- TB
-
- ctx *context.T
-
- // The function to shutdown the context used to create the environment.
- shutdown v23.Shutdown
-
- // The shell to use to start commands.
- shell *modules.Shell
-
- // Maps path to Binary.
- builtBinaries map[string]*Binary
-
- tempFiles []*os.File
- tempDirs []string
- binDir, cachedBinDir string
- dirStack []string
-
- invocations []*Invocation
-}
-
-var errNotShutdown = errors.New("has not been shutdown")
-
-// Caller returns a string of the form <filename>:<lineno> for the
-// caller specified by skip, where skip is as per runtime.Caller.
-func Caller(skip int) string {
- _, file, line, _ := runtime.Caller(skip + 1)
- return fmt.Sprintf("%s:%d", filepath.Base(file), line)
-}
-
-// SkipInRegressionBefore skips the test if this is being run in a regression
-// test and some of the binaries are older than the specified date.
-// This is useful when we're breaking compatibility and we know it.
-// The parameter is a string formatted date YYYY-MM-DD.
-func (t *T) SkipInRegressionBefore(dateStr string) {
- testStr := os.Getenv("V23_REGTEST_DATE")
- if testStr == "" {
- return
- }
- testDate, err := time.Parse("2006-01-02", testStr)
- if err != nil {
- t.Fatalf("%s: could not parse V23_REGTEST_DATE=%q: %v", Caller(1), testStr, err)
- }
- date, err := time.Parse("2006-01-02", dateStr)
- if err != nil {
- t.Fatalf("%s: could not parse date %q: %v", Caller(1), dateStr, err)
- }
- if testDate.Before(date) {
- t.Skipf("Skipping regression test with binary from %s", testStr)
- }
-}
-
-// Run constructs a Binary for path and invokes Run on it.
-func (t *T) Run(path string, args ...string) string {
- return t.BinaryFromPath(path).run(args...)
-}
-
-// Run constructs a Binary for path and invokes Run on it using
-// the specified StartOpts
-func (t *T) RunWithOpts(opts modules.StartOpts, path string, args ...string) string {
- b := t.BinaryFromPath(path)
- return b.WithStartOpts(opts).run(args...)
-}
-
-// WaitFunc is the type of the functions to be used in conjunction
-// with WaitFor and WaitForAsync. It should return a value or an error
-// when it wants those functions to terminate, returning a nil value
-// and nil error will result in it being called again after the specified
-// delay time specified in the calls to WaitFor and WaitForAsync.
-type WaitFunc func() (interface{}, error)
-
-// WaitFor calls fn at least once with the specified delay value
-// between iterations until the first of the following is encountered:
-// 1. fn returns a non-nil value.
-// 2. fn returns an error value
-// 3. fn is executed at least once and the specified timeout is exceeded.
-//
-// WaitFor returns the non-nil value for the first case and calls e.Fatalf for
-// the other two cases.
-// WaitFor will always run fn at least once to completion and hence it will
-// hang if that first iteration of fn hangs. If this behaviour is not
-// appropriate, then WaitForAsync should be used.
-func (t *T) WaitFor(fn WaitFunc, delay, timeout time.Duration) interface{} {
- deadline := time.Now().Add(timeout)
- for {
- val, err := fn()
- if val != nil {
- return val
- }
- if err != nil {
- t.Fatalf("%s: the WaitFunc returned an error: %v", Caller(1), err)
- }
- if time.Now().After(deadline) {
- t.Fatalf("%s: timed out after %s", Caller(1), timeout)
- }
- time.Sleep(delay)
- }
-}
-
-// WaitForAsync is like WaitFor except that it calls fn in a goroutine
-// and can timeout during the execution of fn.
-func (t *T) WaitForAsync(fn WaitFunc, delay, timeout time.Duration) interface{} {
- resultCh := make(chan interface{})
- errCh := make(chan interface{})
- go func() {
- for {
- val, err := fn()
- if val != nil {
- resultCh <- val
- return
- }
- if err != nil {
- errCh <- err
- return
- }
- time.Sleep(delay)
- }
- }()
- select {
- case err := <-errCh:
- t.Fatalf("%s: the WaitFunc returned error: %v", Caller(1), err)
- case result := <-resultCh:
- return result
- case <-time.After(timeout):
- t.Fatalf("%s: timed out after %s", Caller(1), timeout)
- }
- return nil
-}
-
-// Pushd pushes the current working directory to the stack of
-// directories, returning it as its result, and changes the working
-// directory to dir.
-func (t *T) Pushd(dir string) string {
- cwd, err := os.Getwd()
- if err != nil {
- t.Fatalf("%s: Getwd failed: %s", Caller(1), err)
- }
- if err := os.Chdir(dir); err != nil {
- t.Fatalf("%s: Chdir failed: %s", Caller(1), err)
- }
- t.ctx.VI(1).Infof("Pushd: %s -> %s", cwd, dir)
- t.dirStack = append(t.dirStack, cwd)
- return cwd
-}
-
-// Popd pops the most recent entry from the directory stack and changes
-// the working directory to that directory. It returns the new working
-// directory as its result.
-func (t *T) Popd() string {
- if len(t.dirStack) == 0 {
- t.Fatalf("%s: directory stack empty", Caller(1))
- }
- dir := t.dirStack[len(t.dirStack)-1]
- t.dirStack = t.dirStack[:len(t.dirStack)-1]
- if err := os.Chdir(dir); err != nil {
- t.Fatalf("%s: Chdir failed: %s", Caller(1), err)
- }
- t.ctx.VI(1).Infof("Popd: -> %s", dir)
- return dir
-}
-
-// Caller returns a string of the form <filename>:<lineno> for the
-// caller specified by skip, where skip is as per runtime.Caller.
-func (t *T) Caller(skip int) string {
- return Caller(skip + 1)
-}
-
-// Context returns the root context of this environment.
-func (t *T) Context() *context.T {
- return t.ctx
-}
-
-// 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.
-func (t *T) Cleanup() {
- if t.Failed() {
- if test.IntegrationTestsDebugShellOnError {
- t.DebugSystemShell()
- }
- // Print out a summary of the invocations and their status.
- for i, inv := range t.invocations {
- if inv.hasShutdown && inv.Exists() {
- m := fmt.Sprintf("%d: %s has been shutdown but still exists: %v", i, inv.path, inv.shutdownErr)
- t.Log(m)
- t.ctx.VI(1).Info(m)
- t.ctx.VI(2).Infof("%d: %s %v", i, inv.path, inv.args)
- continue
- }
- if inv.shutdownErr != nil {
- m := fmt.Sprintf("%d: %s: shutdown status: %v", i, inv.path, inv.shutdownErr)
- t.Log(m)
- t.ctx.VI(1).Info(m)
- t.ctx.VI(2).Infof("%d: %s %v", i, inv.path, inv.args)
- }
- }
- }
-
- t.ctx.VI(1).Infof("V23Test.Cleanup")
- // Shut down all processes in LIFO order before attempting to delete any
- // files/directories to avoid potential 'file system busy' problems
- // on non-unix systems.
- for i := len(t.invocations); i > 0; i-- {
- inv := t.invocations[i-1]
- if inv.hasShutdown {
- t.ctx.VI(1).Infof("V23Test.Cleanup: %q has been shutdown", inv.Path())
- continue
- }
- t.ctx.VI(1).Infof("V23Test.Cleanup: Kill: %q", inv.Path())
- err := inv.Kill(syscall.SIGTERM)
- inv.Wait(os.Stdout, os.Stderr)
- t.ctx.VI(1).Infof("V23Test.Cleanup: Killed: %q: %v", inv.Path(), err)
- }
- t.ctx.VI(1).Infof("V23Test.Cleanup: all invocations taken care of.")
-
- if err := t.shell.Cleanup(os.Stdout, os.Stderr); err != nil {
- t.Fatalf("WARNING: could not clean up shell (%v)", err)
- }
-
- t.ctx.VI(1).Infof("V23Test.Cleanup: cleaning up binaries & files")
-
- for _, tempFile := range t.tempFiles {
- t.ctx.VI(1).Infof("V23Test.Cleanup: cleaning up %s", tempFile.Name())
- if err := tempFile.Close(); err != nil {
- t.ctx.Errorf("WARNING: Close(%q) failed: %v", tempFile.Name(), err)
- }
- if err := os.RemoveAll(tempFile.Name()); err != nil {
- t.ctx.Errorf("WARNING: RemoveAll(%q) failed: %v", tempFile.Name(), err)
- }
- }
-
- for _, tempDir := range t.tempDirs {
- t.ctx.VI(1).Infof("V23Test.Cleanup: cleaning up %s", tempDir)
- t.ctx.Infof("V23Test.Cleanup: cleaning up %s", tempDir)
- if err := os.RemoveAll(tempDir); err != nil {
- t.ctx.Errorf("WARNING: RemoveAll(%q) failed: %v", tempDir, err)
- }
- }
-
- // shutdown the runtime
- t.shutdown()
-}
-
-// GetVar returns the variable associated with the specified key
-// and an indication of whether it is defined or not.
-func (t *T) GetVar(key string) (string, bool) {
- return t.shell.GetVar(key)
-}
-
-// SetVar sets the value to be associated with key.
-func (t *T) SetVar(key, value string) {
- t.shell.SetVar(key, value)
-}
-
-// ClearVar removes the speficied variable from the Shell's environment
-func (t *T) ClearVar(key string) {
- t.shell.ClearVar(key)
-}
-
-func writeStringOrDie(t *T, f *os.File, s string) {
- if _, err := f.WriteString(s); err != nil {
- t.Fatalf("Write() failed: %v", err)
- }
-}
-
-// DebugSystemShell drops the user into a debug system shell (e.g. bash)
-// with any environment variables specified in env... (in VAR=VAL format)
-// available to it.
-// If there is no controlling TTY, DebugSystemShell will emit a warning message
-// and take no futher action. The DebugSystemShell also sets some environment
-// variables that relate to the running test:
-// - V23_TMP_DIR<#> contains the name of each temp directory created.
-// - V23_BIN_DIR contains the name of the directory containing binaries.
-func (t *T) DebugSystemShell(env ...string) {
- // Get the current working directory.
- cwd, err := os.Getwd()
- if err != nil {
- t.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 {
- t.ctx.Errorf("WARNING: Open(%v) failed, was asked to create a debug shell but cannot: %v", dev, err)
- return
- }
-
- var agentPath string
- if creds, err := t.shell.NewChildCredentials("debug"); err == nil {
- agentPath = creds.Path()
- } else {
- t.ctx.Errorf("WARNING: failed to obtain credentials for the debug shell: %v", err)
- }
-
- file := os.NewFile(uintptr(fd), dev)
- attr := os.ProcAttr{
- Files: []*os.File{file, file, file},
- Dir: cwd,
- }
- // Set up agent for Child.
- attr.Env = append(attr.Env, fmt.Sprintf("%s=%v", ref.EnvAgentPath, agentPath))
-
- // Set up environment for Child.
- for _, v := range t.shell.Env() {
- attr.Env = append(attr.Env, v)
- }
-
- for i, td := range t.tempDirs {
- attr.Env = append(attr.Env, fmt.Sprintf("V23_TMP_DIR%d=%s", i, td))
- }
-
- if len(t.cachedBinDir) > 0 {
- attr.Env = append(attr.Env, "V23_BIN_DIR="+t.BinDir())
- }
- attr.Env = append(attr.Env, env...)
-
- // Start up a new shell.
- writeStringOrDie(t, file, ">> Starting a new interactive shell\n")
- writeStringOrDie(t, file, "Hit CTRL-D to resume the test\n")
- if len(t.builtBinaries) > 0 {
- writeStringOrDie(t, file, "Built binaries:\n")
- for _, value := range t.builtBinaries {
- writeStringOrDie(t, file, "\t"+value.Path()+"\n")
- }
- }
- if len(t.cachedBinDir) > 0 {
- writeStringOrDie(t, file, fmt.Sprintf("Binaries are cached in %q\n", t.cachedBinDir))
- } else {
- writeStringOrDie(t, file, fmt.Sprintf("Caching of binaries was not enabled, being written to %q\n", t.binDir))
- }
-
- shellPath := "/bin/sh"
- if shellPathFromEnv := os.Getenv("SHELL"); shellPathFromEnv != "" {
- shellPath = shellPathFromEnv
- }
- proc, err := os.StartProcess(shellPath, []string{}, &attr)
- if err != nil {
- t.Fatalf("StartProcess(%q) failed: %v", shellPath, err)
- }
-
- // Wait until user exits the shell
- state, err := proc.Wait()
- if err != nil {
- t.Fatalf("Wait(%v) failed: %v", shellPath, err)
- }
-
- writeStringOrDie(t, file, fmt.Sprintf("<< Exited shell: %s\n", state.String()))
-}
-
-// BinaryFromPath returns a new Binary that, when started, will execute the
-// executable or script at the given path. The binary is assumed to not
-// implement the exec protocol defined in v.io/x/ref/lib/exec.
-//
-// E.g. env.BinaryFromPath("/bin/bash").Start("-c", "echo hello world").Output() -> "hello world"
-func (t *T) BinaryFromPath(path string) *Binary {
- return &Binary{
- env: t,
- envVars: nil,
- path: path,
- opts: t.shell.DefaultStartOpts().NoExecProgram(),
- }
-}
-
-// BuildGoPkg expects a Go package path that identifies a "main" package, and
-// any build flags to pass to "go build", and returns a Binary representing the
-// newly built binary.
-//
-// The resulting binary is assumed to not use the exec protocol defined in
-// v.io/x/ref/lib/exec and in particular will not have access to the security
-// agent or any other shared file descriptors. Environment variables and
-// command line arguments are the only means of communicating with the
-// invocations of this binary.
-//
-// Use this for non-Vanadium command-line tools and servers.
-func (t *T) BuildGoPkg(pkg string, flags ...string) *Binary {
- return t.buildPkg(pkg, flags...)
-}
-
-// BuildV23 is like BuildGoPkg, but instead assumes that the resulting binary is
-// a Vanadium application and does implement the exec protocol defined in
-// v.io/x/ref/lib/exec. The invocations of this binary will have access to the
-// security agent configured for the parent process, to shared file descriptors,
-// the config shared by the exec package.
-//
-// Use this for Vanadium servers. Note that some vanadium client only binaries,
-// that do not call v23.Init and hence do not implement the exec protocol cannot
-// be used via BuildV23Pkg.
-func (t *T) BuildV23Pkg(pkg string, flags ...string) *Binary {
- b := t.buildPkg(pkg, flags...)
- b.opts = t.shell.DefaultStartOpts().ExternalProgram()
- return b
-}
-
-func (t *T) buildPkg(pkg string, flags ...string) *Binary {
- then := time.Now()
- loc := Caller(2)
- cached, built_path, err := buildPkg(t, t.BinDir(), pkg, flags)
- if err != nil {
- t.Fatalf("%s: buildPkg(%s, %v) failed: %v", loc, pkg, flags, err)
- return nil
- }
- if _, err := os.Stat(built_path); err != nil {
- t.Fatalf("%s: buildPkg(%s, %v) failed to stat %q", loc, pkg, flags, built_path)
- }
- taken := time.Now().Sub(then)
- if cached {
- t.ctx.Infof("%s: using %s, from %s in %s.", loc, pkg, built_path, taken)
- } else {
- t.ctx.Infof("%s: built %s, written to %s in %s.", loc, pkg, built_path, taken)
- }
- binary := &Binary{
- env: t,
- envVars: nil,
- path: built_path,
- opts: t.shell.DefaultStartOpts().NoExecProgram(),
- }
- t.builtBinaries[pkg] = binary
- return binary
-}
-
-// NewTempFile creates a temporary file. Temporary files will be deleted
-// by Cleanup.
-func (t *T) NewTempFile() *os.File {
- loc := Caller(1)
- f, err := ioutil.TempFile("", "")
- if err != nil {
- t.Fatalf("%s: TempFile() failed: %v", loc, err)
- }
- t.ctx.Infof("%s: created temporary file at %s", loc, f.Name())
- t.tempFiles = append(t.tempFiles, f)
- return f
-}
-
-// NewTempDir creates a temporary directory. Temporary directories and
-// their contents will be deleted by Cleanup.
-func (t *T) NewTempDir(dir string) string {
- loc := Caller(1)
- f, err := ioutil.TempDir(dir, "")
- if err != nil {
- t.Fatalf("%s: TempDir() failed: %v", loc, err)
- }
- t.ctx.Infof("%s: created temporary directory at %s", loc, f)
- t.tempDirs = append(t.tempDirs, f)
- return f
-}
-
-func (t *T) appendInvocation(inv *Invocation) {
- t.invocations = append(t.invocations, inv)
-}
-
-// Creates a new local testing environment. A local testing environment has a
-// a security principal available via v23.GetPrincipal(t.Context()).
-//
-// 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.NewT(t)
-// defer env.Cleanup()
-//
-// ...
-// }
-func New(t TB) *T {
- ctx, shutdown := v23.Init()
-
- principal := testutil.NewPrincipal("root")
- ctx, err := v23.WithPrincipal(ctx, principal)
- if err != nil {
- t.Fatalf("failed to set principal: %v", err)
- }
-
- ctx.Infof("created root principal: %v", principal)
-
- shell, err := modules.NewShell(ctx, principal, testing.Verbose(), t)
- if err != nil {
- t.Fatalf("NewShell() failed: %v", err)
- }
- opts := modules.DefaultStartOpts()
- opts.StartTimeout = time.Minute
- opts.ShutdownTimeout = 5 * time.Minute
- shell.SetDefaultStartOpts(opts)
-
- // The V23_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.
- cachedBinDir := os.Getenv("V23_BIN_DIR")
- e := &T{
- TB: t,
- ctx: ctx,
- builtBinaries: make(map[string]*Binary),
- shell: shell,
- tempFiles: []*os.File{},
- tempDirs: []string{},
- cachedBinDir: cachedBinDir,
- shutdown: shutdown,
- }
- if len(e.cachedBinDir) == 0 {
- e.binDir = e.NewTempDir("")
- }
- return e
-}
-
-// Shell returns the underlying modules.Shell used by v23tests.
-func (t *T) Shell() *modules.Shell {
- return t.shell
-}
-
-// BinDir returns the directory that binarie files are stored in.
-func (t *T) BinDir() string {
- if len(t.cachedBinDir) > 0 {
- return t.cachedBinDir
- }
- return t.binDir
-}
-
-// 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(t *T, binDir, pkg string, flags []string) (bool, string, error) {
- binFile := filepath.Join(binDir, path.Base(pkg))
- t.ctx.Infof("buildPkg: %v .. %v %v", binDir, pkg, flags)
- if _, err := os.Stat(binFile); err != nil {
- if !os.IsNotExist(err) {
- return false, "", err
- }
- baseName := path.Base(binFile)
- tmpdir, err := ioutil.TempDir(binDir, baseName+"-")
- if err != nil {
- return false, "", err
- }
- defer os.RemoveAll(tmpdir)
- uniqueBinFile := filepath.Join(tmpdir, baseName)
-
- buildArgs := []string{"go", "build", "-x", "-o", uniqueBinFile}
- buildArgs = append(buildArgs, flags...)
- buildArgs = append(buildArgs, pkg)
- cmd := exec.Command("jiri", buildArgs...)
- if output, err := cmd.CombinedOutput(); err != nil {
- t.ctx.VI(1).Infof("\n%v:\n%v\n", strings.Join(cmd.Args, " "), string(output))
- return false, "", err
- }
- if err := os.Rename(uniqueBinFile, binFile); err != nil {
- // It seems that on some systems a rename may fail if another rename
- // is in progress in the same directory. We back a random amount of time
- // in the hope that a second attempt will succeed.
- time.Sleep(time.Duration(rand.Int63n(1000)) * time.Millisecond)
- if err := os.Rename(uniqueBinFile, binFile); err != nil {
- return false, "", err
- }
- }
- return false, binFile, nil
- }
- return true, binFile, nil
-}
-
-// RunTest runs a single Vanadium 'v23 style' integration test.
-func RunTest(t *testing.T, fn func(i *T)) {
- if !test.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
-// ref.EnvNamespacePrefix variable in the test environment so that all
-// subsequent invocations will access this root mount table. It also
-// modifies i's context to use this mount table as a namespace root.
-func RunRootMT(i *T, args ...string) (*Binary, *Invocation) {
- b := i.BuildV23Pkg("v.io/x/ref/services/mounttable/mounttabled")
- inv := b.start(1, args...)
- name := inv.ExpectVar("NAME")
- inv.Environment().SetVar(ref.EnvNamespacePrefix, name)
- i.ctx.Infof("Running root mount table: %q", name)
- v23.GetNamespace(i.ctx).SetRoots(name)
- return b, inv
-}
-
-// UseSharedBinDir ensures that a shared directory is used for binaries
-// across multiple instances of the test environment. This is achieved
-// by setting the V23_BIN_DIR environment variable if it is not already
-// set in the test processes environment (as will typically be the case when
-// these tests are run from the jiri tool). It is intended to be called
-// from TestMain.
-func UseSharedBinDir() func() {
- if v23BinDir := os.Getenv("V23_BIN_DIR"); len(v23BinDir) == 0 {
- v23BinDir, err := ioutil.TempDir("", "bin-")
- if err == nil {
- os.Setenv("V23_BIN_DIR", v23BinDir)
- return func() { os.RemoveAll(v23BinDir) }
- }
- }
- return func() {}
-}
diff --git a/test/v23tests/v23tests_test.go b/test/v23tests/v23tests_test.go
deleted file mode 100644
index bd79acc..0000000
--- a/test/v23tests/v23tests_test.go
+++ /dev/null
@@ -1,448 +0,0 @@
-// Copyright 2015 The Vanadium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package v23tests_test
-
-import (
- "bytes"
- "crypto/sha1"
- "fmt"
- "os"
- "regexp"
- "strings"
- "syscall"
- "testing"
- "time"
-
- "v.io/v23/naming"
- "v.io/v23/security"
-
- "v.io/x/ref"
- "v.io/x/ref/internal/logger"
- _ "v.io/x/ref/runtime/factories/generic"
- "v.io/x/ref/test/modules"
- "v.io/x/ref/test/testutil"
- "v.io/x/ref/test/v23tests"
-)
-
-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, "--v23.tcp.address=127.0.0.1:0")
- proxyBin := env.BuildV23Pkg("v.io/x/ref/services/xproxy/xproxyd")
- nsBin := env.BuildGoPkg("v.io/x/ref/cmd/namespace")
-
- mt, ok := env.GetVar(ref.EnvNamespacePrefix)
- if !ok || len(mt) == 0 {
- t.Fatalf("expected a mount table name")
- }
-
- proxy := proxyBin.Start("--v23.tcp.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", "...")
- line, _ := inv.ReadAll()
- parts := re.FindStringSubmatch(line)
- if len(parts) == 2 {
- if want, got := security.JoinPatternName("root/child", 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.
-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) {
- if modules.IsChildProcess() {
- globalT = t
- if err := modules.Dispatch(); err != nil {
- t.Errorf("modules.Dispatch failed: %v", err)
- }
- }
-}
-
-var RunIntegrationTestInChild = modules.Register(func(env *modules.Env, args ...string) error {
- v23tests.RunTest(globalT, IntegrationTestInChild)
- return nil
-}, "RunIntegrationTestInChild")
-
-func TestDeferHandling(t *testing.T) {
- t.Skip("https://v.io/i/686 -- test is flaky in Go1.5")
- sh, _ := modules.NewShell(nil, nil, testing.Verbose(), t)
- child, err := sh.Start(nil, RunIntegrationTestInChild, "--test.run=TestHelperProcess", "--v23.tests")
- if err != nil {
- t.Fatal(err)
- }
- child.Expect("Hello")
- child.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",
- } {
- child.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)
- }
- }
- logger.Global().Infof("Child\n=============\n%s", stderr.String())
- logger.Global().Infof("-----------------")
-}
-
-func TestInputRedirection(t *testing.T) {
- testutil.InitRandGenerator(t.Logf)
- env := v23tests.New(t)
- defer env.Cleanup()
-
- echo := env.BinaryFromPath("/bin/echo")
- cat := env.BinaryFromPath("/bin/cat")
-
- if want, got := "Hello, world!\n", cat.WithStdin(echo.Start("Hello, world!").Stdout()).Start().Output(); want != got {
- t.Fatalf("unexpected output, got %q, want %q", got, want)
- }
-
- // Read something from a file.
- {
- want := "Hello from a file!"
- f := env.NewTempFile()
- f.WriteString(want)
- f.Seek(0, 0)
- if got := cat.WithStdin(f).Start().Output(); want != got {
- t.Fatalf("unexpected output, got %q, want %q", got, want)
- }
- }
-
- // Try it again with 1Mb.
- {
- want := testutil.RandomBytes(1 << 20)
- expectedSum := sha1.Sum(want)
- f := env.NewTempFile()
- f.Write(want)
- f.Seek(0, 0)
- got := cat.WithStdin(f).Start().Output()
- if len(got) != len(want) {
- t.Fatalf("length mismatch, got %d but wanted %d", len(want), len(got))
- }
- actualSum := sha1.Sum([]byte(got))
- if actualSum != expectedSum {
- t.Fatalf("SHA-1 mismatch, got %x but wanted %x", actualSum, expectedSum)
- }
- }
-}
-
-func TestDirStack(t *testing.T) {
- env := v23tests.New(t)
- defer env.Cleanup()
-
- home := os.Getenv("HOME")
- if len(home) == 0 {
- t.Fatalf("failed to read HOME environment variable")
- }
-
- getwd := func() string {
- cwd, err := os.Getwd()
- if err != nil {
- t.Fatalf("Getwd() failed: %v", err)
- }
- return cwd
- }
-
- cwd := getwd()
- if got, want := env.Pushd("/"), cwd; got != want {
- t.Fatalf("got %v, want %v", got, want)
- }
- if got, want := env.Pushd(home), "/"; got != want {
- t.Fatalf("got %v, want %v", got, want)
- }
- tcwd := getwd()
- if got, want := tcwd, home; got != want {
- t.Fatalf("got %v, want %v", got, want)
- }
- if got, want := env.Popd(), "/"; got != want {
- t.Fatalf("got %v, want %v", got, want)
- }
- if got, want := env.Popd(), cwd; got != want {
- t.Fatalf("got %v, want %v", got, want)
- }
- ncwd := getwd()
- if got, want := ncwd, cwd; got != want {
- t.Fatalf("got %v, want %v", got, want)
- }
-}
-
-func TestRun(t *testing.T) {
- env := v23tests.New(t)
- defer env.Cleanup()
-
- if got, want := env.Run("/bin/echo", "hello world"), "hello world"; got != want {
- t.Fatalf("got %v, want %v", got, want)
- }
-
- echo := env.BinaryFromPath("/bin/echo")
- if got, want := echo.Run("hello", "world"), "hello world"; got != want {
- t.Fatalf("got %v, want %v", got, want)
- }
-
- sadEcho := echo.WithPrefixArgs("sad")
- if got, want := sadEcho.Run("hello", "world"), "sad hello world"; got != want {
- t.Fatalf("got %v, want %v", got, want)
- }
-
- happyEcho := echo.WithPrefixArgs("happy")
- if got, want := happyEcho.Run("hello", "world"), "happy hello world"; got != want {
- t.Fatalf("got %v, want %v", got, want)
- }
-}
-
-type mockT struct {
- msg string
- failed bool
-}
-
-func (m *mockT) Error(args ...interface{}) {
- m.msg = fmt.Sprint(args...)
- m.failed = true
-}
-
-func (m *mockT) Errorf(format string, args ...interface{}) {
- m.msg = fmt.Sprintf(format, args...)
- m.failed = true
-}
-
-func (m *mockT) Fail() { panic("Fail") }
-
-func (m *mockT) FailNow() { panic("FailNow") }
-
-func (m *mockT) Failed() bool { return m.failed }
-
-func (m *mockT) Fatal(args ...interface{}) {
- panic(fmt.Sprint(args...))
-}
-
-func (m *mockT) Fatalf(format string, args ...interface{}) {
- panic(fmt.Sprintf(format, args...))
-}
-
-func (m *mockT) Log(args ...interface{}) {}
-
-func (m *mockT) Logf(format string, args ...interface{}) {}
-
-func (m *mockT) Skip(args ...interface{}) {}
-
-func (m *mockT) SkipNow() {}
-
-func (m *mockT) Skipf(format string, args ...interface{}) {}
-
-func (m *mockT) Skipped() bool { return false }
-
-func TestRunFailFromPath(t *testing.T) {
- mock := &mockT{}
- env := v23tests.New(mock)
- defer env.Cleanup()
-
- defer func() {
- msg := recover().(string)
- // this, and the tests below are intended to ensure that line #s
- // are captured and reported correctly.
- if got, want := msg, "v23tests_test.go:304"; !strings.Contains(got, want) {
- t.Fatalf("%q does not contain %q", got, want)
- }
- if got, want := msg, "fork/exec /bin/echox: no such file or directory"; !strings.Contains(got, want) {
- t.Fatalf("%q does not contain %q", got, want)
- }
- }()
- env.Run("/bin/echox", "hello world")
-}
-
-func TestRunFail(t *testing.T) {
- mock := &mockT{}
- env := v23tests.New(mock)
- defer env.Cleanup()
-
- // Fail fast.
- sh := env.Shell()
- opts := sh.DefaultStartOpts()
- opts.StartTimeout = 100 * time.Millisecond
- sh.SetDefaultStartOpts(opts)
- defer func() {
- msg := recover().(string)
- if got, want := msg, "v23tests_test.go:326"; !strings.Contains(got, want) {
- t.Fatalf("%q does not contain %q", got, want)
- }
- if got, want := msg, "StartWithOpts"; !strings.Contains(got, want) {
- t.Fatalf("%q does not contain %q", got, want)
- }
- }()
- v23tests.RunRootMT(env, "--xxv23.tcp.address=127.0.0.1:0")
-}
-
-func TestWaitTimeout(t *testing.T) {
- env := v23tests.New(&mockT{})
- defer env.Cleanup()
-
- iterations := 0
- sleeper := func() (interface{}, error) {
- iterations++
- return nil, nil
- }
-
- defer func() {
- if iterations == 0 {
- t.Fatalf("our sleeper didn't get to run")
- }
- if got, want := recover().(string), "v23tests_test.go:347: timed out"; !strings.Contains(got, want) {
- t.Fatalf("%q does not contain %q", got, want)
- }
- }()
- env.WaitFor(sleeper, time.Millisecond, 50*time.Millisecond)
-}
-
-func TestWaitAsyncTimeout(t *testing.T) {
- env := v23tests.New(&mockT{})
- defer env.Cleanup()
-
- iterations := 0
- sleeper := func() (interface{}, error) {
- time.Sleep(time.Minute)
- iterations++
- return nil, nil
- }
-
- defer func() {
- if iterations != 0 {
- t.Fatalf("our sleeper got to run")
- }
- if got, want := recover().(string), "v23tests_test.go:369: timed out"; !strings.Contains(got, want) {
- t.Fatalf("%q does not contain %q", got, want)
- }
- }()
- env.WaitForAsync(sleeper, time.Millisecond, 50*time.Millisecond)
-}
-
-func TestWaitFor(t *testing.T) {
- env := v23tests.New(t)
- defer env.Cleanup()
- iterations := 0
- countIn5s := func() (interface{}, error) {
- iterations++
- if iterations%5 == 0 {
- return iterations, nil
- }
- return nil, nil
- }
-
- r := env.WaitFor(countIn5s, time.Millisecond, 50*time.Millisecond)
- if got, want := r.(int), 5; got != want {
- env.Fatalf("got %d, want %d", got, want)
- }
-
- r = env.WaitForAsync(countIn5s, time.Millisecond, 50*time.Millisecond)
- if got, want := r.(int), 10; got != want {
- env.Fatalf("got %d, want %d", got, want)
- }
-}
-
-func builder(t *testing.T) (string, string) {
- env := v23tests.New(t)
- defer env.Cleanup()
- bin := env.BuildGoPkg("v.io/x/ref/test/v23tests")
- return env.BinDir(), bin.Path()
-}
-
-func TestCachedBuild(t *testing.T) {
- cleanup := v23tests.UseSharedBinDir()
- defer cleanup()
- defer os.Setenv("V23_BIN_DIR", "")
-
- bin1, path1 := builder(t)
- bin2, path2 := builder(t)
-
- if bin1 != bin2 {
- t.Fatalf("caching failed, bin dirs differ: %q != %q", bin1, bin2)
- }
-
- if path1 != path2 {
- t.Fatalf("caching failed, paths differ: %q != %q", path1, path2)
- }
-}
-
-func TestUncachedBuild(t *testing.T) {
- bin1, path1 := builder(t)
- bin2, path2 := builder(t)
-
- if bin1 == bin2 {
- t.Fatalf("failed, bin dirs are the same: %q != %q", bin1, bin2)
- }
-
- if path1 == path2 {
- t.Fatalf("failed, paths are the same: %q != %q", path1, path2)
- }
-}
-
-func TestShutdownAndCleanupTogetherDontHang(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.Shutdown(&buf, nil)
- if want, got := "hello world\n", buf.String(); want != got {
- t.Fatalf("unexpected output, want %s, got %s", want, got)
- }
- // Make sure that we can call Shutdown and Cleanup without hanging.
-}