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