Skip to content

Commit 9c24dfe

Browse files
committed
Returning keyword names
Non-argument calls return value, argument calls doesn't Dynamic importing of library Example for dynamic import created Replacing gorilla-xmlrpc with forked version to get arguments in
1 parent c1ebfd7 commit 9c24dfe

File tree

8 files changed

+106
-56
lines changed

8 files changed

+106
-56
lines changed

example/go.mod

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
module github.com/daluu/gorrs/example
2+
3+
go 1.13
4+
5+
require github.com/daluu/gorrs v0.0.0-20191113073619-c1ebfd7cfc64 // indirect
6+
7+
replace github.com/daluu/gorrs v0.0.0-20191113073619-c1ebfd7cfc64 => ../

example/go.sum

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
github.com/daluu/gorrs v0.0.0-20191113073619-c1ebfd7cfc64 h1:xL3464UC/iZOSZfu7i+pEGZYFjQRqM/EAM7eVmtdKhM=
2+
github.com/daluu/gorrs v0.0.0-20191113073619-c1ebfd7cfc64/go.mod h1:oo2wZm9B9woepF3VGBfQPFmhDQfajFLLeepu5QqyS5s=
3+
github.com/divan/gorilla-xmlrpc v0.0.0-20190926132722-f0686da74fda h1:q6BJCx6rxRJv/sLreclgzu4dK4dPF8x48afqcXtRtLQ=
4+
github.com/divan/gorilla-xmlrpc v0.0.0-20190926132722-f0686da74fda/go.mod h1:3Cp6mWQcmK3erqkPrriKEkSpok0LO1uB2M5GxGzifhc=
5+
github.com/gorilla/rpc v1.2.0 h1:WvvdC2lNeT1SP32zrIce5l0ECBfbAlmrmSBsuc57wfk=
6+
github.com/gorilla/rpc v1.2.0/go.mod h1:V4h9r+4sF5HnzqbwIez0fKSpANP0zlYd3qR7p36jkTQ=
7+
github.com/rogpeppe/go-charset v0.0.0-20190617161244-0dc95cdf6f31 h1:DE4LcMKyqAVa6a0CGmVxANbnVb7stzMmPkQiieyNmfQ=
8+
github.com/rogpeppe/go-charset v0.0.0-20190617161244-0dc95cdf6f31/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc=

example/main.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package main
2+
3+
import (
4+
"github.com/daluu/gorrs/libraries"
5+
"github.com/daluu/gorrs/runner"
6+
)
7+
8+
func main() {
9+
runner.RunRemoteServer(new(libraries.ExampleRemoteLibrary))
10+
}

go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,5 @@ require (
77
github.com/gorilla/rpc v1.2.0
88
github.com/rogpeppe/go-charset v0.0.0-20190617161244-0dc95cdf6f31 // indirect
99
)
10+
11+
replace github.com/divan/gorilla-xmlrpc v0.0.0-20190926132722-f0686da74fda => github.com/samirkut/gorilla-xmlrpc v0.0.0-20200110153911-8acdd7083791

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,5 @@ github.com/gorilla/rpc v1.2.0 h1:WvvdC2lNeT1SP32zrIce5l0ECBfbAlmrmSBsuc57wfk=
44
github.com/gorilla/rpc v1.2.0/go.mod h1:V4h9r+4sF5HnzqbwIez0fKSpANP0zlYd3qR7p36jkTQ=
55
github.com/rogpeppe/go-charset v0.0.0-20190617161244-0dc95cdf6f31 h1:DE4LcMKyqAVa6a0CGmVxANbnVb7stzMmPkQiieyNmfQ=
66
github.com/rogpeppe/go-charset v0.0.0-20190617161244-0dc95cdf6f31/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc=
7+
github.com/samirkut/gorilla-xmlrpc v0.0.0-20200110153911-8acdd7083791 h1:xrBucR0ZjpmFRYe2dwzobq01njvwffuWp8CaS01VMJ4=
8+
github.com/samirkut/gorilla-xmlrpc v0.0.0-20200110153911-8acdd7083791/go.mod h1:cpCVXo7AA8zZqhx4ApNmJXo3i+UgHUk7IVKYxgdBvD0=

