| // 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 test |
| |
| import ( |
| "fmt" |
| "io" |
| "net" |
| "os" |
| "path/filepath" |
| "runtime" |
| "strings" |
| "testing" |
| "time" |
| |
| "v.io/v23" |
| "v.io/v23/context" |
| "v.io/v23/naming" |
| "v.io/v23/options" |
| "v.io/v23/rpc" |
| "v.io/v23/security" |
| "v.io/v23/vdlroot/signature" |
| "v.io/v23/verror" |
| "v.io/x/ref/envvar" |
| _ "v.io/x/ref/profiles" |
| inaming "v.io/x/ref/profiles/internal/naming" |
| irpc "v.io/x/ref/profiles/internal/rpc" |
| "v.io/x/ref/profiles/internal/rpc/stream/message" |
| "v.io/x/ref/profiles/internal/testing/mocks/mocknet" |
| "v.io/x/ref/services/mounttable/mounttablelib" |
| "v.io/x/ref/test" |
| "v.io/x/ref/test/expect" |
| "v.io/x/ref/test/modules" |
| "v.io/x/ref/test/testutil" |
| ) |
| |
| //go:generate v23 test generate . |
| |
| func rootMT(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error { |
| seclevel := options.SecurityConfidential |
| if len(args) == 1 && args[0] == "nosec" { |
| seclevel = options.SecurityNone |
| } |
| return runRootMT(stdin, stdout, stderr, seclevel, env, args...) |
| } |
| |
| func runRootMT(stdin io.Reader, stdout, stderr io.Writer, seclevel options.SecurityLevel, env map[string]string, args ...string) error { |
| ctx, shutdown := v23.Init() |
| defer shutdown() |
| |
| lspec := v23.GetListenSpec(ctx) |
| server, err := v23.NewServer(ctx, options.ServesMountTable(true), seclevel) |
| if err != nil { |
| return fmt.Errorf("root failed: %v", err) |
| } |
| mt, err := mounttablelib.NewMountTableDispatcher("", "", "mounttable") |
| if err != nil { |
| return fmt.Errorf("mounttablelib.NewMountTableDispatcher failed: %s", err) |
| } |
| eps, err := server.Listen(lspec) |
| if err != nil { |
| return fmt.Errorf("server.Listen failed: %s", err) |
| } |
| if err := server.ServeDispatcher("", mt); err != nil { |
| return fmt.Errorf("root failed: %s", err) |
| } |
| fmt.Fprintf(stdout, "PID=%d\n", os.Getpid()) |
| for _, ep := range eps { |
| fmt.Fprintf(stdout, "MT_NAME=%s\n", ep.Name()) |
| } |
| modules.WaitForEOF(stdin) |
| return nil |
| } |
| |
| type treeDispatcher struct{ id string } |
| |
| func (d treeDispatcher) Lookup(suffix string) (interface{}, security.Authorizer, error) { |
| return &echoServerObject{d.id, suffix}, nil, nil |
| } |
| |
| type echoServerObject struct { |
| id, suffix string |
| } |
| |
| func (es *echoServerObject) Echo(_ *context.T, _ rpc.ServerCall, m string) (string, error) { |
| if len(es.suffix) > 0 { |
| return fmt.Sprintf("%s.%s: %s\n", es.id, es.suffix, m), nil |
| } |
| return fmt.Sprintf("%s: %s\n", es.id, m), nil |
| } |
| |
| func (es *echoServerObject) Sleep(_ *context.T, _ rpc.ServerCall, d string) error { |
| duration, err := time.ParseDuration(d) |
| if err != nil { |
| return err |
| } |
| time.Sleep(duration) |
| return nil |
| } |
| |
| func echoServer(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error { |
| ctx, shutdown := v23.Init() |
| defer shutdown() |
| |
| id, mp := args[0], args[1] |
| disp := &treeDispatcher{id: id} |
| server, err := v23.NewServer(ctx) |
| if err != nil { |
| return err |
| } |
| defer server.Stop() |
| eps, err := server.Listen(v23.GetListenSpec(ctx)) |
| if err != nil { |
| return err |
| } |
| if err := server.ServeDispatcher(mp, disp); err != nil { |
| return err |
| } |
| fmt.Fprintf(stdout, "PID=%d\n", os.Getpid()) |
| for _, ep := range eps { |
| fmt.Fprintf(stdout, "NAME=%s\n", ep.Name()) |
| } |
| modules.WaitForEOF(stdin) |
| return nil |
| } |
| |
| func echoClient(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error { |
| ctx, shutdown := v23.Init() |
| defer shutdown() |
| |
| name := args[0] |
| args = args[1:] |
| client := v23.GetClient(ctx) |
| for _, a := range args { |
| var r string |
| if err := client.Call(ctx, name, "Echo", []interface{}{a}, []interface{}{&r}); err != nil { |
| return err |
| } |
| fmt.Fprintf(stdout, r) |
| } |
| return nil |
| } |
| |
| func newCtx() (*context.T, v23.Shutdown) { |
| ctx, shutdown := test.InitForTest() |
| v23.GetNamespace(ctx).CacheCtl(naming.DisableCache(true)) |
| return ctx, shutdown |
| } |
| |
| func runMountTable(t *testing.T, ctx *context.T, args ...string) (*modules.Shell, func()) { |
| sh, err := modules.NewShell(ctx, nil, testing.Verbose(), t) |
| if err != nil { |
| t.Fatalf("unexpected error: %s", err) |
| } |
| root, err := sh.Start("rootMT", nil, args...) |
| if err != nil { |
| t.Fatalf("unexpected error for root mt: %s", err) |
| } |
| deferFn := func() { |
| sh.Cleanup(os.Stderr, os.Stderr) |
| } |
| |
| root.ExpectVar("PID") |
| rootName := root.ExpectVar("MT_NAME") |
| |
| sh.SetVar(envvar.NamespacePrefix, rootName) |
| if err = v23.GetNamespace(ctx).SetRoots(rootName); err != nil { |
| t.Fatalf("unexpected error setting namespace roots: %s", err) |
| } |
| |
| return sh, deferFn |
| } |
| |
| func runClient(t *testing.T, sh *modules.Shell) error { |
| clt, err := sh.Start("echoClient", nil, "echoServer", "a message") |
| if err != nil { |
| t.Fatalf("unexpected error: %s", err) |
| } |
| s := expect.NewSession(t, clt.Stdout(), 30*time.Second) |
| s.Expect("echoServer: a message") |
| if s.Failed() { |
| return s.Error() |
| } |
| return nil |
| } |
| |
| func numServers(t *testing.T, ctx *context.T, name string) int { |
| me, err := v23.GetNamespace(ctx).Resolve(ctx, name) |
| if err != nil { |
| return 0 |
| } |
| return len(me.Servers) |
| } |
| |
| // TODO(cnicolaou): figure out how to test and see what the internals |
| // of tryCall are doing - e.g. using stats counters. |
| func TestMultipleEndpoints(t *testing.T) { |
| ctx, shutdown := newCtx() |
| defer shutdown() |
| |
| sh, fn := runMountTable(t, ctx) |
| defer fn() |
| srv, err := sh.Start("echoServer", nil, "echoServer", "echoServer") |
| if err != nil { |
| t.Fatalf("unexpected error: %s", err) |
| } |
| s := expect.NewSession(t, srv.Stdout(), time.Minute) |
| s.ExpectVar("PID") |
| s.ExpectVar("NAME") |
| |
| // Verify that there are 1 entries for echoServer in the mount table. |
| if got, want := numServers(t, ctx, "echoServer"), 1; got != want { |
| t.Fatalf("got: %d, want: %d", got, want) |
| } |
| |
| runClient(t, sh) |
| |
| // Create a fake set of 100 entries in the mount table |
| for i := 0; i < 100; i++ { |
| // 203.0.113.0 is TEST-NET-3 from RFC5737 |
| ep := naming.FormatEndpoint("tcp", fmt.Sprintf("203.0.113.%d:443", i)) |
| n := naming.JoinAddressName(ep, "") |
| if err := v23.GetNamespace(ctx).Mount(ctx, "echoServer", n, time.Hour); err != nil { |
| t.Fatalf("unexpected error: %s", err) |
| } |
| } |
| |
| // Verify that there are 101 entries for echoServer in the mount table. |
| if got, want := numServers(t, ctx, "echoServer"), 101; got != want { |
| t.Fatalf("got: %q, want: %q", got, want) |
| } |
| |
| // TODO(cnicolaou): ok, so it works, but I'm not sure how |
| // long it should take or if the parallel connection code |
| // really works. Use counters to inspect it for example. |
| if err := runClient(t, sh); err != nil { |
| t.Fatalf("unexpected error: %s", err) |
| } |
| |
| srv.CloseStdin() |
| srv.Shutdown(nil, nil) |
| |
| // Verify that there are 100 entries for echoServer in the mount table. |
| if got, want := numServers(t, ctx, "echoServer"), 100; got != want { |
| t.Fatalf("got: %d, want: %d", got, want) |
| } |
| } |
| |
| func TestTimeout(t *testing.T) { |
| ctx, shutdown := newCtx() |
| defer shutdown() |
| client := v23.GetClient(ctx) |
| ctx, _ = context.WithTimeout(ctx, 100*time.Millisecond) |
| name := naming.JoinAddressName(naming.FormatEndpoint("tcp", "203.0.113.10:443"), "") |
| _, err := client.StartCall(ctx, name, "echo", []interface{}{"args don't matter"}) |
| t.Log(err) |
| if verror.ErrorID(err) != verror.ErrTimeout.ID { |
| t.Fatalf("wrong error: %s", err) |
| } |
| } |
| |
| func logErrors(t *testing.T, msg string, logerr, logstack, debugString bool, err error) { |
| _, file, line, _ := runtime.Caller(2) |
| loc := fmt.Sprintf("%s:%d", filepath.Base(file), line) |
| if logerr { |
| t.Logf("%s: %s: %v", loc, msg, err) |
| } |
| if logstack { |
| t.Logf("%s: %s: %v", loc, msg, verror.Stack(err).String()) |
| } |
| if debugString { |
| t.Logf("%s: %s: %v", loc, msg, verror.DebugString(err)) |
| } |
| } |
| |
| func TestStartCallErrors(t *testing.T) { |
| ctx, shutdown := newCtx() |
| defer shutdown() |
| client := v23.GetClient(ctx) |
| |
| ns := v23.GetNamespace(ctx) |
| v23.GetNamespace(ctx).CacheCtl(naming.DisableCache(true)) |
| |
| logErr := func(msg string, err error) { |
| logErrors(t, msg, true, false, false, err) |
| } |
| |
| emptyCtx := &context.T{} |
| _, err := client.StartCall(emptyCtx, "noname", "nomethod", nil) |
| if verror.ErrorID(err) != verror.ErrBadArg.ID { |
| t.Fatalf("wrong error: %s", err) |
| } |
| logErr("no context", err) |
| |
| p1 := options.ServerPublicKey{testutil.NewPrincipal().PublicKey()} |
| p2 := options.ServerPublicKey{testutil.NewPrincipal().PublicKey()} |
| _, err = client.StartCall(ctx, "noname", "nomethod", nil, p1, p2) |
| if verror.ErrorID(err) != verror.ErrBadArg.ID { |
| t.Fatalf("wrong error: %s", err) |
| } |
| logErr("too many public keys", err) |
| |
| // This will fail with NoServers, but because there is no mount table |
| // to communicate with. The error message should include a |
| // 'connection refused' string. |
| ns.SetRoots("/127.0.0.1:8101") |
| _, err = client.StartCall(ctx, "noname", "nomethod", nil, options.NoRetry{}) |
| if verror.ErrorID(err) != verror.ErrNoServers.ID { |
| t.Fatalf("wrong error: %s", err) |
| } |
| if want := "connection refused"; !strings.Contains(verror.DebugString(err), want) { |
| t.Fatalf("wrong error: %s - doesn't contain %q", err, want) |
| } |
| logErr("no mount table", err) |
| |
| // This will fail with NoServers, but because there really is no |
| // name registered with the mount table. |
| _, shutdown = runMountTable(t, ctx) |
| defer shutdown() |
| _, err = client.StartCall(ctx, "noname", "nomethod", nil, options.NoRetry{}) |
| if verror.ErrorID(err) != verror.ErrNoServers.ID { |
| t.Fatalf("wrong error: %s", err) |
| } |
| roots := ns.Roots() |
| if unwanted := "connection refused"; strings.Contains(err.Error(), unwanted) { |
| t.Fatalf("wrong error: %s - does contain %q", err, unwanted) |
| } |
| logErr("no name registered", err) |
| |
| // The following tests will fail with NoServers, but because there are |
| // no protocols that the client and servers (mount table, and "name") share. |
| nctx, nclient, err := v23.WithNewClient(ctx, irpc.PreferredProtocols([]string{"wsh"})) |
| |
| addr := naming.FormatEndpoint("nope", "127.0.0.1:1081") |
| if err := ns.Mount(ctx, "name", addr, time.Minute); err != nil { |
| t.Fatal(err) |
| } |
| |
| // This will fail in its attempt to call ResolveStep to the mount table |
| // because we are using both the new context and the new client. |
| _, err = nclient.StartCall(nctx, "name", "nomethod", nil, options.NoRetry{}) |
| if verror.ErrorID(err) != verror.ErrNoServers.ID { |
| t.Fatalf("wrong error: %s", err) |
| } |
| if want := "ResolveStep"; !strings.Contains(err.Error(), want) { |
| t.Fatalf("wrong error: %s - doesn't contain %q", err, want) |
| } |
| logErr("mismatched protocols", err) |
| |
| // This will fail in its attempt to invoke the actual RPC because |
| // we are using the old context (which supplies the context for the calls |
| // to ResolveStep) and the new client. |
| _, err = nclient.StartCall(ctx, "name", "nomethod", nil, options.NoRetry{}) |
| if verror.ErrorID(err) != verror.ErrNoServers.ID { |
| t.Fatalf("wrong error: %s", err) |
| } |
| if want := "nope"; !strings.Contains(err.Error(), want) { |
| t.Fatalf("wrong error: %s - doesn't contain %q", err, want) |
| } |
| if unwanted := "ResolveStep"; strings.Contains(err.Error(), unwanted) { |
| t.Fatalf("wrong error: %s - does contain %q", err, unwanted) |
| |
| } |
| logErr("mismatched protocols", err) |
| |
| // The following two tests will fail due to a timeout. |
| ns.SetRoots("/203.0.113.10:8101") |
| nctx, _ = context.WithTimeout(ctx, 100*time.Millisecond) |
| // This call will timeout talking to the mount table, returning |
| // NoServers, but with the string 'Timeout' in the message. |
| call, err := client.StartCall(nctx, "name", "noname", nil, options.NoRetry{}) |
| if verror.ErrorID(err) != verror.ErrNoServers.ID { |
| t.Fatalf("wrong error: %s", err) |
| } |
| if want := "Timeout"; !strings.Contains(err.Error(), want) { |
| t.Fatalf("wrong error: %s - doesn't contain %q", err, want) |
| } |
| if call != nil { |
| t.Fatalf("expected call to be nil") |
| } |
| logErr("timeout to mount table", err) |
| |
| // This, second test, will fail due a timeout contacting the server itself. |
| ns.SetRoots(roots...) |
| addr = naming.FormatEndpoint("tcp", "203.0.113.10:8101") |
| if err := ns.Mount(ctx, "new-name", addr, time.Minute); err != nil { |
| t.Fatal(err) |
| } |
| |
| nctx, _ = context.WithTimeout(ctx, 100*time.Millisecond) |
| call, err = client.StartCall(nctx, "new-name", "noname", nil, options.NoRetry{}) |
| if verror.ErrorID(err) != verror.ErrTimeout.ID { |
| t.Fatalf("wrong error: %s", err) |
| } |
| if call != nil { |
| t.Fatalf("expected call to be nil") |
| } |
| logErr("timeout to server", err) |
| } |
| |
| func dropDataDialer(network, address string, timeout time.Duration) (net.Conn, error) { |
| matcher := func(read bool, msg message.T) bool { |
| switch msg.(type) { |
| case *message.Data: |
| return true |
| } |
| return false |
| } |
| opts := mocknet.Opts{ |
| Mode: mocknet.V23CloseAtMessage, |
| V23MessageMatcher: matcher, |
| } |
| return mocknet.DialerWithOpts(opts, network, address, timeout) |
| } |
| |
| func TestStartCallBadProtocol(t *testing.T) { |
| ctx, shutdown := newCtx() |
| defer shutdown() |
| client := v23.GetClient(ctx) |
| |
| ns := v23.GetNamespace(ctx) |
| ns.CacheCtl(naming.DisableCache(true)) |
| |
| logErr := func(msg string, err error) { |
| logErrors(t, msg, true, false, false, err) |
| } |
| |
| rpc.RegisterProtocol("dropData", dropDataDialer, net.Listen) |
| |
| // The following test will fail due to a broken connection. |
| // We need to run mount table and servers with no security to use |
| // the V23CloseAtMessage net.Conn mock. |
| _, shutdown = runMountTable(t, ctx, "nosec") |
| defer shutdown() |
| |
| roots := ns.Roots() |
| brkRoot, err := mocknet.RewriteEndpointProtocol(roots[0], "dropData") |
| if err != nil { |
| t.Fatal(err) |
| } |
| ns.SetRoots(brkRoot.Name()) |
| |
| nctx, _ := context.WithTimeout(ctx, time.Minute) |
| call, err := client.StartCall(nctx, "name", "noname", nil, options.NoRetry{}, options.SecurityNone) |
| if verror.ErrorID(err) != verror.ErrBadProtocol.ID { |
| t.Fatalf("wrong error: %s", err) |
| } |
| if call != nil { |
| t.Fatalf("expected call to be nil") |
| } |
| logErr("broken connection", err) |
| |
| // The following test will fail with because the client will set up |
| // a secure connection to a server that isn't expecting one. |
| name, fn := initServer(t, ctx, options.SecurityNone) |
| defer fn() |
| |
| call, err = client.StartCall(nctx, name, "noname", nil, options.NoRetry{}) |
| if verror.ErrorID(err) != verror.ErrBadProtocol.ID { |
| t.Fatalf("wrong error: %s", err) |
| } |
| if call != nil { |
| t.Fatalf("expected call to be nil") |
| } |
| logErr("insecure server", err) |
| |
| // This is the inverse, secure server, insecure client |
| name, fn = initServer(t, ctx) |
| defer fn() |
| |
| call, err = client.StartCall(nctx, name, "noname", nil, options.NoRetry{}, options.SecurityNone) |
| if verror.ErrorID(err) != verror.ErrBadProtocol.ID { |
| t.Fatalf("wrong error: %s", err) |
| } |
| if call != nil { |
| t.Fatalf("expected call to be nil") |
| } |
| logErr("insecure client", err) |
| } |
| |
| func TestStartCallSecurity(t *testing.T) { |
| ctx, shutdown := newCtx() |
| defer shutdown() |
| client := v23.GetClient(ctx) |
| |
| logErr := func(msg string, err error) { |
| logErrors(t, msg, true, false, false, err) |
| } |
| |
| name, fn := initServer(t, ctx) |
| defer fn() |
| |
| // Create a context with a new principal that doesn't match the server, |
| // so that the client will not trust the server. |
| ctx1, err := v23.WithPrincipal(ctx, testutil.NewPrincipal("test-blessing")) |
| if err != nil { |
| t.Fatal(err) |
| } |
| call, err := client.StartCall(ctx1, name, "noname", nil, options.NoRetry{}) |
| if verror.ErrorID(err) != verror.ErrNotTrusted.ID { |
| t.Fatalf("wrong error: %s", err) |
| } |
| if call != nil { |
| t.Fatalf("expected call to be nil") |
| } |
| logErr("client does not trust server", err) |
| } |
| |
| func childPing(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error { |
| ctx, shutdown := test.InitForTest() |
| defer shutdown() |
| v23.GetNamespace(ctx).CacheCtl(naming.DisableCache(true)) |
| |
| name := args[0] |
| got := "" |
| if err := v23.GetClient(ctx).Call(ctx, name, "Ping", nil, []interface{}{&got}); err != nil { |
| fmt.Errorf("unexpected error: %s", err) |
| } |
| fmt.Fprintf(stdout, "RESULT=%s\n", got) |
| return nil |
| } |
| |
| func initServer(t *testing.T, ctx *context.T, opts ...rpc.ServerOpt) (string, func()) { |
| server, err := v23.NewServer(ctx, opts...) |
| if err != nil { |
| t.Fatalf("unexpected error: %s", err) |
| } |
| done := make(chan struct{}) |
| deferFn := func() { close(done); server.Stop() } |
| |
| eps, err := server.Listen(v23.GetListenSpec(ctx)) |
| if err != nil { |
| t.Fatalf("unexpected error: %s", err) |
| } |
| server.Serve("", &simple{done}, nil) |
| return eps[0].Name(), deferFn |
| } |
| |
| func testForVerror(t *testing.T, err error, verr verror.IDAction) { |
| _, file, line, _ := runtime.Caller(1) |
| loc := fmt.Sprintf("%s:%d", filepath.Base(file), line) |
| if verror.ErrorID(err) != verr.ID { |
| if _, ok := err.(verror.E); !ok { |
| t.Fatalf("%s: err %v not a verror", loc, err) |
| } |
| stack := "" |
| if err != nil { |
| stack = verror.Stack(err).String() |
| } |
| t.Fatalf("%s: expecting one of: %v, got: %v: stack: %s", loc, verr, err, stack) |
| } |
| } |
| |
| func TestTimeoutResponse(t *testing.T) { |
| ctx, shutdown := newCtx() |
| defer shutdown() |
| name, fn := initServer(t, ctx) |
| defer fn() |
| ctx, _ = context.WithTimeout(ctx, time.Millisecond) |
| if err := v23.GetClient(ctx).Call(ctx, name, "Sleep", nil, nil); err != nil { |
| testForVerror(t, err, verror.ErrTimeout) |
| return |
| } |
| } |
| |
| func TestArgsAndResponses(t *testing.T) { |
| ctx, shutdown := newCtx() |
| defer shutdown() |
| name, fn := initServer(t, ctx) |
| defer fn() |
| |
| call, err := v23.GetClient(ctx).StartCall(ctx, name, "Sleep", []interface{}{"too many args"}) |
| if err != nil { |
| t.Fatalf("unexpected error: %s", err) |
| } |
| err = call.Finish() |
| testForVerror(t, err, verror.ErrBadProtocol) |
| |
| call, err = v23.GetClient(ctx).StartCall(ctx, name, "Ping", nil) |
| if err != nil { |
| t.Fatalf("unexpected error: %s", err) |
| } |
| pong := "" |
| dummy := "" |
| err = call.Finish(&pong, &dummy) |
| testForVerror(t, err, verror.ErrBadProtocol) |
| } |
| |
| func TestAccessDenied(t *testing.T) { |
| ctx, shutdown := test.InitForTest() |
| defer shutdown() |
| |
| name, fn := initServer(t, ctx) |
| defer fn() |
| |
| ctx1, err := v23.WithPrincipal(ctx, testutil.NewPrincipal("test-blessing")) |
| // Client must recognize the server, otherwise it won't even send the request. |
| v23.GetPrincipal(ctx1).AddToRoots(v23.GetPrincipal(ctx).BlessingStore().Default()) |
| if err != nil { |
| t.Fatal(err) |
| } |
| call, err := v23.GetClient(ctx1).StartCall(ctx1, name, "Sleep", nil) |
| if err != nil { |
| t.Fatalf("unexpected error: %s", err) |
| } |
| err = call.Finish() |
| testForVerror(t, err, verror.ErrNoAccess) |
| } |
| |
| func TestCanceledBeforeFinish(t *testing.T) { |
| ctx, shutdown := newCtx() |
| defer shutdown() |
| name, fn := initServer(t, ctx) |
| defer fn() |
| |
| ctx, cancel := context.WithCancel(ctx) |
| call, err := v23.GetClient(ctx).StartCall(ctx, name, "Sleep", nil) |
| if err != nil { |
| t.Fatalf("unexpected error: %s", err) |
| } |
| // Cancel before we call finish. |
| cancel() |
| err = call.Finish() |
| testForVerror(t, err, verror.ErrCanceled) |
| } |
| |
| func TestCanceledDuringFinish(t *testing.T) { |
| ctx, shutdown := newCtx() |
| defer shutdown() |
| name, fn := initServer(t, ctx) |
| defer fn() |
| |
| ctx, cancel := context.WithCancel(ctx) |
| call, err := v23.GetClient(ctx).StartCall(ctx, name, "Sleep", nil) |
| if err != nil { |
| t.Fatalf("unexpected error: %s", err) |
| } |
| // Cancel whilst the RPC is running. |
| go func() { |
| time.Sleep(100 * time.Millisecond) |
| cancel() |
| }() |
| err = call.Finish() |
| testForVerror(t, err, verror.ErrCanceled) |
| } |
| |
| func TestRendezvous(t *testing.T) { |
| ctx, shutdown := newCtx() |
| defer shutdown() |
| sh, fn := runMountTable(t, ctx) |
| defer fn() |
| |
| name := "echoServer" |
| |
| // We start the client before we start the server, StartCall will reresolve |
| // the name until it finds an entry or timesout after an exponential |
| // backoff of some minutes. |
| startServer := func() { |
| time.Sleep(10 * time.Millisecond) |
| srv, _ := sh.Start("echoServer", nil, "message", name) |
| s := expect.NewSession(t, srv.Stdout(), time.Minute) |
| s.ExpectVar("PID") |
| s.ExpectVar("NAME") |
| } |
| go startServer() |
| |
| call, err := v23.GetClient(ctx).StartCall(ctx, name, "Echo", []interface{}{"hello"}) |
| if err != nil { |
| t.Fatalf("unexpected error: %s", err) |
| } |
| |
| response := "" |
| if err := call.Finish(&response); err != nil { |
| testForVerror(t, err, verror.ErrCanceled) |
| return |
| } |
| if got, want := response, "message: hello\n"; got != want { |
| t.Errorf("got %q, want %q", got, want) |
| } |
| } |
| |
| func TestCallback(t *testing.T) { |
| ctx, shutdown := newCtx() |
| defer shutdown() |
| sh, fn := runMountTable(t, ctx) |
| defer fn() |
| |
| name, fn := initServer(t, ctx) |
| defer fn() |
| |
| srv, err := sh.Start("childPing", nil, name) |
| if err != nil { |
| t.Fatalf("unexpected error: %s", err) |
| } |
| s := expect.NewSession(t, srv.Stdout(), time.Minute) |
| if got, want := s.ExpectVar("RESULT"), "pong"; got != want { |
| t.Errorf("got %q, want %q", got, want) |
| } |
| } |
| |
| func TestStreamTimeout(t *testing.T) { |
| ctx, shutdown := newCtx() |
| defer shutdown() |
| name, fn := initServer(t, ctx) |
| defer fn() |
| |
| want := 10 |
| ctx, _ = context.WithTimeout(ctx, 300*time.Millisecond) |
| call, err := v23.GetClient(ctx).StartCall(ctx, name, "Source", []interface{}{want}) |
| if err != nil { |
| if verror.ErrorID(err) != verror.ErrTimeout.ID { |
| t.Fatalf("verror should be a timeout not %s: stack %s", |
| err, verror.Stack(err)) |
| } |
| return |
| } |
| |
| for { |
| got := 0 |
| err := call.Recv(&got) |
| if err == nil { |
| if got != want { |
| t.Fatalf("got %d, want %d", got, want) |
| } |
| want++ |
| continue |
| } |
| // TOO(cnicolaou): this should be Timeout only. |
| testForVerror(t, err, verror.ErrTimeout) |
| break |
| } |
| err = call.Finish() |
| testForVerror(t, err, verror.ErrTimeout) |
| } |
| |
| func TestStreamAbort(t *testing.T) { |
| ctx, shutdown := newCtx() |
| defer shutdown() |
| name, fn := initServer(t, ctx) |
| defer fn() |
| |
| call, err := v23.GetClient(ctx).StartCall(ctx, name, "Sink", nil) |
| if err != nil { |
| t.Fatalf("unexpected error: %s", err) |
| } |
| |
| want := 10 |
| for i := 0; i <= want; i++ { |
| if err := call.Send(i); err != nil { |
| t.Fatalf("unexpected error: %s", err) |
| } |
| } |
| call.CloseSend() |
| err = call.Send(100) |
| testForVerror(t, err, verror.ErrAborted) |
| |
| result := 0 |
| err = call.Finish(&result) |
| if err != nil { |
| t.Errorf("unexpected error: %#v", err) |
| } |
| if got := result; got != want { |
| t.Errorf("got %d, want %d", got, want) |
| } |
| } |
| |
| func TestNoServersAvailable(t *testing.T) { |
| ctx, shutdown := newCtx() |
| defer shutdown() |
| _, fn := runMountTable(t, ctx) |
| defer fn() |
| name := "noservers" |
| call, err := v23.GetClient(ctx).StartCall(ctx, name, "Sleep", nil, options.NoRetry{}) |
| if err != nil { |
| testForVerror(t, err, verror.ErrNoServers) |
| return |
| } |
| err = call.Finish() |
| testForVerror(t, err, verror.ErrNoServers) |
| } |
| |
| func TestNoMountTable(t *testing.T) { |
| ctx, shutdown := newCtx() |
| defer shutdown() |
| v23.GetNamespace(ctx).SetRoots() |
| name := "a_mount_table_entry" |
| |
| // If there is no mount table, then we'll get a NoServers error message. |
| ctx, _ = context.WithTimeout(ctx, 300*time.Millisecond) |
| _, err := v23.GetClient(ctx).StartCall(ctx, name, "Sleep", nil) |
| testForVerror(t, err, verror.ErrNoServers) |
| } |
| |
| // TestReconnect verifies that the client transparently re-establishes the |
| // connection to the server if the server dies and comes back (on the same |
| // endpoint). |
| func TestReconnect(t *testing.T) { |
| t.Skip() |
| ctx, shutdown := test.InitForTest() |
| defer shutdown() |
| |
| sh, err := modules.NewShell(ctx, v23.GetPrincipal(ctx), testing.Verbose(), t) |
| if err != nil { |
| t.Fatalf("unexpected error: %s", err) |
| } |
| defer sh.Cleanup(os.Stderr, os.Stderr) |
| server, err := sh.Start("echoServer", nil, "--v23.tcp.address=127.0.0.1:0", "mymessage", "") |
| if err != nil { |
| t.Fatalf("unexpected error: %s", err) |
| } |
| server.ReadLine() |
| serverName := server.ExpectVar("NAME") |
| serverEP, _ := naming.SplitAddressName(serverName) |
| ep, _ := inaming.NewEndpoint(serverEP) |
| |
| makeCall := func(ctx *context.T, opts ...rpc.CallOpt) (string, error) { |
| ctx, _ = context.WithDeadline(ctx, time.Now().Add(10*time.Second)) |
| call, err := v23.GetClient(ctx).StartCall(ctx, serverName, "Echo", []interface{}{"bratman"}, opts...) |
| if err != nil { |
| return "", fmt.Errorf("START: %s", err) |
| } |
| var result string |
| if err := call.Finish(&result); err != nil { |
| return "", err |
| } |
| return result, nil |
| } |
| |
| expected := "mymessage: bratman\n" |
| if result, err := makeCall(ctx); err != nil || result != expected { |
| t.Errorf("Got (%q, %v) want (%q, nil)", result, err, expected) |
| } |
| // Kill the server, verify client can't talk to it anymore. |
| if err := server.Shutdown(os.Stderr, os.Stderr); err != nil { |
| t.Fatalf("unexpected error: %s", err) |
| } |
| |
| if _, err := makeCall(ctx, options.NoRetry{}); err == nil || (!strings.HasPrefix(err.Error(), "START") && !strings.Contains(err.Error(), "EOF")) { |
| t.Fatalf(`Got (%v) want ("START: <err>" or "EOF") as server is down`, err) |
| } |
| |
| // Resurrect the server with the same address, verify client |
| // re-establishes the connection. This is racy if another |
| // process grabs the port. |
| server, err = sh.Start("echoServer", nil, "--v23.tcp.address="+ep.Address, "mymessage again", "") |
| if err != nil { |
| t.Fatalf("unexpected error: %s", err) |
| } |
| defer server.Shutdown(os.Stderr, os.Stderr) |
| expected = "mymessage again: bratman\n" |
| if result, err := makeCall(ctx); err != nil || result != expected { |
| t.Errorf("Got (%q, %v) want (%q, nil)", result, err, expected) |
| } |
| } |
| |
| func TestMethodErrors(t *testing.T) { |
| ctx, shutdown := newCtx() |
| defer shutdown() |
| clt := v23.GetClient(ctx) |
| |
| name, fn := initServer(t, ctx) |
| defer fn() |
| |
| logErr := func(msg string, err error) { |
| logErrors(t, msg, true, false, false, err) |
| } |
| |
| // Unknown method |
| call, err := clt.StartCall(ctx, name, "NoMethod", nil) |
| if err != nil { |
| t.Fatal(err) |
| } |
| verr := call.Finish() |
| if verror.ErrorID(verr) != verror.ErrUnknownMethod.ID { |
| t.Errorf("wrong error: %s", verr) |
| } |
| logErr("unknown method", verr) |
| |
| // Unknown suffix |
| call, err = clt.StartCall(ctx, name+"/NoSuffix", "Ping", nil) |
| if err != nil { |
| t.Fatal(err) |
| } |
| verr = call.Finish() |
| if verror.ErrorID(verr) != verror.ErrUnknownSuffix.ID { |
| t.Errorf("wrong error: %s", verr) |
| } |
| logErr("unknown suffix", verr) |
| |
| // Too many args. |
| call, err = clt.StartCall(ctx, name, "Ping", []interface{}{1, 2}) |
| if err != nil { |
| // We check for "failed to encode arg" here because sometimes the server detects the |
| // mismatched number of arguments, sends an error response, and closes the connection, |
| // before the client gets through encoding the args. In this case the flow is closed and |
| // encoding of args fails, preventing the client from calling call.Finish, and seeing |
| // the error in the response. In the normal case network time dominates, so this case |
| // will very rarely get hit, but since the client and server in this test are in the |
| // same process we see this race quite a bit. |
| if got, want := err.Error(), "failed to encode arg"; !strings.Contains(got, want) { |
| t.Errorf("want %q to contain %q", got, want) |
| } |
| logErr("too many args", err) |
| } else { |
| r1 := "" |
| verr = call.Finish(&r1) |
| if verror.ErrorID(verr) != verror.ErrBadProtocol.ID { |
| t.Errorf("wrong error: %s", verr) |
| } |
| if got, want := verr.Error(), "wrong number of input arguments"; !strings.Contains(got, want) { |
| t.Errorf("want %q to contain %q", got, want) |
| } |
| logErr("too many args", verr) |
| } |
| |
| // Too many results. |
| call, err = clt.StartCall(ctx, name, "Ping", nil) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| r1, r2 := "", "" |
| verr = call.Finish(&r1, &r2) |
| if verror.ErrorID(verr) != verror.ErrBadProtocol.ID { |
| t.Errorf("wrong error: %s", verr) |
| } |
| if got, want := verr.Error(), "results, but want"; !strings.Contains(got, want) { |
| t.Errorf("want %q to contain %q", got, want) |
| } |
| logErr("wrong # results", verr) |
| |
| // Mismatched arg types |
| call, err = clt.StartCall(ctx, name, "Echo", []interface{}{1}) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| verr = call.Finish(&r2) |
| if verror.ErrorID(verr) != verror.ErrBadProtocol.ID { |
| t.Errorf("wrong error: %s", verr) |
| } |
| if got, want := verr.Error(), "aren't compatible"; !strings.Contains(got, want) { |
| t.Errorf("want %q to contain %q", got, want) |
| } |
| logErr("wrong arg types", verr) |
| |
| // Mismatched result types |
| call, err = clt.StartCall(ctx, name, "Ping", nil) |
| if err != nil { |
| t.Fatal(err) |
| } |
| |
| r3 := 2 |
| verr = call.Finish(&r3) |
| if verror.ErrorID(verr) != verror.ErrBadProtocol.ID { |
| t.Errorf("wrong error: %s", verr) |
| } |
| if got, want := verr.Error(), "aren't compatible"; !strings.Contains(got, want) { |
| t.Errorf("want %q to contain %q", got, want) |
| } |
| logErr("wrong result types", verr) |
| } |
| |
| func TestReservedMethodErrors(t *testing.T) { |
| ctx, shutdown := newCtx() |
| defer shutdown() |
| clt := v23.GetClient(ctx) |
| |
| name, fn := initServer(t, ctx) |
| defer fn() |
| |
| logErr := func(msg string, err error) { |
| logErrors(t, msg, true, false, false, err) |
| } |
| |
| // This call will fail because the __xx suffix is not supported by |
| // the dispatcher implementing Signature. |
| call, err := clt.StartCall(ctx, name+"/__xx", "__Signature", nil) |
| if err != nil { |
| t.Fatal(err) |
| } |
| sig := []signature.Interface{} |
| verr := call.Finish(&sig) |
| if verror.ErrorID(verr) != verror.ErrUnknownSuffix.ID { |
| t.Fatalf("wrong error: %s", verr) |
| } |
| logErr("unknown suffix", verr) |
| |
| // This call will fail for the same reason, but with a different error, |
| // saying that MethodSignature is an unknown method. |
| call, err = clt.StartCall(ctx, name+"/__xx", "__MethodSignature", []interface{}{"dummy"}) |
| if err != nil { |
| t.Fatal(err) |
| } |
| verr = call.Finish(&sig) |
| if verror.ErrorID(verr) != verror.ErrUnknownMethod.ID { |
| t.Fatalf("wrong error: %s", verr) |
| } |
| logErr("unknown method", verr) |
| } |