github.com/core-coin/go-core/v2@v2.1.9/rpc/subscription_test.go (about) 1 // Copyright 2016 by the Authors 2 // This file is part of the go-core library. 3 // 4 // The go-core library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The go-core library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the go-core library. If not, see <http://www.gnu.org/licenses/>. 16 17 package rpc 18 19 import ( 20 "encoding/json" 21 "fmt" 22 "net" 23 "strings" 24 "testing" 25 "time" 26 ) 27 28 func TestNewID(t *testing.T) { 29 hexchars := "0123456789ABCDEFabcdef" 30 for i := 0; i < 100; i++ { 31 id := string(NewID()) 32 if !strings.HasPrefix(id, "0x") { 33 t.Fatalf("invalid ID prefix, want '0x...', got %s", id) 34 } 35 36 id = id[2:] 37 if len(id) == 0 || len(id) > 32 { 38 t.Fatalf("invalid ID length, want len(id) > 0 && len(id) <= 32), got %d", len(id)) 39 } 40 41 for i := 0; i < len(id); i++ { 42 if strings.IndexByte(hexchars, id[i]) == -1 { 43 t.Fatalf("unexpected byte, want any valid hex char, got %c", id[i]) 44 } 45 } 46 } 47 } 48 49 func TestSubscriptions(t *testing.T) { 50 var ( 51 namespaces = []string{"xcb", "bzz"} 52 service = ¬ificationTestService{} 53 subCount = len(namespaces) 54 notificationCount = 3 55 56 server = NewServer() 57 clientConn, serverConn = net.Pipe() 58 out = json.NewEncoder(clientConn) 59 in = json.NewDecoder(clientConn) 60 successes = make(chan subConfirmation) 61 notifications = make(chan subscriptionResult) 62 errors = make(chan error, subCount*notificationCount+1) 63 ) 64 65 // setup and start server 66 for _, namespace := range namespaces { 67 if err := server.RegisterName(namespace, service); err != nil { 68 t.Fatalf("unable to register test service %v", err) 69 } 70 } 71 go server.ServeCodec(NewCodec(serverConn), 0) 72 defer server.Stop() 73 74 // wait for message and write them to the given channels 75 go waitForMessages(in, successes, notifications, errors) 76 77 // create subscriptions one by one 78 for i, namespace := range namespaces { 79 request := map[string]interface{}{ 80 "id": i, 81 "method": fmt.Sprintf("%s_subscribe", namespace), 82 "version": "2.0", 83 "params": []interface{}{"someSubscription", notificationCount, i}, 84 } 85 if err := out.Encode(&request); err != nil { 86 t.Fatalf("Could not create subscription: %v", err) 87 } 88 } 89 90 timeout := time.After(30 * time.Second) 91 subids := make(map[string]string, subCount) 92 count := make(map[string]int, subCount) 93 allReceived := func() bool { 94 done := len(count) == subCount 95 for _, c := range count { 96 if c < notificationCount { 97 done = false 98 } 99 } 100 return done 101 } 102 for !allReceived() { 103 select { 104 case confirmation := <-successes: // subscription created 105 subids[namespaces[confirmation.reqid]] = string(confirmation.subid) 106 case notification := <-notifications: 107 count[notification.ID]++ 108 case err := <-errors: 109 t.Fatal(err) 110 case <-timeout: 111 for _, namespace := range namespaces { 112 subid, found := subids[namespace] 113 if !found { 114 t.Errorf("subscription for %q not created", namespace) 115 continue 116 } 117 if count, found := count[subid]; !found || count < notificationCount { 118 t.Errorf("didn't receive all notifications (%d<%d) in time for namespace %q", count, notificationCount, namespace) 119 } 120 } 121 t.Fatal("timed out") 122 } 123 } 124 } 125 126 // This test checks that unsubscribing works. 127 func TestServerUnsubscribe(t *testing.T) { 128 p1, p2 := net.Pipe() 129 defer p2.Close() 130 131 // Start the server. 132 server := newTestServer() 133 service := ¬ificationTestService{unsubscribed: make(chan string, 1)} 134 server.RegisterName("nftest2", service) 135 go server.ServeCodec(NewCodec(p1), 0) 136 137 // Subscribe. 138 p2.SetDeadline(time.Now().Add(10 * time.Second)) 139 p2.Write([]byte(`{"jsonrpc":"2.0","id":1,"method":"nftest2_subscribe","params":["someSubscription",0,10]}`)) 140 141 // Handle received messages. 142 var ( 143 resps = make(chan subConfirmation) 144 notifications = make(chan subscriptionResult) 145 errors = make(chan error, 1) 146 ) 147 go waitForMessages(json.NewDecoder(p2), resps, notifications, errors) 148 149 // Receive the subscription ID. 150 var sub subConfirmation 151 select { 152 case sub = <-resps: 153 case err := <-errors: 154 t.Fatal(err) 155 } 156 157 // Unsubscribe and check that it is handled on the server side. 158 p2.Write([]byte(`{"jsonrpc":"2.0","method":"nftest2_unsubscribe","params":["` + sub.subid + `"]}`)) 159 for { 160 select { 161 case id := <-service.unsubscribed: 162 if id != string(sub.subid) { 163 t.Errorf("wrong subscription ID unsubscribed") 164 } 165 return 166 case err := <-errors: 167 t.Fatal(err) 168 case <-notifications: 169 // drop notifications 170 } 171 } 172 } 173 174 type subConfirmation struct { 175 reqid int 176 subid ID 177 } 178 179 // waitForMessages reads RPC messages from 'in' and dispatches them into the given channels. 180 // It stops if there is an error. 181 func waitForMessages(in *json.Decoder, successes chan subConfirmation, notifications chan subscriptionResult, errors chan error) { 182 for { 183 resp, notification, err := readAndValidateMessage(in) 184 if err != nil { 185 errors <- err 186 return 187 } else if resp != nil { 188 successes <- *resp 189 } else { 190 notifications <- *notification 191 } 192 } 193 } 194 195 func readAndValidateMessage(in *json.Decoder) (*subConfirmation, *subscriptionResult, error) { 196 var msg jsonrpcMessage 197 if err := in.Decode(&msg); err != nil { 198 return nil, nil, fmt.Errorf("decode error: %v", err) 199 } 200 switch { 201 case msg.isNotification(): 202 var res subscriptionResult 203 if err := json.Unmarshal(msg.Params, &res); err != nil { 204 return nil, nil, fmt.Errorf("invalid subscription result: %v", err) 205 } 206 return nil, &res, nil 207 case msg.isResponse(): 208 var c subConfirmation 209 if msg.Error != nil { 210 return nil, nil, msg.Error 211 } else if err := json.Unmarshal(msg.Result, &c.subid); err != nil { 212 return nil, nil, fmt.Errorf("invalid response: %v", err) 213 } else { 214 json.Unmarshal(msg.ID, &c.reqid) 215 return &c, nil, nil 216 } 217 default: 218 return nil, nil, fmt.Errorf("unrecognized message: %v", msg) 219 } 220 }