libraries/example_library.go

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@ import (
66
"io/ioutil"
77
)
88

9-
//Example library to be used with Robot Framework's remote server.
9+
//ExampleRemoteLibrary to be used with Robot Framework's remote server.
1010
type ExampleRemoteLibrary struct{}
1111

12-
//Returns the number of items in the directory specified by `path`.
12+
//CountItemsInDirectory the number of items in the directory specified by `path`.
1313
func (lib *ExampleRemoteLibrary) CountItemsInDirectory(path string) (int, error) {
1414
fileCount := 0
1515
files, err := ioutil.ReadDir(path)
@@ -20,6 +20,7 @@ func (lib *ExampleRemoteLibrary) CountItemsInDirectory(path string) (int, error)
2020
return fileCount, err
2121
}
2222

23+
//StringsShouldBeEqual ...
2324
func (lib *ExampleRemoteLibrary) StringsShouldBeEqual(str1 string, str2 string) error {
2425
fmt.Printf("Comparing '%s' to '%s'.", str1, str2)
2526
if str1 != str2 {
@@ -32,6 +33,16 @@ func (lib *ExampleRemoteLibrary) StringsShouldBeEqual(str1 string, str2 string)
3233
//optional extra keyword below, following phrrs (PHP robot framework remote server)
3334
//comment out if it interferes with running example remote library tests against gorrs
3435

36+
//TruthOfLife ...
3537
func (lib *ExampleRemoteLibrary) TruthOfLife() int {
3638
return 42
3739
}
40+
41+
//TruthOfLife ...
42+
func (lib *ExampleRemoteLibrary) ReturnArray() []interface{} {
43+
var testArray []interface{}
44+
testArray = append(testArray, "string")
45+
testArray = append(testArray, 1)
46+
testArray = append(testArray, 1.1)
47+
return testArray
48+
}

main.go

Lines changed: 1 addition & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,6 @@ package main
22

33
import (
44
"log"
5-
"net/http"
6-
7-
"github.com/daluu/gorrs/protocol"
8-
"github.com/divan/gorilla-xmlrpc/xml"
9-
"github.com/gorilla/rpc"
105
)
116

127
/* add to import list of github.com/daluu/gorrs/protocol/protocol.go,
@@ -26,29 +21,5 @@ import (
2621
*/
2722

2823
func main() {
29-
RPC := rpc.NewServer()
30-
xmlrpcCodec := xml.NewCodec()
31-
//map XML-RPC methods to the go implemented functions
32-
//CamelCase mapping
33-
xmlrpcCodec.RegisterAlias("GetKeywordNames", "RobotRemoteService.GetKeywordNames")
34-
xmlrpcCodec.RegisterAlias("GetKeywordArguments", "RobotRemoteService.GetKeywordArguments")
35-
xmlrpcCodec.RegisterAlias("GetKeywordDocumentation", "RobotRemoteService.GetKeywordDocumentation")
36-
xmlrpcCodec.RegisterAlias("RunKeyword", "RobotRemoteService.RunKeyword")
37-
//pythonic mapping
38-
xmlrpcCodec.RegisterAlias("get_keyword_names", "RobotRemoteService.GetKeywordNames")
39-
xmlrpcCodec.RegisterAlias("get_keyword_arguments", "RobotRemoteService.GetKeywordArguments")
40-
xmlrpcCodec.RegisterAlias("get_keyword_documentation", "RobotRemoteService.GetKeywordDocumentation")
41-
xmlrpcCodec.RegisterAlias("run_keyword", "RobotRemoteService.RunKeyword")
42-
43-
//set server to handle both XML MIME types
44-
RPC.RegisterCodec(xmlrpcCodec, "application/xml")
45-
RPC.RegisterCodec(xmlrpcCodec, "text/xml")
46-
47-
RPC.RegisterService(new(protocol.RobotRemoteService), "")
48-
http.Handle("/RPC2", RPC) //preserve option to use RPC2 endpoint
49-
http.Handle("/", RPC) //but not make it required when using with Robot Framework
50-
51-
//TODO: make port and host/IP address binding be configurable via CLI flags and not fixed to localhost:8270 (the default)
52-
log.Println("Robot remote server started on localhost:8270 under / and /RPC2 endpoints. Stop server with Ctrl+C, kill, etc. or XML-RPC method 'run_keyword' with parameter 'stop_remote_server'\n")
53-
log.Fatal(http.ListenAndServe(":8270", nil))
24+
log.Fatal("not runnable directly")
5425
}

protocol/protocol.go

Lines changed: 63 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package protocol
22

33
import (
4+
"fmt"
45
"log"
56
"net/http"
67
"os"
@@ -45,6 +46,12 @@ type KeywordNamesReturnValue struct {
4546
Keywords []interface{}
4647
}
4748

49+
var offeredLibrary interface{}
50+
51+
func (h *RobotRemoteService) InitilizeRemoteLibrary(library interface{}) {
52+
offeredLibrary = library
53+
}
54+
4855
//sample XML-RPC input: <methodCall><methodName>GetKeywordNames</methodName><params></params></methodCall>
4956
/* sample XML-RPC output:
5057
* <methodResponse><params><param><value><array><data>
@@ -56,6 +63,13 @@ type KeywordNamesReturnValue struct {
5663
func (h *RobotRemoteService) GetKeywordNames(r *http.Request, args *struct{}, reply *KeywordNamesReturnValue) error {
5764
//TODO: use reflection to generate array of keywords (found in the imported namespace) to return in reply
5865
//maybe rather than all imported packages, restrict to a specific one, etc. as specified at server startup?
66+
//keywordLibrary := new(offeredLibrary)
67+
libraryKeywords := reflect.TypeOf(offeredLibrary)
68+
//libraryKeywords := reflect.PtrTo(reflect.TypeOf(offeredLibrary{}))
69+
log.Printf("Found %d keywords", libraryKeywords.NumMethod())
70+
for i := 0; i < libraryKeywords.NumMethod(); i++ {
71+
reply.Keywords = append(reply.Keywords, libraryKeywords.Method(i).Name)
72+
}
5973

6074
//add special keyword built-in to the server:
6175
reply.Keywords = append(reply.Keywords, "StopRemoteServer")
@@ -76,6 +90,10 @@ func _stopRemoteServer() {
7690
os.Exit(1)
7791
}
7892

93+
type Response struct {
94+
Content RunKeywordReturnValue
95+
}
96+
7997
type RunKeywordReturnValue struct {
8098
Return interface{} `xml:"return"`
8199
Status string `xml:"status"`
@@ -131,36 +149,45 @@ type KeywordAndArgsInput struct {
131149
*/
132150
//this function doesn't fully work yet, see
133151
//https://github.com/divan/gorilla-xmlrpc/issues/ #16 and 18
134-
func (h *RobotRemoteService) RunKeyword(r *http.Request, args *KeywordAndArgsInput, reply *RunKeywordReturnValue) error {
152+
func (h *RobotRemoteService) RunKeyword(r *http.Request, args *KeywordAndArgsInput, reply *Response) error {
135153
//use reflection to run function "keyword name" out of 1st arg
136154
//with 2nd arg (array) containing the args for the keyword function
137155
//sample debug/test code for now...
138156
log.Printf("keyword: %+v\n", args.KeywordName)
157+
log.Printf("args: %+v\n", args.KeywordAguments)
158+
159+
reply.Content.Status = "PASS"
160+
139161
if args.KeywordName == "StopRemoteServer" {
140162
go h.StopRemoteServer()
141-
}
142-
log.Printf("args: %+v\n", args.KeywordAguments)
143-
for _, a := range args.KeywordAguments {
144-
log.Printf("arg: %+v\n", a)
145-
switch reflect.TypeOf(a).Kind() {
146-
case reflect.Slice:
147-
log.Printf("args:\n")
148-
s := reflect.ValueOf(a)
149-
for i := 0; i < s.Len(); i++ {
150-
log.Printf("%v: %+v\n", i, s.Index(i))
163+
} else {
164+
method := reflect.ValueOf(offeredLibrary).MethodByName(args.KeywordName)
165+
if method.Type().NumIn() == len(args.KeywordAguments) {
166+
in := make([]reflect.Value, method.Type().NumIn())
167+
for i := 0; i < method.Type().NumIn(); i++ {
168+
var object interface{}
169+
if method.Type().In(i).Kind() == reflect.Ptr {
170+
object = offeredLibrary
171+
} else {
172+
object = args.KeywordAguments[i]
173+
}
174+
fmt.Println(i, "->", object)
175+
in[i] = reflect.ValueOf(object)
176+
}
177+
returnValue := method.Call(in)
178+
if method.Type().NumOut() == 1 {
179+
reply.Content.Return = returnValue[0].Interface()
180+
} else if method.Type().NumOut() > 1 {
181+
reply.Content.Stderr = "supporting only 0 or 1 return values"
151182
}
152-
default:
153-
log.Println("Somehow didn't get an array of arguments for keyword.")
183+
} else {
184+
reply.Content.Stderr = fmt.Sprintf("incorrect amount of input variables; expected %d and got %d", method.Type().NumIn()-1, len(args.KeywordAguments))
185+
reply.Content.Status = "FAIL"
154186
}
155187
}
156-
//and return the results in struct below (sample static output for now):
157-
var retval interface{}
158-
retval = 42 //truth of life
159-
reply.Return = retval
160-
reply.Status = "FAIL"
161-
reply.Stdout = "TODO: stdout from keyword execution gets piped into this"
162-
reply.Stderr = "TODO: stderr from keyword execution gets piped into this"
163-
reply.Traceback = "TODO: stack trace info goes here, if any..."
188+
reply.Content.Stdout = "TODO: stdout from keyword execution gets piped into this"
189+
//reply.Content.Stderr = "TODO: stderr from keyword execution gets piped into this"
190+
reply.Content.Traceback = "TODO: stack trace info goes here, if any..."
164191
return nil
165192
}
166193

@@ -179,9 +206,21 @@ type KeywordArgumentsReturnValue struct {
179206
//sample XML-RPC output: <methodResponse><params><param><value><array><data><value><string>arg1</string></value>...</data></array></value></param></params></methodResponse>
180207
func (h *RobotRemoteService) GetKeywordArguments(r *http.Request, args *KeywordInput, reply *KeywordArgumentsReturnValue) error {
181208
//use reflection to get the arguments to keyword function and pass back to reply
182-
reply.KeywordAguments = make([]interface{}, 0) //if to pass back no arguments
183-
//http://stackoverflow.com/questions/12990338/cannot-convert-string-to-interface
184-
//else something like reply.KeywordAguments = append(reply.KeywordAguments,"two","arguments")
209+
log.Printf("Getting arguments for %s", args.KeywordName)
210+
method := reflect.ValueOf(offeredLibrary).MethodByName(args.KeywordName)
211+
j := 0
212+
if args.KeywordName != "StopRemoteServer" {
213+
for i := 0; i < method.Type().NumIn(); i++ {
214+
if method.Type().In(i).Kind() != reflect.Ptr {
215+
methodName := method.Type().In(i).Name()
216+
if len(methodName) == 0 || methodName == method.Type().In(i).Kind().String() {
217+
methodName = fmt.Sprintf("arg%d", j)
218+
}
219+
reply.KeywordAguments = append(reply.KeywordAguments, methodName)
220+
j++
221+
}
222+
}
223+
}
185224
return nil
186225
}
187226

0 commit comments

Comments
 (0)