github.com/klaytn/klaytn@v1.10.2/networks/rpc/client_test.go (about)

     1  // Modifications Copyright 2018 The klaytn Authors
     2  // Copyright 2016 The go-ethereum Authors
     3  // This file is part of the go-ethereum library.
     4  //
     5  // The go-ethereum library is free software: you can redistribute it and/or modify
     6  // it under the terms of the GNU Lesser General Public License as published by
     7  // the Free Software Foundation, either version 3 of the License, or
     8  // (at your option) any later version.
     9  //
    10  // The go-ethereum library is distributed in the hope that it will be useful,
    11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    13  // GNU Lesser General Public License for more details.
    14  //
    15  // You should have received a copy of the GNU Lesser General Public License
    16  // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
    17  //
    18  // This file is derived from rpc/client_test.go (2018/06/04).
    19  // Modified and improved for the klaytn development.
    20  
    21  package rpc
    22  
    23  import (
    24  	"context"
    25  	"encoding/binary"
    26  	"fmt"
    27  	"math/rand"
    28  	"net"
    29  	"net/http"
    30  	"net/http/httptest"
    31  	"os"
    32  	"reflect"
    33  	"runtime"
    34  	"strings"
    35  	"sync"
    36  	"testing"
    37  	"time"
    38  
    39  	"github.com/davecgh/go-spew/spew"
    40  )
    41  
    42  func TestClientRequest(t *testing.T) {
    43  	server := newTestServer("service", new(Service))
    44  	defer server.Stop()
    45  	client := DialInProc(server)
    46  	defer client.Close()
    47  
    48  	var resp Result
    49  	if err := client.Call(&resp, "service_echo", "hello", 10, &Args{"world"}); err != nil {
    50  		t.Fatal(err)
    51  	}
    52  	if !reflect.DeepEqual(resp, Result{"hello", 10, &Args{"world"}}) {
    53  		t.Errorf("incorrect result %#v", resp)
    54  	}
    55  }
    56  
    57  func TestClientBatchRequest(t *testing.T) {
    58  	server := newTestServer("service", new(Service))
    59  	defer server.Stop()
    60  	client := DialInProc(server)
    61  	defer client.Close()
    62  
    63  	batch := []BatchElem{
    64  		{
    65  			Method: "service_echo",
    66  			Args:   []interface{}{"hello", 10, &Args{"world"}},
    67  			Result: new(Result),
    68  		},
    69  		{
    70  			Method: "service_echo",
    71  			Args:   []interface{}{"hello2", 11, &Args{"world"}},
    72  			Result: new(Result),
    73  		},
    74  		{
    75  			Method: "no_such_method",
    76  			Args:   []interface{}{1, 2, 3},
    77  			Result: new(int),
    78  		},
    79  	}
    80  	if err := client.BatchCall(batch); err != nil {
    81  		t.Fatal(err)
    82  	}
    83  	wantResult := []BatchElem{
    84  		{
    85  			Method: "service_echo",
    86  			Args:   []interface{}{"hello", 10, &Args{"world"}},
    87  			Result: &Result{"hello", 10, &Args{"world"}},
    88  		},
    89  		{
    90  			Method: "service_echo",
    91  			Args:   []interface{}{"hello2", 11, &Args{"world"}},
    92  			Result: &Result{"hello2", 11, &Args{"world"}},
    93  		},
    94  		{
    95  			Method: "no_such_method",
    96  			Args:   []interface{}{1, 2, 3},
    97  			Result: new(int),
    98  			Error:  &jsonError{Code: -32601, Message: "the method no_such_method does not exist/is not available"},
    99  		},
   100  	}
   101  	if !reflect.DeepEqual(batch, wantResult) {
   102  		t.Errorf("batch results mismatch:\ngot %swant %s", spew.Sdump(batch), spew.Sdump(wantResult))
   103  	}
   104  }
   105  
   106  func TestClientNotify(t *testing.T) {
   107  	server := newTestServer("service", new(Service))
   108  	defer server.Stop()
   109  	client := DialInProc(server)
   110  	defer client.Close()
   111  
   112  	if err := client.Notify(context.Background(), "service_echo", "hello", 10, &Args{"world"}); err != nil {
   113  		t.Fatal(err)
   114  	}
   115  }
   116  
   117  // func TestClientCancelInproc(t *testing.T)    { testClientCancel("inproc", t) }
   118  func TestClientCancelWebsocket(t *testing.T) { testClientCancel("ws", t) }
   119  func TestClientCancelHTTP(t *testing.T)      { testClientCancel("http", t) }
   120  func TestClientCancelIPC(t *testing.T)       { testClientCancel("ipc", t) }
   121  
   122  // This test checks that requests made through CallContext can be canceled by canceling
   123  // the context.
   124  func testClientCancel(transport string, t *testing.T) {
   125  	server := newTestServer("service", new(Service))
   126  	defer server.Stop()
   127  
   128  	// What we want to achieve is that the context gets canceled
   129  	// at various stages of request processing. The interesting cases
   130  	// are:
   131  	//  - cancel during dial
   132  	//  - cancel while performing a HTTP request
   133  	//  - cancel while waiting for a response
   134  	//
   135  	// To trigger those, the times are chosen such that connections
   136  	// are killed within the deadline for every other call (maxKillTimeout
   137  	// is 2x maxCancelTimeout).
   138  	//
   139  	// Once a connection is dead, there is a fair chance it won't connect
   140  	// successfully because the accept is delayed by 1s.
   141  	maxContextCancelTimeout := 300 * time.Millisecond
   142  	fl := &flakeyListener{
   143  		maxAcceptDelay: 1 * time.Second,
   144  		maxKillTimeout: 600 * time.Millisecond,
   145  	}
   146  
   147  	var client *Client
   148  	switch transport {
   149  	case "ws", "http":
   150  		c, hs := httpTestClient(server, transport, fl)
   151  		defer hs.Close()
   152  		client = c
   153  	case "ipc":
   154  		c, l := ipcTestClient(server, fl)
   155  		defer l.Close()
   156  		client = c
   157  	default:
   158  		panic("unknown transport: " + transport)
   159  	}
   160  
   161  	// These tests take a lot of time, run them all at once.
   162  	// You probably want to run with -parallel 1 or comment out
   163  	// the call to t.Parallel if you enable the logging.
   164  	// t.Parallel()
   165  
   166  	// The actual test starts here.
   167  	var (
   168  		wg       sync.WaitGroup
   169  		nreqs    = 10
   170  		ncallers = 6
   171  	)
   172  	caller := func(index int) {
   173  		defer wg.Done()
   174  		for i := 0; i < nreqs; i++ {
   175  			var (
   176  				ctx     context.Context
   177  				cancel  func()
   178  				timeout = time.Duration(rand.Int63n(int64(maxContextCancelTimeout)))
   179  			)
   180  			if index < ncallers/2 {
   181  				// For half of the callers, create a context without deadline
   182  				// and cancel it later.
   183  				ctx, cancel = context.WithCancel(context.Background())
   184  				time.AfterFunc(timeout, cancel)
   185  			} else {
   186  				// For the other half, create a context with a deadline instead. This is
   187  				// different because the context deadline is used to set the socket write
   188  				// deadline.
   189  				ctx, cancel = context.WithTimeout(context.Background(), timeout)
   190  			}
   191  			// Now perform a call with the context.
   192  			// The key thing here is that no call will ever complete successfully.
   193  			err := client.CallContext(ctx, nil, "service_sleep", 2*maxContextCancelTimeout)
   194  			if err != nil {
   195  				logger.Debug(fmt.Sprint("got expected error:", err))
   196  			} else {
   197  				t.Errorf("no error for call with %v wait time", timeout)
   198  			}
   199  			cancel()
   200  		}
   201  	}
   202  	wg.Add(ncallers)
   203  	for i := 0; i < ncallers; i++ {
   204  		go caller(i)
   205  	}
   206  	wg.Wait()
   207  }
   208  
   209  func TestClientSubscribeInvalidArg(t *testing.T) {
   210  	server := newTestServer("service", new(Service))
   211  	defer server.Stop()
   212  	client := DialInProc(server)
   213  	defer client.Close()
   214  
   215  	check := func(shouldPanic bool, arg interface{}) {
   216  		defer func() {
   217  			err := recover()
   218  			if shouldPanic && err == nil {
   219  				t.Errorf("KlaySubscribe should've panicked for %#v", arg)
   220  			}
   221  			if !shouldPanic && err != nil {
   222  				t.Errorf("KlaySubscribe shouldn't have panicked for %#v", arg)
   223  				buf := make([]byte, 1024*1024)
   224  				buf = buf[:runtime.Stack(buf, false)]
   225  				t.Error(err)
   226  				t.Error(string(buf))
   227  			}
   228  		}()
   229  		client.KlaySubscribe(context.Background(), arg, "foo_bar")
   230  	}
   231  	check(true, nil)
   232  	check(true, 1)
   233  	check(true, (chan int)(nil))
   234  	check(true, make(<-chan int))
   235  	check(false, make(chan int))
   236  	check(false, make(chan<- int))
   237  }
   238  
   239  func TestClientSubscribe(t *testing.T) {
   240  	server := newTestServer("klay", new(NotificationTestService))
   241  	defer server.Stop()
   242  	client := DialInProc(server)
   243  	defer client.Close()
   244  
   245  	nc := make(chan int)
   246  	count := 10
   247  	sub, err := client.KlaySubscribe(context.Background(), nc, "someSubscription", count, 0)
   248  	if err != nil {
   249  		t.Fatal("can't subscribe:", err)
   250  	}
   251  	for i := 0; i < count; i++ {
   252  		if val := <-nc; val != i {
   253  			t.Fatalf("value mismatch: got %d, want %d", val, i)
   254  		}
   255  	}
   256  
   257  	sub.Unsubscribe()
   258  	select {
   259  	case v := <-nc:
   260  		t.Fatal("received value after unsubscribe:", v)
   261  	case err := <-sub.Err():
   262  		if err != nil {
   263  			t.Fatalf("Err returned a non-nil error after explicit unsubscribe: %q", err)
   264  		}
   265  	case <-time.After(1 * time.Second):
   266  		t.Fatalf("subscription not closed within 1s after unsubscribe")
   267  	}
   268  }
   269  
   270  func TestClientSubscribeCustomNamespace(t *testing.T) {
   271  	namespace := "custom"
   272  	server := newTestServer(namespace, new(NotificationTestService))
   273  	defer server.Stop()
   274  	client := DialInProc(server)
   275  	defer client.Close()
   276  
   277  	nc := make(chan int)
   278  	count := 10
   279  	sub, err := client.Subscribe(context.Background(), namespace, nc, "someSubscription", count, 0)
   280  	if err != nil {
   281  		t.Fatal("can't subscribe:", err)
   282  	}
   283  	for i := 0; i < count; i++ {
   284  		if val := <-nc; val != i {
   285  			t.Fatalf("value mismatch: got %d, want %d", val, i)
   286  		}
   287  	}
   288  
   289  	sub.Unsubscribe()
   290  	select {
   291  	case v := <-nc:
   292  		t.Fatal("received value after unsubscribe:", v)
   293  	case err := <-sub.Err():
   294  		if err != nil {
   295  			t.Fatalf("Err returned a non-nil error after explicit unsubscribe: %q", err)
   296  		}
   297  	case <-time.After(1 * time.Second):
   298  		t.Fatalf("subscription not closed within 1s after unsubscribe")
   299  	}
   300  }
   301  
   302  // In this test, the connection drops while KlaySubscribe is
   303  // waiting for a response.
   304  func TestClientSubscribeClose(t *testing.T) {
   305  	service := &NotificationTestService{
   306  		gotHangSubscriptionReq:  make(chan struct{}),
   307  		unblockHangSubscription: make(chan struct{}),
   308  	}
   309  	server := newTestServer("klay", service)
   310  	defer server.Stop()
   311  	client := DialInProc(server)
   312  	defer client.Close()
   313  
   314  	var (
   315  		nc   = make(chan int)
   316  		errc = make(chan error)
   317  		sub  *ClientSubscription
   318  		err  error
   319  	)
   320  	go func() {
   321  		sub, err = client.KlaySubscribe(context.Background(), nc, "hangSubscription", 999)
   322  		errc <- err
   323  	}()
   324  
   325  	<-service.gotHangSubscriptionReq
   326  	client.Close()
   327  	service.unblockHangSubscription <- struct{}{}
   328  
   329  	select {
   330  	case err := <-errc:
   331  		if err == nil {
   332  			t.Errorf("KlaySubscribe returned nil error after Close")
   333  		}
   334  		if sub != nil {
   335  			t.Error("KlaySubscribe returned non-nil subscription after Close")
   336  		}
   337  	case <-time.After(1 * time.Second):
   338  		t.Fatalf("KlaySubscribe did not return within 1s after Close")
   339  	}
   340  }
   341  
   342  /* TODO-Klaytn-FailedTest Test fails
   343  // This test reproduces https://github.com/ethereum/go-ethereum/issues/17837 where the
   344  // client hangs during shutdown when Unsubscribe races with Client.Close.
   345  func TestClientCloseUnsubscribeRace(t *testing.T) {
   346  	service := &NotificationTestService{}
   347  	server := newTestServer("klay", service)
   348  	defer server.Stop()
   349  
   350  	for i := 0; i < 20; i++ {
   351  		client := DialInProc(server)
   352  		nc := make(chan int)
   353  		sub, err := client.KlaySubscribe(context.Background(), nc, "someSubscription", 3, 1)
   354  		if err != nil {
   355  			t.Fatal(err)
   356  		}
   357  		go client.Close()
   358  		go sub.Unsubscribe()
   359  		select {
   360  		case <-sub.Err():
   361  		case <-time.After(5 * time.Second):
   362  			t.Fatal("subscription not closed within timeout")
   363  		}
   364  	}
   365  }
   366  */
   367  
   368  // This test checks that Client doesn't lock up when a single subscriber
   369  // doesn't read subscription events.
   370  func TestClientNotificationStorm(t *testing.T) {
   371  	server := newTestServer("klay", new(NotificationTestService))
   372  	defer server.Stop()
   373  
   374  	doTest := func(count int, wantError bool) {
   375  		client := DialInProc(server)
   376  		defer client.Close()
   377  		ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
   378  		defer cancel()
   379  
   380  		// Subscribe on the server. It will start sending many notifications
   381  		// very quickly.
   382  		nc := make(chan int)
   383  		sub, err := client.KlaySubscribe(ctx, nc, "someSubscription", count, 0)
   384  		if err != nil {
   385  			t.Fatal("can't subscribe:", err)
   386  		}
   387  		defer sub.Unsubscribe()
   388  
   389  		// Process each notification, try to run a call in between each of them.
   390  		for i := 0; i < count; i++ {
   391  			select {
   392  			case val := <-nc:
   393  				if val != i {
   394  					t.Fatalf("(%d/%d) unexpected value %d", i, count, val)
   395  				}
   396  			case err := <-sub.Err():
   397  				if wantError && err != ErrSubscriptionQueueOverflow {
   398  					t.Fatalf("(%d/%d) got error %q, want %q", i, count, err, ErrSubscriptionQueueOverflow)
   399  				} else if !wantError {
   400  					t.Fatalf("(%d/%d) got unexpected error %q", i, count, err)
   401  				}
   402  				return
   403  			}
   404  			var r int
   405  			err := client.CallContext(ctx, &r, "klay_echo", i)
   406  			if err != nil {
   407  				if !wantError {
   408  					t.Fatalf("(%d/%d) call error: %v", i, count, err)
   409  				}
   410  				return
   411  			}
   412  		}
   413  	}
   414  
   415  	doTest(8000, false)
   416  	doTest(10000, true)
   417  }
   418  
   419  func TestClientSetHeader(t *testing.T) {
   420  	var gotHeader bool
   421  	srv := newTestServer("service", new(Service))
   422  	httpsrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   423  		if r.Header.Get("test") == "ok" {
   424  			gotHeader = true
   425  		}
   426  		srv.ServeHTTP(w, r)
   427  	}))
   428  	defer httpsrv.Close()
   429  	defer srv.Stop()
   430  
   431  	client, err := Dial(httpsrv.URL)
   432  	if err != nil {
   433  		t.Fatal(err)
   434  	}
   435  	defer client.Close()
   436  
   437  	client.SetHeader("test", "ok")
   438  	if _, err := client.SupportedModules(); err != nil {
   439  		t.Fatal(err)
   440  	}
   441  	if !gotHeader {
   442  		t.Fatal("client did not set custom header")
   443  	}
   444  
   445  	// Check that Content-Type can be replaced.
   446  	client.SetHeader("content-type", "application/x-garbage")
   447  	_, err = client.SupportedModules()
   448  	if err == nil {
   449  		t.Fatal("no error for invalid content-type header")
   450  	} else if !strings.Contains(err.Error(), "Unsupported Media Type") {
   451  		t.Fatalf("error is not related to content-type: %q", err)
   452  	}
   453  }
   454  
   455  func TestClientHTTP(t *testing.T) {
   456  	server := newTestServer("service", new(Service))
   457  	defer server.Stop()
   458  
   459  	client, hs := httpTestClient(server, "http", nil)
   460  	defer hs.Close()
   461  	defer client.Close()
   462  
   463  	// Launch concurrent requests.
   464  	var (
   465  		results    = make([]Result, 100)
   466  		errc       = make(chan error)
   467  		wantResult = Result{"a", 1, new(Args)}
   468  	)
   469  	defer client.Close()
   470  	for i := range results {
   471  		i := i
   472  		go func() {
   473  			errc <- client.Call(&results[i], "service_echo",
   474  				wantResult.String, wantResult.Int, wantResult.Args)
   475  		}()
   476  	}
   477  
   478  	// Wait for all of them to complete.
   479  	timeout := time.NewTimer(5 * time.Second)
   480  	defer timeout.Stop()
   481  	for i := range results {
   482  		select {
   483  		case err := <-errc:
   484  			if err != nil {
   485  				t.Fatal(err)
   486  			}
   487  		case <-timeout.C:
   488  			t.Fatalf("timeout (got %d/%d) results)", i+1, len(results))
   489  		}
   490  	}
   491  
   492  	// Check results.
   493  	for i := range results {
   494  		if !reflect.DeepEqual(results[i], wantResult) {
   495  			t.Errorf("result %d mismatch: got %#v, want %#v", i, results[i], wantResult)
   496  		}
   497  	}
   498  }
   499  
   500  func TestClientReconnect(t *testing.T) {
   501  	startServer := func(addr string) (*Server, net.Listener) {
   502  		srv := newTestServer("service", new(Service))
   503  		l, err := net.Listen("tcp", addr)
   504  		if err != nil {
   505  			t.Fatal(err)
   506  		}
   507  		go http.Serve(l, srv.WebsocketHandler([]string{"*"}))
   508  		return srv, l
   509  	}
   510  
   511  	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
   512  	defer cancel()
   513  
   514  	// Start a server and corresponding client.
   515  	s1, l1 := startServer("127.0.0.1:0")
   516  	client, err := DialContext(ctx, "ws://"+l1.Addr().String())
   517  	if err != nil {
   518  		t.Fatal("can't dial", err)
   519  	}
   520  
   521  	// Perform a call. This should work because the server is up.
   522  	var resp Result
   523  	if err := client.CallContext(ctx, &resp, "service_echo", "", 1, nil); err != nil {
   524  		t.Fatal(err)
   525  	}
   526  
   527  	// Shut down the server and try calling again. It shouldn't work.
   528  	l1.Close()
   529  	s1.Stop()
   530  	if err := client.CallContext(ctx, &resp, "service_echo", "", 2, nil); err == nil {
   531  		t.Error("successful call while the server is down")
   532  		t.Logf("resp: %#v", resp)
   533  	}
   534  
   535  	// Allow for some cool down time so we can listen on the same address again.
   536  	time.Sleep(2 * time.Second)
   537  
   538  	// Start it up again and call again. The connection should be reestablished.
   539  	// We spawn multiple calls here to check whether this hangs somehow.
   540  	s2, l2 := startServer(l1.Addr().String())
   541  	defer l2.Close()
   542  	defer s2.Stop()
   543  
   544  	start := make(chan struct{})
   545  	errors := make(chan error, 20)
   546  	for i := 0; i < cap(errors); i++ {
   547  		go func() {
   548  			<-start
   549  			var resp Result
   550  			errors <- client.CallContext(ctx, &resp, "service_echo", "", 3, nil)
   551  		}()
   552  	}
   553  	close(start)
   554  	errcount := 0
   555  	for i := 0; i < cap(errors); i++ {
   556  		if err = <-errors; err != nil {
   557  			errcount++
   558  		}
   559  	}
   560  	if errcount > 1 {
   561  		t.Errorf("expected one error after disconnect, got %d", errcount)
   562  	}
   563  }
   564  
   565  func newTestServer(serviceName string, service interface{}) *Server {
   566  	server := NewServer()
   567  	server.idgen = sequentialIDGenerator()
   568  	if err := server.RegisterName(serviceName, service); err != nil {
   569  		panic(err)
   570  	}
   571  	return server
   572  }
   573  
   574  func sequentialIDGenerator() func() ID {
   575  	var (
   576  		mu      sync.Mutex
   577  		counter uint64
   578  	)
   579  	return func() ID {
   580  		mu.Lock()
   581  		defer mu.Unlock()
   582  		counter++
   583  		id := make([]byte, 8)
   584  		binary.BigEndian.PutUint64(id, counter)
   585  		return encodeID(id)
   586  	}
   587  }
   588  
   589  func httpTestClient(srv *Server, transport string, fl *flakeyListener) (*Client, *httptest.Server) {
   590  	// Create the HTTP server.
   591  	var hs *httptest.Server
   592  	switch transport {
   593  	case "ws":
   594  		hs = httptest.NewUnstartedServer(srv.WebsocketHandler([]string{"*"}))
   595  	case "http":
   596  		hs = httptest.NewUnstartedServer(srv)
   597  	default:
   598  		panic("unknown HTTP transport: " + transport)
   599  	}
   600  	// Wrap the listener if required.
   601  	if fl != nil {
   602  		fl.Listener = hs.Listener
   603  		hs.Listener = fl
   604  	}
   605  	// Connect the client.
   606  	hs.Start()
   607  	client, err := Dial(transport + "://" + hs.Listener.Addr().String())
   608  	if err != nil {
   609  		panic(err)
   610  	}
   611  	return client, hs
   612  }
   613  
   614  func ipcTestClient(srv *Server, fl *flakeyListener) (*Client, net.Listener) {
   615  	// Listen on a random endpoint.
   616  	endpoint := fmt.Sprintf("klaytn-test-ipc-%d-%d", os.Getpid(), rand.Int63())
   617  	if runtime.GOOS == "windows" {
   618  		endpoint = `\\.\pipe\` + endpoint
   619  	} else {
   620  		endpoint = os.TempDir() + "/" + endpoint
   621  	}
   622  	l, err := ipcListen(endpoint)
   623  	if err != nil {
   624  		panic(err)
   625  	}
   626  	// Connect the listener to the server.
   627  	if fl != nil {
   628  		fl.Listener = l
   629  		l = fl
   630  	}
   631  	go srv.ServeListener(l)
   632  	// Connect the client.
   633  	client, err := Dial(endpoint)
   634  	if err != nil {
   635  		panic(err)
   636  	}
   637  	return client, l
   638  }
   639  
   640  // flakeyListener kills accepted connections after a random timeout.
   641  type flakeyListener struct {
   642  	net.Listener
   643  	maxKillTimeout time.Duration
   644  	maxAcceptDelay time.Duration
   645  }
   646  
   647  func (l *flakeyListener) Accept() (net.Conn, error) {
   648  	delay := time.Duration(rand.Int63n(int64(l.maxAcceptDelay)))
   649  	time.Sleep(delay)
   650  
   651  	c, err := l.Listener.Accept()
   652  	if err == nil {
   653  		timeout := time.Duration(rand.Int63n(int64(l.maxKillTimeout)))
   654  		time.AfterFunc(timeout, func() {
   655  			logger.Debug(fmt.Sprintf("killing conn %v after %v", c.LocalAddr(), timeout))
   656  			c.Close()
   657  		})
   658  	}
   659  	return c, err
   660  }
   661  
   662  // This test reproduces https://github.com/ethereum/go-ethereum/issues/17837 where the
   663  // client hangs during shutdown when Unsubscribe races with Client.Close.
   664  func TestClientCloseUnsubscribeRace(t *testing.T) {
   665  	service := &NotificationTestService{}
   666  	server := newTestServer("klay", service)
   667  	defer server.Stop()
   668  
   669  	for i := 0; i < 20; i++ {
   670  		client := DialInProc(server)
   671  		nc := make(chan int)
   672  		sub, err := client.KlaySubscribe(context.Background(), nc, "someSubscription", 3, 1)
   673  		if err != nil {
   674  			t.Fatal(err)
   675  		}
   676  		go client.Close()
   677  		go sub.Unsubscribe()
   678  		select {
   679  		case <-sub.Err():
   680  		case <-time.After(5 * time.Second):
   681  			t.Fatal("subscription not closed within timeout")
   682  		}
   683  	}
   684  }