veyron/veyron/lib/modules/core: add exec command to shell

This change adds an exec command to the shell. The purpose of this
change is to allow the integration test framework to allow integration
test authors to execute arbitrary system commands through the Veyron
shell.

Change-Id: I6d1730e6c067f6b6bda9ecce01a3149b30a3f82b
diff --git a/lib/modules/core/core.go b/lib/modules/core/core.go
index e6519d9..a3a889c 100644
--- a/lib/modules/core/core.go
+++ b/lib/modules/core/core.go
@@ -44,6 +44,9 @@
 // echoClient <name> <text>
 //    invoke <name>.Echo(<text>)
 //
+// exec <command> [args...]
+//    executes the given command with the given arguments
+//
 // proxyd <names>...
 //    runs a proxy server
 package core
@@ -60,5 +63,5 @@
 	LSCommand          = "ls"
 	ProxyServerCommand = "proxyd"
 	WSPRCommand        = "wsprd"
-	ShellCommand       = "sh"
+	ExecCommand        = "exec"
 )
diff --git a/lib/modules/core/core_test.go b/lib/modules/core/core_test.go
index eeae40e..cd9e26b 100644
--- a/lib/modules/core/core_test.go
+++ b/lib/modules/core/core_test.go
@@ -1,6 +1,7 @@
 package core_test
 
 import (
+	"bytes"
 	"fmt"
 	"os"
 	"reflect"
@@ -191,6 +192,38 @@
 	srv.Shutdown(nil, nil)
 }
 
+func TestExec(t *testing.T) {
+	sh, cleanup := newShell(t)
+	defer cleanup()
+	h, err := sh.Start(core.ExecCommand, nil, []string{"echo", "-n", "hello world"}...)
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	output := bytes.NewBuffer([]byte{})
+	if _, err := output.ReadFrom(h.Stdout()); err != nil {
+		t.Fatalf("could not read output from command: %v", err)
+	}
+	if got, want := output.String(), "hello world"; got != want {
+		t.Fatalf("unexpected output: got %v, want %v", got, want)
+	}
+}
+
+func TestExecWithEnv(t *testing.T) {
+	sh, cleanup := newShell(t)
+	defer cleanup()
+	h, err := sh.Start(core.ExecCommand, []string{"BLAH=hello world"}, "printenv", "BLAH")
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	output := bytes.NewBuffer([]byte{})
+	if _, err := output.ReadFrom(h.Stdout()); err != nil {
+		t.Fatalf("could not read output from command: %v", err)
+	}
+	if got, want := output.String(), "hello world\n"; got != want {
+		t.Fatalf("unexpected output: got %v, want %v", got, want)
+	}
+}
+
 func TestHelperProcess(t *testing.T) {
 	modules.DispatchInTest()
 }
diff --git a/lib/modules/core/exec.go b/lib/modules/core/exec.go
new file mode 100644
index 0000000..eff8aa1
--- /dev/null
+++ b/lib/modules/core/exec.go
@@ -0,0 +1,26 @@
+package core
+
+import (
+	"io"
+	"os/exec"
+
+	"veyron.io/veyron/veyron/lib/modules"
+)
+
+func init() {
+	modules.RegisterChild(ExecCommand, "", execCommand)
+}
+
+func execCommand(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error {
+	cmd := exec.Command(args[1], args[2:]...)
+	envSlice := []string{}
+	for key, value := range env {
+		envSlice = append(envSlice, key+"="+value)
+	}
+
+	cmd.Env = envSlice
+	cmd.Stdin = stdin
+	cmd.Stdout = stdout
+	cmd.Stderr = stderr
+	return cmd.Run()
+}