Merge lp:~jameinel/gouda/test-create into lp:gouda

Proposed by John A Meinel
Status: Merged
Approved by: Eric Casteleijn
Approved revision: 34
Merged at revision: 24
Proposed branch: lp:~jameinel/gouda/test-create
Merge into: lp:gouda
Diff against target: 159 lines (+112/-7)
2 files modified
test_serv/db_manager.go (+65/-5)
test_serv/db_manager_test.go (+47/-2)
To merge this branch: bzr merge lp:~jameinel/gouda/test-create
Reviewer Review Type Date Requested Status
Eric Casteleijn Approve
Review via email: mp+131647@code.launchpad.net

Description of the change

Get a reflection system working, and use it to expose CreateDoc.

This is a bit of black magic, but it should be pretty stable, and uses JSON's Unmarshal magic. So while we use reflect to work out what types things should be, we don't have to do the actually JSON <=> type decoding.

To post a comment you must log in.
Revision history for this message
Eric Casteleijn (thisfred) wrote :

exciting stuff!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'test_serv/db_manager.go'
2--- test_serv/db_manager.go 2012-10-26 09:24:54 +0000
3+++ test_serv/db_manager.go 2012-10-26 16:00:30 +0000
4@@ -1,8 +1,10 @@
5 package main
6
7 import (
8+ "encoding/json"
9 "fmt"
10 "launchpad.net/gouda/u1db"
11+ "reflect"
12 )
13
14 type DBManager struct {
15@@ -35,13 +37,71 @@
16 type OpArgs struct {
17 Path string // Path to the DBManager
18 Op string // Operation to perform
19- Args string // JSON string of arguments to pass to Op
20-}
21-
22-func (dbm *DBManager) Op(args OpArgs, reply *string) error {
23- _, ok := dbm.dbs[args.Path]
24+ Args []byte // JSON string of arguments to pass to Op
25+}
26+
27+// Decode the array passed over the wire, and Unmarshal it into types that
28+// match the function signature. This returns a []reflect.Value array that is
29+// suitable for passing to Method.Call()
30+func decodeArgumentArray(funcType reflect.Type, rawJSON []byte) ([]reflect.Value, error) {
31+ num_wanted_args := funcType.NumIn()
32+ // First we use json.Unmarshal to pull out each argument into a
33+ // separate string. JSON already knows how to unmarshall into real
34+ // types
35+ var rawJSONBits = make([]json.RawMessage, 0, num_wanted_args)
36+ if err := json.Unmarshal(rawJSON, &rawJSONBits); err != nil {
37+ return nil, fmt.Errorf("Error decoding arguments: %v", err)
38+ }
39+ if len(rawJSONBits) != num_wanted_args {
40+ return nil, fmt.Errorf("Incorrect number of arguments. Wanted %d got %d",
41+ num_wanted_args, len(rawJSONBits))
42+ }
43+ reflect_args := make([]reflect.Value, num_wanted_args)
44+ // unmarshall each individual arg into its matching type
45+ for i := 0; i < num_wanted_args; i++ {
46+ // Create a Value to store the i-th argument
47+ v := reflect.New(funcType.In(i))
48+ err := json.Unmarshal(rawJSONBits[i], v.Interface())
49+ reflect_args[i] = v.Elem()
50+ if err != nil {
51+ return nil, fmt.Errorf("Failed to encode result: %v", err)
52+ }
53+ }
54+ return reflect_args, nil
55+}
56+
57+func callAndSerializeResult(method reflect.Value, args []reflect.Value) ([]byte, error) {
58+ // reflect.Value.Call returns an array of Value objects, so we turn
59+ // that back into the real instances, and use json.Marshal to turn it
60+ // into a response string.
61+ result := method.Call(args)
62+ realResult := make([]interface{}, len(result))
63+ for i, res := range result {
64+ realResult[i] = res.Interface()
65+ }
66+ reply, err := json.Marshal(realResult)
67+ if err != nil {
68+ return nil, fmt.Errorf("Failed to encode result: %v", err)
69+ }
70+ return reply, nil
71+}
72+
73+func (dbm *DBManager) Op(args *OpArgs, reply *[]byte) error {
74+ db, ok := dbm.dbs[args.Path]
75 if !ok {
76 return fmt.Errorf("No database at path: %s", args.Path)
77 }
78+ v := reflect.ValueOf(db)
79+ typeMethod, ok := v.Type().MethodByName(args.Op)
80+ if !ok {
81+ return fmt.Errorf("No method: %s", args.Op)
82+ }
83+ method := v.Method(typeMethod.Index)
84+ reflectedArgs, err := decodeArgumentArray(method.Type(), args.Args)
85+ if err != nil {
86+ return err
87+ }
88+ Logf("Decoded: %+v", reflectedArgs)
89+ *reply, err = callAndSerializeResult(method, reflectedArgs)
90 return nil
91 }
92
93=== modified file 'test_serv/db_manager_test.go'
94--- test_serv/db_manager_test.go 2012-10-26 09:57:39 +0000
95+++ test_serv/db_manager_test.go 2012-10-26 16:00:30 +0000
96@@ -1,7 +1,9 @@
97 package main
98
99 import (
100+ "encoding/json"
101 . "launchpad.net/gocheck"
102+ "launchpad.net/gouda/u1db"
103 )
104
105 type TestDBManager struct {
106@@ -20,8 +22,51 @@
107 s.LoggingFixture.TearDown(c)
108 }
109
110+func (s *TestDBManager) CreateADB(c *C) {
111+ var reply string
112+ err := s.dbm.Create(&CreateArgs{Path: "a", Type: "memory", ReplicaUID: "uuid"}, &reply)
113+ c.Assert(err, IsNil)
114+ c.Assert(reply, Equals, "success")
115+}
116+
117 func (s *TestDBManager) TestRunOpWithoutDB(c *C) {
118- var reply string
119- err := s.dbm.Op(OpArgs{Path: "no-such-path"}, &reply)
120+ var reply []byte
121+ err := s.dbm.Op(&OpArgs{Path: "no-such-path"}, &reply)
122 c.Check(err, ErrorMatches, "No database at path: no-such-path")
123 }
124+
125+func (s *TestDBManager) TestRunInvalidOp(c *C) {
126+ var reply []byte
127+ s.CreateADB(c)
128+ invalid_op := OpArgs{Path: "a", Op: "NoSuchOperation"}
129+ err := s.dbm.Op(&invalid_op, &reply)
130+ c.Assert(err, NotNil)
131+ c.Assert(err, ErrorMatches, "No method: NoSuchOperation")
132+}
133+
134+func (s *TestDBManager) TestCreateDocInvalidNArgs(c *C) {
135+ var reply []byte
136+ s.CreateADB(c)
137+ create_doc_op := OpArgs{Path: "a", Op: "CreateDoc", Args: []byte("[]")}
138+ err := s.dbm.Op(&create_doc_op, &reply)
139+ c.Assert(err, ErrorMatches, "Incorrect number of arguments. Wanted 2 got 0")
140+}
141+
142+func (s *TestDBManager) TestCreateDoc(c *C) {
143+ var reply []byte
144+ s.CreateADB(c)
145+ create_doc_op := OpArgs{Path: "a", Op: "CreateDoc", Args: []byte(`[{}, null]`)}
146+ err := s.dbm.Op(&create_doc_op, &reply)
147+ c.Assert(err, IsNil)
148+ var response []json.RawMessage
149+ err = json.Unmarshal([]byte(reply), &response)
150+ // The second value is the 'error' from the function
151+ c.Assert(response, HasLen, 2)
152+ c.Assert(string(response[1]), Equals, "null")
153+ var doc u1db.Document
154+ err = json.Unmarshal(response[0], &doc)
155+ c.Assert(err, IsNil)
156+ c.Assert(doc.Id, NotNil)
157+ c.Assert(doc.Rev, NotNil)
158+ c.Assert(doc.HasConflicts, Equals, false)
159+}

Subscribers

People subscribed via source and target branches

to all changes: