зеркало из https://github.com/golang/playground.git
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:
Родитель
046e863671
Коммит
1cc919a729
11
README.md
11
README.md
|
@ -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
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
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=
|
||||
|
|
404
sandbox.go
404
sandbox.go
|
@ -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"},
|
||||
}
|
||||
|
|
|
@ -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
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
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче