From 575489fa782dd6c8166baec2b44b705bf56ac488 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 2 May 2013 15:20:12 -0700 Subject: [PATCH] RPC commands and some basic tests for this [GH-1] --- packer/command.go | 37 ++++++++++++++- packer/command_test.go | 95 ++++++++++++++++++++++++++++++++++++++ packer/environment_test.go | 17 ------- 3 files changed, 131 insertions(+), 18 deletions(-) create mode 100644 packer/command_test.go diff --git a/packer/command.go b/packer/command.go index c69f2e532f5..a10246cf3ea 100644 --- a/packer/command.go +++ b/packer/command.go @@ -22,6 +22,41 @@ type Command interface { // An RPCCommand is an implementation of the Command interface where the // command is actually executed over an RPC connection. -type RPCCommand struct { +type RPCClientCommand struct { 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 +} diff --git a/packer/command_test.go b/packer/command_test.go new file mode 100644 index 00000000000..446805850d9 --- /dev/null +++ b/packer/command_test.go @@ -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") +} diff --git a/packer/environment_test.go b/packer/environment_test.go index 03898db0186..a842f1c4d12 100644 --- a/packer/environment_test.go +++ b/packer/environment_test.go @@ -8,23 +8,6 @@ import ( "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 { config := &EnvironmentConfig{} config.Ui = &ReaderWriterUi{