sandbox: add fake filesystem, tour dependencies, and tests

Change-Id: I5a1f20b4dee47425137748612b2884d1f73e6286
Reviewed-on: https://go-review.googlesource.com/3261
Reviewed-by: Andrew Gerrand <adg@golang.org>
diff --git a/sandbox/Dockerfile b/sandbox/Dockerfile
index b70f018..3f5b76d 100644
--- a/sandbox/Dockerfile
+++ b/sandbox/Dockerfile
@@ -2,7 +2,7 @@
 # Use of this source code is governed by a BSD-style
 # license that can be found in the LICENSE file.
 
-FROM golang:1.4
+FROM golang:1.4.1
 
 # enable faketime
 RUN apt-get update && apt-get install -yq --no-install-recommends patch
@@ -11,11 +11,21 @@
 ADD write-to-stderr.patch /usr/src/go/
 RUN patch /usr/src/go/src/runtime/sys_nacl_amd64p32.s /usr/src/go/write-to-stderr.patch
 
+# add fake file system
+ADD fake_fs.lst /usr/src/go/
+RUN cd /usr/src/go && go run misc/nacl/mkzip.go -p syscall fake_fs.lst src/syscall/fstest_nacl.go
+
 # build go nacl tool chain
-# TODO(proppy): add fake filesystem
 RUN cd /usr/src/go/src && GOOS=nacl GOARCH=amd64p32 ./make.bash --no-clean
 RUN cd /usr/local/bin && curl -s -O https://storage.googleapis.com/gobuilder/sel_ldr_x86_64 && chmod 0755 sel_ldr_x86_64
 
+# add and compile tour packages
+RUN GOOS=nacl GOARCH=amd64p32 go get \
+	code.google.com/p/go-tour/pic \
+	code.google.com/p/go-tour/reader \
+	code.google.com/p/go-tour/tree \
+	code.google.com/p/go-tour/wc
+
 # add and compile sandbox daemon
 ADD . /go/src/sandbox/
 RUN go install sandbox
diff --git a/sandbox/Makefile b/sandbox/Makefile
new file mode 100644
index 0000000..72cdfc2
--- /dev/null
+++ b/sandbox/Makefile
@@ -0,0 +1,6 @@
+docker: Dockerfile
+	docker build -t playground/sandbox .
+
+test: docker
+	go test
+	docker run --rm playground/sandbox test
diff --git a/sandbox/fake_fs.lst b/sandbox/fake_fs.lst
new file mode 100644
index 0000000..d9204ca
--- /dev/null
+++ b/sandbox/fake_fs.lst
@@ -0,0 +1,11 @@
+etc	src=/etc
+	resolv.conf	src=misc/nacl/testdata/empty
+	group	src=misc/nacl/testdata/group
+	passwd	src=misc/nacl/testdata/empty
+	hosts	src=misc/nacl/testdata/hosts
+usr	src=/usr
+	src
+		go
+			lib
+				time
+					zoneinfo.zip
diff --git a/sandbox/sandbox.go b/sandbox/sandbox.go
index dbca0b1..c78c758 100644
--- a/sandbox/sandbox.go
+++ b/sandbox/sandbox.go
@@ -20,6 +20,7 @@
 	"os"
 	"os/exec"
 	"path/filepath"
+	"strings"
 	"time"
 )
 
@@ -34,6 +35,16 @@
 	Events []Event
 }
 
+func main() {
+	if len(os.Args) > 1 && os.Args[1] == "test" {
+		test()
+		return
+	}
+	http.HandleFunc("/compile", compileHandler)
+	http.HandleFunc("/_ah/health", healthHandler)
+	log.Fatal(http.ListenAndServe(":8080", nil))
+}
+
 func compileHandler(w http.ResponseWriter, r *http.Request) {
 	var req Request
 	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
@@ -64,7 +75,7 @@
 	}
 	exe := filepath.Join(tmpDir, "a.out")
 	cmd := exec.Command("go", "build", "-o", exe, in)
-	cmd.Env = []string{"GOOS=nacl", "GOARCH=amd64p32"}
+	cmd.Env = []string{"GOOS=nacl", "GOARCH=amd64p32", "GOPATH=" + os.Getenv("GOPATH")}
 	if out, err := cmd.CombinedOutput(); err != nil {
 		if _, ok := err.(*exec.ExitError); ok {
 			// Build error.
@@ -120,8 +131,6 @@
 	fmt.Fprint(w, "ok")
 }
 
-const healthProg = `package main;import "fmt";func main(){fmt.Print("ok")}`
-
 func healthCheck() error {
 	resp, err := compileAndRun(&Request{Body: healthProg})
 	if err != nil {
@@ -136,8 +145,119 @@
 	return nil
 }
 
+const healthProg = `
+package main
+
+import "fmt"
+
+func main() { fmt.Print("ok") }
+`
+
+func test() {
+	if err := healthCheck(); err != nil {
+		log.Fatal(err)
+	}
+	for _, t := range tests {
+		resp, err := compileAndRun(&Request{Body: t.prog})
+		if err != nil {
+			log.Fatal(err)
+		}
+		if resp.Errors != "" {
+			log.Fatal(resp.Errors)
+		}
+		if len(resp.Events) != 1 || !strings.Contains(resp.Events[0].Message, t.want) {
+			log.Fatalf("unexpected output: %v, want %q", resp.Events, t.want)
+		}
+	}
+	fmt.Println("OK")
+}
+
+var tests = []struct {
+	prog, want string
+}{
+	{`
+package main
+
+import "time"
+
 func main() {
-	http.HandleFunc("/compile", compileHandler)
-	http.HandleFunc("/_ah/health", healthHandler)
-	log.Fatal(http.ListenAndServe(":8080", nil))
+	loc, err := time.LoadLocation("America/New_York")
+	if err != nil {
+		panic(err.Error())
+	}
+	println(loc.String())
+}
+	`, "America/New_York"},
+
+	{`
+package main
+
+import (
+	"fmt"
+	"time"
+)
+
+func main() {
+	fmt.Println(time.Now())
+}
+	`, "2009-11-10 23:00:00 +0000 UTC"},
+
+	{`
+package main
+
+import (
+	"fmt"
+	"time"
+)
+
+func main() {
+	t1 := time.Tick(time.Second * 3)
+	t2 := time.Tick(time.Second * 7)
+	t3 := time.Tick(time.Second * 11)
+	end := time.After(time.Second * 19)
+	want := "112131211"
+	var got []byte
+	for {
+		var c byte
+		select {
+		case <-t1:
+			c = '1'
+		case <-t2:
+			c = '2'
+		case <-t3:
+			c = '3'
+		case <-end:
+			if g := string(got); g != want {
+				fmt.Printf("got %q, want %q\n", g, want)
+			} else {
+				fmt.Println("timers fired as expected")
+			}
+			return
+		}
+		got = append(got, c)
+	}
+}
+	`, "timers fired as expected"},
+
+	{`
+package main
+
+import (
+	"code.google.com/p/go-tour/pic"
+	"code.google.com/p/go-tour/reader"
+	"code.google.com/p/go-tour/tree"
+	"code.google.com/p/go-tour/wc"
+)
+
+var (
+	_ = pic.Show
+	_ = reader.Validate
+	_ = tree.New
+	_ = wc.Test
+)
+
+func main() {
+	println("ok")
+}
+	`, "ok"},
 }