github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/rpc/jsoncodec/codec_test.go (about) 1 package jsoncodec_test 2 3 import ( 4 "encoding/json" 5 "errors" 6 "io" 7 "reflect" 8 "regexp" 9 stdtesting "testing" 10 11 "github.com/juju/loggo" 12 "github.com/juju/testing" 13 jc "github.com/juju/testing/checkers" 14 gc "gopkg.in/check.v1" 15 16 "github.com/juju/juju/rpc" 17 "github.com/juju/juju/rpc/jsoncodec" 18 ) 19 20 type suite struct { 21 testing.LoggingSuite 22 } 23 24 var _ = gc.Suite(&suite{}) 25 26 func TestPackage(t *stdtesting.T) { 27 gc.TestingT(t) 28 } 29 30 type value struct { 31 X string 32 } 33 34 var readTests = []struct { 35 msg string 36 expectHdr rpc.Header 37 expectBody interface{} 38 }{{ 39 msg: `{"RequestId": 1, "Type": "foo", "Id": "id", "Request": "frob", "Params": {"X": "param"}}`, 40 expectHdr: rpc.Header{ 41 RequestId: 1, 42 Request: rpc.Request{ 43 Type: "foo", 44 Id: "id", 45 Action: "frob", 46 }, 47 }, 48 expectBody: &value{X: "param"}, 49 }, { 50 msg: `{"RequestId": 2, "Error": "an error", "ErrorCode": "a code"}`, 51 expectHdr: rpc.Header{ 52 RequestId: 2, 53 Error: "an error", 54 ErrorCode: "a code", 55 }, 56 expectBody: new(map[string]interface{}), 57 }, { 58 msg: `{"RequestId": 3, "Response": {"X": "result"}}`, 59 expectHdr: rpc.Header{ 60 RequestId: 3, 61 }, 62 expectBody: &value{X: "result"}, 63 }, { 64 msg: `{"RequestId": 4, "Type": "foo", "Version": 2, "Id": "id", "Request": "frob", "Params": {"X": "param"}}`, 65 expectHdr: rpc.Header{ 66 RequestId: 4, 67 Request: rpc.Request{ 68 Type: "foo", 69 Version: 2, 70 Id: "id", 71 Action: "frob", 72 }, 73 }, 74 expectBody: &value{X: "param"}, 75 }} 76 77 func (*suite) TestRead(c *gc.C) { 78 for i, test := range readTests { 79 c.Logf("test %d", i) 80 codec := jsoncodec.New(&testConn{ 81 readMsgs: []string{test.msg}, 82 }) 83 var hdr rpc.Header 84 err := codec.ReadHeader(&hdr) 85 c.Assert(err, jc.ErrorIsNil) 86 c.Assert(hdr, gc.DeepEquals, test.expectHdr) 87 88 c.Assert(hdr.IsRequest(), gc.Equals, test.expectHdr.IsRequest()) 89 90 body := reflect.New(reflect.ValueOf(test.expectBody).Type().Elem()).Interface() 91 err = codec.ReadBody(body, test.expectHdr.IsRequest()) 92 c.Assert(err, jc.ErrorIsNil) 93 c.Assert(body, gc.DeepEquals, test.expectBody) 94 95 err = codec.ReadHeader(&hdr) 96 c.Assert(err, gc.Equals, io.EOF) 97 } 98 } 99 100 func (*suite) TestReadHeaderLogsRequests(c *gc.C) { 101 codecLogger := loggo.GetLogger("juju.rpc.jsoncodec") 102 defer codecLogger.SetLogLevel(codecLogger.LogLevel()) 103 codecLogger.SetLogLevel(loggo.TRACE) 104 msg := `{"RequestId":1,"Type": "foo","Id": "id","Request":"frob","Params":{"X":"param"}}` 105 codec := jsoncodec.New(&testConn{ 106 readMsgs: []string{msg, msg, msg}, 107 }) 108 // Check that logging is off by default 109 var h rpc.Header 110 err := codec.ReadHeader(&h) 111 c.Assert(err, jc.ErrorIsNil) 112 c.Assert(c.GetTestLog(), gc.Matches, "") 113 114 // Check that we see a log message when we switch logging on. 115 codec.SetLogging(true) 116 err = codec.ReadHeader(&h) 117 c.Assert(err, jc.ErrorIsNil) 118 c.Assert(c.GetTestLog(), gc.Matches, ".*TRACE juju.rpc.jsoncodec <- "+regexp.QuoteMeta(msg)+`\n`) 119 120 // Check that we can switch it off again 121 codec.SetLogging(false) 122 err = codec.ReadHeader(&h) 123 c.Assert(err, jc.ErrorIsNil) 124 c.Assert(c.GetTestLog(), gc.Matches, ".*TRACE juju.rpc.jsoncodec <- "+regexp.QuoteMeta(msg)+`\n`) 125 } 126 127 func (*suite) TestWriteMessageLogsRequests(c *gc.C) { 128 codecLogger := loggo.GetLogger("juju.rpc.jsoncodec") 129 defer codecLogger.SetLogLevel(codecLogger.LogLevel()) 130 codecLogger.SetLogLevel(loggo.TRACE) 131 codec := jsoncodec.New(&testConn{}) 132 h := rpc.Header{ 133 RequestId: 1, 134 Request: rpc.Request{ 135 Type: "foo", 136 Id: "id", 137 Action: "frob", 138 }, 139 } 140 141 // Check that logging is off by default 142 err := codec.WriteMessage(&h, value{X: "param"}) 143 c.Assert(err, jc.ErrorIsNil) 144 c.Assert(c.GetTestLog(), gc.Matches, "") 145 146 // Check that we see a log message when we switch logging on. 147 codec.SetLogging(true) 148 err = codec.WriteMessage(&h, value{X: "param"}) 149 c.Assert(err, jc.ErrorIsNil) 150 msg := `{"RequestId":1,"Type":"foo","Id":"id","Request":"frob","Params":{"X":"param"}}` 151 c.Assert(c.GetTestLog(), gc.Matches, `.*TRACE juju.rpc.jsoncodec -> `+regexp.QuoteMeta(msg)+`\n`) 152 153 // Check that we can switch it off again 154 codec.SetLogging(false) 155 err = codec.WriteMessage(&h, value{X: "param"}) 156 c.Assert(err, jc.ErrorIsNil) 157 c.Assert(c.GetTestLog(), gc.Matches, `.*TRACE juju.rpc.jsoncodec -> `+regexp.QuoteMeta(msg)+`\n`) 158 } 159 160 func (*suite) TestConcurrentSetLoggingAndWrite(c *gc.C) { 161 // If log messages are not set atomically, this 162 // test will fail when run under the race detector. 163 codec := jsoncodec.New(&testConn{}) 164 done := make(chan struct{}) 165 go func() { 166 codec.SetLogging(true) 167 done <- struct{}{} 168 }() 169 h := rpc.Header{ 170 RequestId: 1, 171 Request: rpc.Request{ 172 Type: "foo", 173 Id: "id", 174 Action: "frob", 175 }, 176 } 177 err := codec.WriteMessage(&h, value{X: "param"}) 178 c.Assert(err, jc.ErrorIsNil) 179 <-done 180 } 181 182 func (*suite) TestConcurrentSetLoggingAndRead(c *gc.C) { 183 // If log messages are not set atomically, this 184 // test will fail when run under the race detector. 185 msg := `{"RequestId":1,"Type": "foo","Id": "id","Request":"frob","Params":{"X":"param"}}` 186 codec := jsoncodec.New(&testConn{ 187 readMsgs: []string{msg, msg, msg}, 188 }) 189 done := make(chan struct{}) 190 go func() { 191 codec.SetLogging(true) 192 done <- struct{}{} 193 }() 194 var h rpc.Header 195 err := codec.ReadHeader(&h) 196 c.Assert(err, jc.ErrorIsNil) 197 <-done 198 } 199 200 func (*suite) TestErrorAfterClose(c *gc.C) { 201 conn := &testConn{ 202 err: errors.New("some error"), 203 } 204 codec := jsoncodec.New(conn) 205 var hdr rpc.Header 206 err := codec.ReadHeader(&hdr) 207 c.Assert(err, gc.ErrorMatches, "error receiving message: some error") 208 209 err = codec.Close() 210 c.Assert(err, jc.ErrorIsNil) 211 c.Assert(conn.closed, jc.IsTrue) 212 213 err = codec.ReadHeader(&hdr) 214 c.Assert(err, gc.Equals, io.EOF) 215 } 216 217 var writeTests = []struct { 218 hdr *rpc.Header 219 body interface{} 220 isRequest bool 221 expect string 222 }{{ 223 hdr: &rpc.Header{ 224 RequestId: 1, 225 Request: rpc.Request{ 226 Type: "foo", 227 Id: "id", 228 Action: "frob", 229 }, 230 }, 231 body: &value{X: "param"}, 232 expect: `{"RequestId": 1, "Type": "foo","Id":"id", "Request": "frob", "Params": {"X": "param"}}`, 233 }, { 234 hdr: &rpc.Header{ 235 RequestId: 2, 236 Error: "an error", 237 ErrorCode: "a code", 238 }, 239 expect: `{"RequestId": 2, "Error": "an error", "ErrorCode": "a code"}`, 240 }, { 241 hdr: &rpc.Header{ 242 RequestId: 3, 243 }, 244 body: &value{X: "result"}, 245 expect: `{"RequestId": 3, "Response": {"X": "result"}}`, 246 }, { 247 hdr: &rpc.Header{ 248 RequestId: 4, 249 Request: rpc.Request{ 250 Type: "foo", 251 Version: 2, 252 Id: "", 253 Action: "frob", 254 }, 255 }, 256 body: &value{X: "param"}, 257 expect: `{"RequestId": 4, "Type": "foo", "Version": 2, "Request": "frob", "Params": {"X": "param"}}`, 258 }} 259 260 func (*suite) TestWrite(c *gc.C) { 261 for i, test := range writeTests { 262 c.Logf("test %d", i) 263 var conn testConn 264 codec := jsoncodec.New(&conn) 265 err := codec.WriteMessage(test.hdr, test.body) 266 c.Assert(err, jc.ErrorIsNil) 267 c.Assert(conn.writeMsgs, gc.HasLen, 1) 268 269 assertJSONEqual(c, conn.writeMsgs[0], test.expect) 270 } 271 } 272 273 var dumpRequestTests = []struct { 274 hdr rpc.Header 275 body interface{} 276 expect string 277 }{{ 278 hdr: rpc.Header{ 279 RequestId: 1, 280 Request: rpc.Request{ 281 Type: "Foo", 282 Id: "id", 283 Action: "Something", 284 }, 285 }, 286 body: struct{ Arg string }{Arg: "an arg"}, 287 expect: `{"RequestId":1,"Type":"Foo","Id":"id","Request":"Something","Params":{"Arg":"an arg"}}`, 288 }, { 289 hdr: rpc.Header{ 290 RequestId: 2, 291 }, 292 body: struct{ Ret string }{Ret: "return value"}, 293 expect: `{"RequestId":2,"Response":{"Ret":"return value"}}`, 294 }, { 295 hdr: rpc.Header{ 296 RequestId: 3, 297 }, 298 expect: `{"RequestId":3}`, 299 }, { 300 hdr: rpc.Header{ 301 RequestId: 4, 302 Error: "an error", 303 ErrorCode: "an error code", 304 }, 305 expect: `{"RequestId":4,"Error":"an error","ErrorCode":"an error code"}`, 306 }, { 307 hdr: rpc.Header{ 308 RequestId: 5, 309 }, 310 body: make(chan int), 311 expect: `"marshal error: json: unsupported type: chan int"`, 312 }, { 313 hdr: rpc.Header{ 314 RequestId: 1, 315 Request: rpc.Request{ 316 Type: "Foo", 317 Version: 2, 318 Id: "id", 319 Action: "Something", 320 }, 321 }, 322 body: struct{ Arg string }{Arg: "an arg"}, 323 expect: `{"RequestId":1,"Type":"Foo","Version":2,"Id":"id","Request":"Something","Params":{"Arg":"an arg"}}`, 324 }} 325 326 func (*suite) TestDumpRequest(c *gc.C) { 327 for i, test := range dumpRequestTests { 328 c.Logf("test %d; %#v", i, test.hdr) 329 data := jsoncodec.DumpRequest(&test.hdr, test.body) 330 c.Check(string(data), gc.Equals, test.expect) 331 } 332 } 333 334 // assertJSONEqual compares the json strings v0 335 // and v1 ignoring white space. 336 func assertJSONEqual(c *gc.C, v0, v1 string) { 337 var m0, m1 interface{} 338 err := json.Unmarshal([]byte(v0), &m0) 339 c.Assert(err, jc.ErrorIsNil) 340 err = json.Unmarshal([]byte(v1), &m1) 341 c.Assert(err, jc.ErrorIsNil) 342 data0, err := json.Marshal(m0) 343 c.Assert(err, jc.ErrorIsNil) 344 data1, err := json.Marshal(m1) 345 c.Assert(err, jc.ErrorIsNil) 346 c.Assert(string(data0), gc.Equals, string(data1)) 347 } 348 349 type testConn struct { 350 readMsgs []string 351 err error 352 writeMsgs []string 353 closed bool 354 } 355 356 func (c *testConn) Receive(msg interface{}) error { 357 if len(c.readMsgs) > 0 { 358 s := c.readMsgs[0] 359 c.readMsgs = c.readMsgs[1:] 360 return json.Unmarshal([]byte(s), msg) 361 } 362 if c.err != nil { 363 return c.err 364 } 365 return io.EOF 366 } 367 368 func (c *testConn) Send(msg interface{}) error { 369 data, err := json.Marshal(msg) 370 if err != nil { 371 return err 372 } 373 c.writeMsgs = append(c.writeMsgs, string(data)) 374 return nil 375 } 376 377 func (c *testConn) Close() error { 378 c.closed = true 379 return nil 380 }