playground: let clients request vet check in same HTTP request as compile+run
Also, move the tests to their own file and extend them a bit, give
them names, and make the test step more verbose (only visible in
docker build anyway). They were hogging up the sandbox file.
Updates golang/go#31970
Change-Id: Id710ea613c77a5b16cc5e79545c0812d0f4650e3
Reviewed-on: https://go-review.googlesource.com/c/playground/+/176598
Reviewed-by: Andrew Bonventre <andybons@golang.org>
Reviewed-by: Yury Smolsky <yury@smolsky.by>
diff --git a/README.md b/README.md
index e900ea6..33bdcfc 100644
--- a/README.md
+++ b/README.md
@@ -26,10 +26,19 @@
gcloud config set app/cloud_build_timeout 1200 # 20 mins
```
+Alternatively, to avoid Cloud Build and build locally:
+
+```
+make docker
+docker tag playground:latest gcr.io/golang-org/playground:latest
+docker push gcr.io/golang-org/playground:latest
+gcloud --project=golang-org --account=you@google.com app deploy app.yaml --image-url=gcr.io/golang-org/playground:latest
+```
+
Then:
```
-gcloud --project=golang-org --account=person@example.com app deploy app.yaml
+gcloud --project=golang-org --account=you@google.com app deploy app.yaml
```
# Contributing
diff --git a/go.mod b/go.mod
index 522d24a..f68be74 100644
--- a/go.mod
+++ b/go.mod
@@ -5,5 +5,6 @@
require (
cloud.google.com/go v0.38.0
github.com/bradfitz/gomemcache v0.0.0-20190329173943-551aad21a668
- golang.org/x/tools v0.0.0-20190509153222-73554e0f7805
+ golang.org/x/net v0.0.0-20190509222800-a4d6f7feada5 // indirect
+ golang.org/x/tools v0.0.0-20190513214131-2a413a02cc73
)
diff --git a/go.sum b/go.sum
index 9bf9295..8e9d91d 100644
--- a/go.sum
+++ b/go.sum
@@ -6,12 +6,14 @@
github.com/bradfitz/gomemcache v0.0.0-20190329173943-551aad21a668 h1:U/lr3Dgy4WK+hNk4tyD+nuGjpVLPEHuJSFXMw11/HPA=
github.com/bradfitz/gomemcache v0.0.0-20190329173943-551aad21a668/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
+github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
+github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
@@ -33,6 +35,8 @@
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190509222800-a4d6f7feada5 h1:6M3SDHlHHDCx2PcQw3S4KsR170vGqDhJDOmpVd4Hjak=
+golang.org/x/net v0.0.0-20190509222800-a4d6f7feada5/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421 h1:Wo7BWFiOk0QRFMLYMqJGFMd9CgUAcGx7V+qEg/h5IBI=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -40,6 +44,7 @@
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
@@ -51,11 +56,12 @@
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
-golang.org/x/tools v0.0.0-20190509153222-73554e0f7805 h1:1ufBXAsTpUhSmmPXEEs5PrGQSfnBhsjAd2SmVhp9xrY=
-golang.org/x/tools v0.0.0-20190509153222-73554e0f7805/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190513214131-2a413a02cc73 h1:zHwPzzQF2U6W4cSM2929cb7MvpB6dLYu9dHwYjOv+ag=
+golang.org/x/tools v0.0.0-20190513214131-2a413a02cc73/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
google.golang.org/api v0.4.0 h1:KKgc1aqhV8wDPbDzlDtpvyjZFY3vjz85FP7p4wcQUyI=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
+google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
diff --git a/sandbox.go b/sandbox.go
index 56ab9e3..56e9613 100644
--- a/sandbox.go
+++ b/sandbox.go
@@ -19,12 +19,10 @@
"go/token"
"io"
"io/ioutil"
- stdlog "log"
"net/http"
"os"
"os/exec"
"path/filepath"
- "reflect"
"runtime"
"strconv"
"strings"
@@ -38,7 +36,8 @@
const (
maxRunTime = 2 * time.Second
- // progName is the program name in compiler errors
+ // progName is the implicit program name written to the temp
+ // dir and used in compiler and vet errors.
progName = "prog.go"
)
@@ -47,7 +46,8 @@
var nonCachingErrors = []string{"out of memory", "cannot allocate memory"}
type request struct {
- Body string
+ Body string
+ WithVet bool // whether client supports vet response in a /compile request (Issue 31970)
}
type response struct {
@@ -56,6 +56,14 @@
Status int
IsTest bool
TestsFailed int
+
+ // VetErrors, if non-empty, contains any vet errors. It is
+ // only populated if request.WithVet was true.
+ VetErrors string `json:",omitempty"`
+ // VetOK reports whether vet ran & passsed. It is only
+ // populated if request.WithVet was true. Only one of
+ // VetErrors or VetOK can be non-zero.
+ VetOK bool `json:",omitempty"`
}
// commandHandler returns an http.HandlerFunc.
@@ -77,6 +85,7 @@
// are updated to always send JSON, this check is in place.
if b := r.FormValue("body"); b != "" {
req.Body = b
+ req.WithVet, _ = strconv.ParseBool(r.FormValue("withVet"))
} else if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
s.log.Errorf("error decoding request: %v", err)
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
@@ -182,7 +191,7 @@
func getTestProg(src []byte) []byte {
fset := token.NewFileSet()
// Early bail for most cases.
- f, err := parser.ParseFile(fset, "main.go", src, parser.ImportsOnly)
+ f, err := parser.ParseFile(fset, progName, src, parser.ImportsOnly)
if err != nil || f.Name.Name != "main" {
return nil
}
@@ -199,7 +208,7 @@
}
// Parse everything and extract test names.
- f, err = parser.ParseFile(fset, "main.go", src, parser.ParseComments)
+ f, err = parser.ParseFile(fset, progName, src, parser.ParseComments)
if err != nil {
return nil
}
@@ -303,7 +312,7 @@
defer os.RemoveAll(tmpDir)
src := []byte(req.Body)
- in := filepath.Join(tmpDir, "main.go")
+ in := filepath.Join(tmpDir, progName)
if err := ioutil.WriteFile(in, src, 0400); err != nil {
return nil, fmt.Errorf("error creating temp file %q: %v", in, err)
}
@@ -326,29 +335,29 @@
exe := filepath.Join(tmpDir, "a.out")
goCache := filepath.Join(tmpDir, "gocache")
cmd := exec.Command("go", "build", "-o", exe, in)
+ var goPath string
cmd.Env = []string{"GOOS=nacl", "GOARCH=amd64p32", "GOCACHE=" + goCache}
- if allowModuleDownloads(src) {
+ useModules := allowModuleDownloads(src)
+ if useModules {
// Create a GOPATH just for modules to be downloaded
// into GOPATH/pkg/mod.
- gopath, err := ioutil.TempDir("", "gopath")
+ goPath, err = ioutil.TempDir("", "gopath")
if err != nil {
return nil, fmt.Errorf("error creating temp directory: %v", err)
}
- defer os.RemoveAll(gopath)
- cmd.Env = append(cmd.Env, "GO111MODULE=on", "GOPROXY=https://proxy.golang.org", "GOPATH="+gopath)
+ defer os.RemoveAll(goPath)
+ cmd.Env = append(cmd.Env, "GO111MODULE=on", "GOPROXY=https://proxy.golang.org")
} else {
-
- cmd.Env = append(cmd.Env,
- "GO111MODULE=off", // in case it becomes on by default later
- "GOPATH="+os.Getenv("GOPATH"), // contains old code.google.com/p/go-tour, etc
- )
+ goPath = os.Getenv("GOPATH") // contains old code.google.com/p/go-tour, etc
+ cmd.Env = append(cmd.Env, "GO111MODULE=off") // in case it becomes on by default later
}
+ cmd.Env = append(cmd.Env, "GOPATH="+goPath)
if out, err := cmd.CombinedOutput(); err != nil {
if _, ok := err.(*exec.ExitError); ok {
// Return compile errors to the user.
// Rewrite compiler errors to refer to progName
- // instead of '/tmp/sandbox1234/main.go'.
+ // instead of '/tmp/sandbox1234/prog.go'.
errs := strings.Replace(string(out), in, progName, -1)
// "go build", invoked with a file name, puts this odd
@@ -394,7 +403,21 @@
fails += strings.Count(e.Message, failedTestPattern)
}
}
- return &response{Events: events, Status: status, IsTest: testParam != "", TestsFailed: fails}, nil
+ var vetOut string
+ if req.WithVet {
+ vetOut, err = vetCheckInDir(tmpDir, goPath, useModules)
+ if err != nil {
+ return nil, fmt.Errorf("running vet: %v", err)
+ }
+ }
+ return &response{
+ Events: events,
+ Status: status,
+ IsTest: testParam != "",
+ TestsFailed: fails,
+ VetErrors: vetOut,
+ VetOK: req.WithVet && vetOut == "",
+ }, nil
}
// allowModuleDownloads reports whether the code snippet in src should be allowed
@@ -433,348 +456,3 @@
func main() { fmt.Print("ok") }
`
-
-func (s *server) test() {
- if err := s.healthCheck(); err != nil {
- stdlog.Fatal(err)
- }
-
- // Enable module downloads for testing:
- defer func(old string) { os.Setenv("ALLOW_PLAY_MODULE_DOWNLOADS", old) }(os.Getenv("ALLOW_PLAY_MODULE_DOWNLOADS"))
- os.Setenv("ALLOW_PLAY_MODULE_DOWNLOADS", "true")
-
- for _, t := range tests {
- resp, err := compileAndRun(&request{Body: t.prog})
- if err != nil {
- stdlog.Fatal(err)
- }
- if t.wantEvents != nil {
- if !reflect.DeepEqual(resp.Events, t.wantEvents) {
- stdlog.Fatalf("resp.Events = %q, want %q", resp.Events, t.wantEvents)
- }
- continue
- }
- if t.errors != "" {
- if resp.Errors != t.errors {
- stdlog.Fatalf("resp.Errors = %q, want %q", resp.Errors, t.errors)
- }
- continue
- }
- if resp.Errors != "" {
- stdlog.Fatal(resp.Errors)
- }
- if len(resp.Events) == 0 {
- stdlog.Fatalf("unexpected output: %q, want %q", "", t.want)
- }
- var b strings.Builder
- for _, e := range resp.Events {
- b.WriteString(e.Message)
- }
- if !strings.Contains(b.String(), t.want) {
- stdlog.Fatalf("unexpected output: %q, want %q", b.String(), t.want)
- }
- }
- fmt.Println("OK")
-}
-
-var tests = []struct {
- prog, want, errors string
- wantEvents []Event
-}{
- {prog: `
-package main
-
-import "time"
-
-func main() {
- loc, err := time.LoadLocation("America/New_York")
- if err != nil {
- panic(err.Error())
- }
- println(loc.String())
-}
-`, want: "America/New_York"},
-
- {prog: `
-package main
-
-import (
- "fmt"
- "time"
-)
-
-func main() {
- fmt.Println(time.Now())
-}
-`, want: "2009-11-10 23:00:00 +0000 UTC"},
-
- {prog: `
-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)
- }
-}
-`, want: "timers fired as expected"},
-
- {prog: `
-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")
-}
-`, want: "ok"},
- {prog: `
-package test
-
-func main() {
- println("test")
-}
-`, want: "", errors: "package name must be main"},
- {prog: `
-package main
-
-import (
- "fmt"
- "os"
- "path/filepath"
-)
-
-func main() {
- filepath.Walk("/", func(path string, info os.FileInfo, err error) error {
- fmt.Println(path)
- return nil
- })
-}
-`, want: `/
-/dev
-/dev/null
-/dev/random
-/dev/urandom
-/dev/zero
-/etc
-/etc/group
-/etc/hosts
-/etc/passwd
-/etc/resolv.conf
-/tmp
-/usr
-/usr/local
-/usr/local/go
-/usr/local/go/lib
-/usr/local/go/lib/time
-/usr/local/go/lib/time/zoneinfo.zip`},
- {prog: `
-package main
-
-import "testing"
-
-func TestSanity(t *testing.T) {
- if 1+1 != 2 {
- t.Error("uhh...")
- }
-}
-`, want: `=== RUN TestSanity
---- PASS: TestSanity (0.00s)
-PASS`},
-
- {prog: `
-package main
-
-func TestSanity(t *testing.T) {
- t.Error("uhh...")
-}
-
-func ExampleNotExecuted() {
- // Output: it should not run
-}
-`, want: "", errors: "prog.go:4:20: undefined: testing\n"},
-
- {prog: `
-package main
-
-import (
- "fmt"
- "testing"
-)
-
-func TestSanity(t *testing.T) {
- t.Error("uhh...")
-}
-
-func main() {
- fmt.Println("test")
-}
-`, want: "test"},
-
- {prog: `
-package main//comment
-
-import "fmt"
-
-func ExampleOutput() {
- fmt.Println("The output")
- // Output: The output
-}
-`, want: `=== RUN ExampleOutput
---- PASS: ExampleOutput (0.00s)
-PASS`},
-
- {prog: `
-package main//comment
-
-import "fmt"
-
-func ExampleUnorderedOutput() {
- fmt.Println("2")
- fmt.Println("1")
- fmt.Println("3")
- // Unordered output: 3
- // 2
- // 1
-}
-`, want: `=== RUN ExampleUnorderedOutput
---- PASS: ExampleUnorderedOutput (0.00s)
-PASS`},
-
- {prog: `
-package main
-
-import "fmt"
-
-func ExampleEmptyOutput() {
- // Output:
-}
-
-func ExampleEmptyOutputFail() {
- fmt.Println("1")
- // Output:
-}
-`, want: `=== RUN ExampleEmptyOutput
---- PASS: ExampleEmptyOutput (0.00s)
-=== RUN ExampleEmptyOutputFail
---- FAIL: ExampleEmptyOutputFail (0.00s)
-got:
-1
-want:
-
-FAIL`},
-
- // Run program without executing this example function.
- {prog: `
-package main
-
-func ExampleNoOutput() {
- panic(1)
-}
-`, want: `testing: warning: no tests to run
-PASS`},
-
- {prog: `
-package main
-
-import "fmt"
-
-func ExampleShouldNotRun() {
- fmt.Println("The output")
- // Output: The output
-}
-
-func main() {
- fmt.Println("Main")
-}
-`, want: "Main"},
-
- {prog: `
-package main
-
-import (
- "fmt"
- "os"
-)
-
-func main() {
- fmt.Fprintln(os.Stdout, "A")
- fmt.Fprintln(os.Stderr, "B")
- fmt.Fprintln(os.Stdout, "A")
- fmt.Fprintln(os.Stdout, "A")
-}
-`, want: "A\nB\nA\nA\n"},
-
- // Integration test for runtime.write fake timestamps.
- {prog: `
-package main
-
-import (
- "fmt"
- "os"
- "time"
-)
-
-func main() {
- fmt.Fprintln(os.Stdout, "A")
- fmt.Fprintln(os.Stderr, "B")
- fmt.Fprintln(os.Stdout, "A")
- fmt.Fprintln(os.Stdout, "A")
- time.Sleep(time.Second)
- fmt.Fprintln(os.Stderr, "B")
- time.Sleep(time.Second)
- fmt.Fprintln(os.Stdout, "A")
-}
-`, wantEvents: []Event{
- {"A\n", "stdout", 0},
- {"B\n", "stderr", time.Nanosecond},
- {"A\nA\n", "stdout", time.Nanosecond},
- {"B\n", "stderr", time.Second - 2*time.Nanosecond},
- {"A\n", "stdout", time.Second},
- }},
-
- // Test third-party imports:
- {prog: `
-package main
-import ("fmt"; "github.com/bradfitz/iter")
-func main() { for i := range iter.N(5) { fmt.Println(i) } }
-`, want: "0\n1\n2\n3\n4\n"},
-}
diff --git a/tests.go b/tests.go
new file mode 100644
index 0000000..437da2b
--- /dev/null
+++ b/tests.go
@@ -0,0 +1,444 @@
+// Copyright 2014 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Test tests are linked into the main binary and are run as part of
+// the Docker build step.
+
+package main
+
+import (
+ "fmt"
+ stdlog "log"
+ "os"
+ "reflect"
+ "strings"
+ "time"
+)
+
+type compileTest struct {
+ name string // test name
+ prog, want, errors string
+ withVet bool
+ wantEvents []Event
+ wantVetErrors string
+}
+
+func (s *server) test() {
+ if err := s.healthCheck(); err != nil {
+ stdlog.Fatal(err)
+ }
+
+ // Enable module downloads for testing:
+ defer func(old string) { os.Setenv("ALLOW_PLAY_MODULE_DOWNLOADS", old) }(os.Getenv("ALLOW_PLAY_MODULE_DOWNLOADS"))
+ os.Setenv("ALLOW_PLAY_MODULE_DOWNLOADS", "true")
+
+ for i, t := range tests {
+ fmt.Printf("testing case %d (%q)...\n", i, t.name)
+ resp, err := compileAndRun(&request{Body: t.prog, WithVet: t.withVet})
+ if err != nil {
+ stdlog.Fatal(err)
+ }
+ if t.wantEvents != nil {
+ if !reflect.DeepEqual(resp.Events, t.wantEvents) {
+ stdlog.Fatalf("resp.Events = %q, want %q", resp.Events, t.wantEvents)
+ }
+ continue
+ }
+ if t.errors != "" {
+ if resp.Errors != t.errors {
+ stdlog.Fatalf("resp.Errors = %q, want %q", resp.Errors, t.errors)
+ }
+ continue
+ }
+ if resp.Errors != "" {
+ stdlog.Fatal(resp.Errors)
+ }
+ if resp.VetErrors != t.wantVetErrors {
+ stdlog.Fatalf("resp.VetErrs = %q, want %q", resp.VetErrors, t.wantVetErrors)
+ }
+ if len(resp.Events) == 0 {
+ stdlog.Fatalf("unexpected output: %q, want %q", "", t.want)
+ }
+ var b strings.Builder
+ for _, e := range resp.Events {
+ b.WriteString(e.Message)
+ }
+ if !strings.Contains(b.String(), t.want) {
+ stdlog.Fatalf("unexpected output: %q, want %q", b.String(), t.want)
+ }
+ }
+ fmt.Println("OK")
+}
+
+var tests = []compileTest{
+ {
+ name: "timezones_available",
+ prog: `
+package main
+
+import "time"
+
+func main() {
+ loc, err := time.LoadLocation("America/New_York")
+ if err != nil {
+ panic(err.Error())
+ }
+ println(loc.String())
+}
+`, want: "America/New_York"},
+
+ {
+ name: "faketime_works",
+ prog: `
+package main
+
+import (
+ "fmt"
+ "time"
+)
+
+func main() {
+ fmt.Println(time.Now())
+}
+`, want: "2009-11-10 23:00:00 +0000 UTC"},
+
+ {
+ name: "faketime_tickers",
+ prog: `
+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)
+ }
+}
+`, want: "timers fired as expected"},
+
+ {
+ name: "old_tour_pkgs_in_gopath",
+ prog: `
+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")
+}
+`, want: "ok"},
+ {
+ name: "must_be_package_main",
+ prog: `
+package test
+
+func main() {
+ println("test")
+}
+`, want: "", errors: "package name must be main"},
+ {
+ name: "filesystem_contents",
+ prog: `
+package main
+
+import (
+ "fmt"
+ "os"
+ "path/filepath"
+)
+
+func main() {
+ filepath.Walk("/", func(path string, info os.FileInfo, err error) error {
+ fmt.Println(path)
+ return nil
+ })
+}
+`, want: `/
+/dev
+/dev/null
+/dev/random
+/dev/urandom
+/dev/zero
+/etc
+/etc/group
+/etc/hosts
+/etc/passwd
+/etc/resolv.conf
+/tmp
+/usr
+/usr/local
+/usr/local/go
+/usr/local/go/lib
+/usr/local/go/lib/time
+/usr/local/go/lib/time/zoneinfo.zip`},
+
+ {
+ name: "test_passes",
+ prog: `
+package main
+
+import "testing"
+
+func TestSanity(t *testing.T) {
+ if 1+1 != 2 {
+ t.Error("uhh...")
+ }
+}
+`, want: `=== RUN TestSanity
+--- PASS: TestSanity (0.00s)
+PASS`},
+
+ {
+ name: "test_without_import",
+ prog: `
+package main
+
+func TestSanity(t *testing.T) {
+ t.Error("uhh...")
+}
+
+func ExampleNotExecuted() {
+ // Output: it should not run
+}
+`, want: "", errors: "prog.go:4:20: undefined: testing\n"},
+
+ {
+ name: "test_with_import_ignored",
+ prog: `
+package main
+
+import (
+ "fmt"
+ "testing"
+)
+
+func TestSanity(t *testing.T) {
+ t.Error("uhh...")
+}
+
+func main() {
+ fmt.Println("test")
+}
+`, want: "test"},
+
+ {
+ name: "example_runs",
+ prog: `
+package main//comment
+
+import "fmt"
+
+func ExampleOutput() {
+ fmt.Println("The output")
+ // Output: The output
+}
+`, want: `=== RUN ExampleOutput
+--- PASS: ExampleOutput (0.00s)
+PASS`},
+
+ {
+ name: "example_unordered",
+ prog: `
+package main//comment
+
+import "fmt"
+
+func ExampleUnorderedOutput() {
+ fmt.Println("2")
+ fmt.Println("1")
+ fmt.Println("3")
+ // Unordered output: 3
+ // 2
+ // 1
+}
+`, want: `=== RUN ExampleUnorderedOutput
+--- PASS: ExampleUnorderedOutput (0.00s)
+PASS`},
+
+ {
+ name: "example_fail",
+ prog: `
+package main
+
+import "fmt"
+
+func ExampleEmptyOutput() {
+ // Output:
+}
+
+func ExampleEmptyOutputFail() {
+ fmt.Println("1")
+ // Output:
+}
+`, want: `=== RUN ExampleEmptyOutput
+--- PASS: ExampleEmptyOutput (0.00s)
+=== RUN ExampleEmptyOutputFail
+--- FAIL: ExampleEmptyOutputFail (0.00s)
+got:
+1
+want:
+
+FAIL`},
+
+ // Run program without executing this example function.
+ {
+ name: "example_no_output_skips_run",
+ prog: `
+package main
+
+func ExampleNoOutput() {
+ panic(1)
+}
+`, want: `testing: warning: no tests to run
+PASS`},
+
+ {
+ name: "example_output",
+ prog: `
+package main
+
+import "fmt"
+
+func ExampleShouldNotRun() {
+ fmt.Println("The output")
+ // Output: The output
+}
+
+func main() {
+ fmt.Println("Main")
+}
+`, want: "Main"},
+
+ {
+ name: "stdout_stderr_merge",
+ prog: `
+package main
+
+import (
+ "fmt"
+ "os"
+)
+
+func main() {
+ fmt.Fprintln(os.Stdout, "A")
+ fmt.Fprintln(os.Stderr, "B")
+ fmt.Fprintln(os.Stdout, "A")
+ fmt.Fprintln(os.Stdout, "A")
+}
+`, want: "A\nB\nA\nA\n"},
+
+ // Integration test for runtime.write fake timestamps.
+ {
+ name: "faketime_write_interaction",
+ prog: `
+package main
+
+import (
+ "fmt"
+ "os"
+ "time"
+)
+
+func main() {
+ fmt.Fprintln(os.Stdout, "A")
+ fmt.Fprintln(os.Stderr, "B")
+ fmt.Fprintln(os.Stdout, "A")
+ fmt.Fprintln(os.Stdout, "A")
+ time.Sleep(time.Second)
+ fmt.Fprintln(os.Stderr, "B")
+ time.Sleep(time.Second)
+ fmt.Fprintln(os.Stdout, "A")
+}
+`, wantEvents: []Event{
+ {"A\n", "stdout", 0},
+ {"B\n", "stderr", time.Nanosecond},
+ {"A\nA\n", "stdout", time.Nanosecond},
+ {"B\n", "stderr", time.Second - 2*time.Nanosecond},
+ {"A\n", "stdout", time.Second},
+ }},
+
+ {
+ name: "third_party_imports",
+ prog: `
+package main
+import ("fmt"; "github.com/bradfitz/iter")
+func main() { for i := range iter.N(5) { fmt.Println(i) } }
+`, want: "0\n1\n2\n3\n4\n"},
+
+ {
+ name: "compile_with_vet",
+ withVet: true,
+ wantVetErrors: "prog.go:5:2: Printf format %v reads arg #1, but call has 0 args\n",
+ prog: `
+package main
+import "fmt"
+func main() {
+ fmt.Printf("hi %v")
+}
+`,
+ },
+
+ {
+ name: "compile_without_vet",
+ withVet: false,
+ prog: `
+package main
+import "fmt"
+func main() {
+ fmt.Printf("hi %v")
+}
+`,
+ },
+
+ {
+ name: "compile_modules_with_vet",
+ withVet: true,
+ wantVetErrors: "prog.go:6:2: Printf format %v reads arg #1, but call has 0 args\n",
+ prog: `
+package main
+import ("fmt"; "github.com/bradfitz/iter")
+func main() {
+ for i := range iter.N(5) { fmt.Println(i) }
+ fmt.Printf("hi %v")
+}
+`,
+ },
+}
diff --git a/vet.go b/vet.go
index b4925db..78df95c 100644
--- a/vet.go
+++ b/vet.go
@@ -16,6 +16,11 @@
// vetCheck runs the "vet" tool on the source code in req.Body.
// In case of no errors it returns an empty, non-nil *response.
// Otherwise &response.Errors contains found errors.
+//
+// Deprecated: this is the handler for the legacy /vet endpoint; use
+// the /compile (compileAndRun) handler instead with the WithVet
+// boolean set. This code path doesn't support modules and only exists
+// as a temporary compatiblity bridge to older javascript clients.
func vetCheck(req *request) (*response, error) {
tmpDir, err := ioutil.TempDir("", "vet")
if err != nil {
@@ -23,23 +28,44 @@
}
defer os.RemoveAll(tmpDir)
- in := filepath.Join(tmpDir, "main.go")
+ in := filepath.Join(tmpDir, progName)
if err := ioutil.WriteFile(in, []byte(req.Body), 0400); err != nil {
return nil, fmt.Errorf("error creating temp file %q: %v", in, err)
}
+ const useModules = false // legacy handler; no modules (see func comment)
+ vetOutput, err := vetCheckInDir(tmpDir, os.Getenv("GOPATH"), useModules)
+ if err != nil {
+ // This is about errors running vet, not vet returning output.
+ return nil, err
+ }
+ return &response{Errors: vetOutput}, nil
+}
+// vetCheckInDir runs go vet in the provided directory, using the
+// provided GOPATH value, and whether modules are enabled. The
+// returned error is only about whether go vet was able to run, not
+// whether vet reported problem. The returned value is ("", nil) if
+// vet successfully found nothing, and (non-empty, nil) if vet ran and
+// found issues.
+func vetCheckInDir(dir, goPath string, modules bool) (output string, execErr error) {
+ in := filepath.Join(dir, progName)
cmd := exec.Command("go", "vet", in)
// Linux go binary is not built with CGO_ENABLED=0.
// Prevent vet to compile packages in cgo mode.
// See #26307.
- cmd.Env = append(os.Environ(), "CGO_ENABLED=0")
+ cmd.Env = append(os.Environ(), "CGO_ENABLED=0", "GOPATH="+goPath)
+ if modules {
+ cmd.Env = append(cmd.Env,
+ "GO111MODULE=on",
+ "GOPROXY=https://proxy.golang.org",
+ )
+ }
out, err := cmd.CombinedOutput()
if err == nil {
- return &response{}, nil
+ return "", nil
}
-
if _, ok := err.(*exec.ExitError); !ok {
- return nil, fmt.Errorf("error vetting go source: %v", err)
+ return "", fmt.Errorf("error vetting go source: %v", err)
}
// Rewrite compiler errors to refer to progName
@@ -50,5 +76,5 @@
// message before any compile errors; strip it.
errs = strings.Replace(errs, "# command-line-arguments\n", "", 1)
- return &response{Errors: errs}, nil
+ return errs, nil
}