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  }