RPC commands and some basic tests for this [GH-1]
This commit is contained in:
parent
520503e10c
commit
575489fa78
|
@ -22,6 +22,41 @@ type Command interface {
|
||||||
|
|
||||||
// An RPCCommand is an implementation of the Command interface where the
|
// An RPCCommand is an implementation of the Command interface where the
|
||||||
// command is actually executed over an RPC connection.
|
// command is actually executed over an RPC connection.
|
||||||
type RPCCommand struct {
|
type RPCClientCommand struct {
|
||||||
client *rpc.Client
|
client *rpc.Client
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// An RPCServerCommand wraps a Command and makes it exportable as part
|
||||||
|
// of a Golang RPC server.
|
||||||
|
type RPCServerCommand struct {
|
||||||
|
command Command
|
||||||
|
}
|
||||||
|
|
||||||
|
type RPCCommandRunArgs struct {
|
||||||
|
Env Environment
|
||||||
|
Args []string
|
||||||
|
}
|
||||||
|
|
||||||
|
type RPCCommandSynopsisArgs byte
|
||||||
|
|
||||||
|
func (c *RPCClientCommand) Run(env Environment, args []string) (result int) {
|
||||||
|
// TODO: Environment
|
||||||
|
rpcArgs := &RPCCommandRunArgs{nil, args}
|
||||||
|
c.client.Call("Command.Run", rpcArgs, &result)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *RPCClientCommand) Synopsis() (result string) {
|
||||||
|
c.client.Call("Command.Synopsis", RPCCommandSynopsisArgs(0), &result)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *RPCServerCommand) Run(args *RPCCommandRunArgs, reply *int) error {
|
||||||
|
*reply = c.command.Run(args.Env, args.Args)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *RPCServerCommand) Synopsis(args *RPCCommandSynopsisArgs, reply *string) error {
|
||||||
|
*reply = c.command.Synopsis()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,95 @@
|
||||||
|
package packer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"cgl.tideland.biz/asserts"
|
||||||
|
"net"
|
||||||
|
"net/rpc"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TestCommand struct {
|
||||||
|
runArgs []string
|
||||||
|
runCalled bool
|
||||||
|
runEnv Environment
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tc *TestCommand) Run(env Environment, args []string) int {
|
||||||
|
tc.runCalled = true
|
||||||
|
tc.runArgs = args
|
||||||
|
tc.runEnv = env
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tc *TestCommand) Synopsis() string {
|
||||||
|
return "foo"
|
||||||
|
}
|
||||||
|
|
||||||
|
// This starts a RPC server for the given command listening on the
|
||||||
|
// given address. The RPC server is ready when "readyChan" receives a message
|
||||||
|
// and the RPC server will quit when "stopChan" receives a message.
|
||||||
|
//
|
||||||
|
// This function should be run in a goroutine.
|
||||||
|
func testCommandRPCServer(laddr string, command interface{}, readyChan chan int, stopChan <-chan int) {
|
||||||
|
listener, err := net.Listen("tcp", laddr)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close the listener when we exit so that the RPC server ends
|
||||||
|
defer listener.Close()
|
||||||
|
|
||||||
|
// Start the RPC server
|
||||||
|
server := rpc.NewServer()
|
||||||
|
server.RegisterName("Command", command)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
conn, err := listener.Accept()
|
||||||
|
if err != nil {
|
||||||
|
// If there is an error, just ignore it.
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
go server.ServeConn(conn)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// We're ready!
|
||||||
|
readyChan <- 1
|
||||||
|
|
||||||
|
// Block on waiting to receive from the channel
|
||||||
|
<-stopChan
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRPCCommand(t *testing.T) {
|
||||||
|
assert := asserts.NewTestingAsserts(t, true)
|
||||||
|
|
||||||
|
// Create the command
|
||||||
|
command := new(TestCommand)
|
||||||
|
serverCommand := &RPCServerCommand{command}
|
||||||
|
|
||||||
|
// Start the RPC server, and make sure to exit it at the end
|
||||||
|
// of the test.
|
||||||
|
readyChan := make(chan int)
|
||||||
|
stopChan := make(chan int)
|
||||||
|
defer func() { stopChan <- 1 }()
|
||||||
|
go testCommandRPCServer(":1234", serverCommand, readyChan, stopChan)
|
||||||
|
<-readyChan
|
||||||
|
|
||||||
|
// Create the command client over RPC and run some methods to verify
|
||||||
|
// we get the proper behavior.
|
||||||
|
client, err := rpc.Dial("tcp", ":1234")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
clientComm := &RPCClientCommand{client}
|
||||||
|
runArgs := []string{"foo", "bar"}
|
||||||
|
testEnv := testEnvironment()
|
||||||
|
exitCode := clientComm.Run(testEnv, runArgs)
|
||||||
|
synopsis := clientComm.Synopsis()
|
||||||
|
|
||||||
|
assert.Equal(command.runArgs, runArgs, "Correct args should be sent")
|
||||||
|
assert.Equal(exitCode, 0, "Exit code should be correct")
|
||||||
|
assert.Equal(synopsis, "foo", "Synopsis should be correct")
|
||||||
|
}
|
|
@ -8,23 +8,6 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
type TestCommand struct {
|
|
||||||
runArgs []string
|
|
||||||
runCalled bool
|
|
||||||
runEnv Environment
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tc *TestCommand) Run(env Environment, args []string) int {
|
|
||||||
tc.runCalled = true
|
|
||||||
tc.runArgs = args
|
|
||||||
tc.runEnv = env
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (tc *TestCommand) Synopsis() string {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func testEnvironment() Environment {
|
func testEnvironment() Environment {
|
||||||
config := &EnvironmentConfig{}
|
config := &EnvironmentConfig{}
|
||||||
config.Ui = &ReaderWriterUi{
|
config.Ui = &ReaderWriterUi{
|
||||||
|
|
Loading…
Reference in New Issue