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>
This commit is contained in:
Brad Fitzpatrick 2019-05-10 18:19:28 +00:00
Родитель 046e863671
Коммит 1cc919a729
6 изменённых файлов: 537 добавлений и 373 удалений

Просмотреть файл

@ -26,10 +26,19 @@ Building the playground Docker container takes more than the default 10 minute t
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

3
go.mod
Просмотреть файл

@ -5,5 +5,6 @@ go 1.12
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
)

10
go.sum
Просмотреть файл

@ -6,12 +6,14 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
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-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73r
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-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJ
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/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxb
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=

Просмотреть файл

@ -19,12 +19,10 @@ import (
"go/token"
"io"
"io/ioutil"
stdlog "log"
"net/http"
"os"
"os/exec"
"path/filepath"
"reflect"
"runtime"
"strconv"
"strings"
@ -38,7 +36,8 @@ import (
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 @@ const (
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 @@ type response struct {
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 @@ func (s *server) commandHandler(cachePrefix string, cmdFunc func(*request) (*res
// 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 isTest(name, prefix string) bool {
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 @@ func getTestProg(src []byte) []byte {
}
// 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 @@ func compileAndRun(req *request) (*response, error) {
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 @@ func compileAndRun(req *request) (*response, error) {
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 @@ func compileAndRun(req *request) (*response, error) {
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 @@ import "fmt"
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"},
}

444
tests.go Normal file
Просмотреть файл

@ -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")
}
`,
},
}

38
vet.go
Просмотреть файл

@ -16,6 +16,11 @@ import (
// 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 @@ func vetCheck(req *request) (*response, error) {
}
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 @@ func vetCheck(req *request) (*response, error) {
// message before any compile errors; strip it.
errs = strings.Replace(errs, "# command-line-arguments\n", "", 1)
return &response{Errors: errs}, nil
return errs, nil
}