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  }