| // 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 ( | |
| "context" | |
| "fmt" | |
| stdlog "log" | |
| "net" | |
| "os" | |
| "reflect" | |
| "strings" | |
| "time" | |
| ) | |
| type compileTest struct { | |
| name string // test name | |
| prog, want, errors string | |
| wantFunc func(got string) error // alternative to want | |
| withVet bool | |
| wantEvents []Event | |
| wantVetErrors string | |
| } | |
| func (s *server) test() { | |
| if _, err := net.ResolveIPAddr("ip", "sandbox_dev.sandnet."); err != nil { | |
| log.Fatalf("sandbox_dev.sandnet not available") | |
| } | |
| os.Setenv("DEBUG_FORCE_GVISOR", "1") | |
| os.Setenv("SANDBOX_BACKEND_URL", "http://sandbox_dev.sandnet/run") | |
| s.runTests() | |
| } | |
| func (s *server) runTests() { | |
| if err := s.healthCheck(context.Background()); err != nil { | |
| stdlog.Fatal(err) | |
| } | |
| failed := false | |
| for i, t := range tests { | |
| stdlog.Printf("testing case %d (%q)...\n", i, t.name) | |
| resp, err := compileAndRun(context.Background(), &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.Printf("resp.Events = %q, want %q", resp.Events, t.wantEvents) | |
| failed = true | |
| } | |
| continue | |
| } | |
| if t.errors != "" { | |
| if resp.Errors != t.errors { | |
| stdlog.Printf("resp.Errors = %q, want %q", resp.Errors, t.errors) | |
| failed = true | |
| } | |
| continue | |
| } | |
| if resp.Errors != "" { | |
| stdlog.Printf("resp.Errors = %q, want %q", resp.Errors, t.errors) | |
| failed = true | |
| continue | |
| } | |
| if resp.VetErrors != t.wantVetErrors { | |
| stdlog.Printf("resp.VetErrs = %q, want %q", resp.VetErrors, t.wantVetErrors) | |
| failed = true | |
| continue | |
| } | |
| if t.withVet && (resp.VetErrors != "") == resp.VetOK { | |
| stdlog.Printf("resp.VetErrs & VetOK inconsistent; VetErrs = %q; VetOK = %v", resp.VetErrors, resp.VetOK) | |
| failed = true | |
| continue | |
| } | |
| if len(resp.Events) == 0 { | |
| stdlog.Printf("unexpected output: %q, want %q", "", t.want) | |
| failed = true | |
| continue | |
| } | |
| var b strings.Builder | |
| for _, e := range resp.Events { | |
| b.WriteString(e.Message) | |
| } | |
| if t.wantFunc != nil { | |
| if err := t.wantFunc(b.String()); err != nil { | |
| stdlog.Printf("%v\n", err) | |
| failed = true | |
| } | |
| } else { | |
| if !strings.Contains(b.String(), t.want) { | |
| stdlog.Printf("unexpected output: %q, want %q", b.String(), t.want) | |
| failed = true | |
| } | |
| } | |
| } | |
| if failed { | |
| stdlog.Fatalf("FAILED") | |
| } | |
| 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: "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 { | |
| if path == "/proc" || path == "/sys" { | |
| return filepath.SkipDir | |
| } | |
| fmt.Println(path) | |
| return nil | |
| }) | |
| } | |
| `, wantFunc: func(got string) error { | |
| // The environment for the old nacl sandbox: | |
| if strings.TrimSpace(got) == `/ | |
| /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` { | |
| return nil | |
| } | |
| have := map[string]bool{} | |
| for f := range strings.SplitSeq(got, "\n") { | |
| have[f] = true | |
| } | |
| for _, expect := range []string{ | |
| "/.dockerenv", | |
| "/etc/hostname", | |
| "/dev/zero", | |
| "/lib/ld-linux-x86-64.so.2", | |
| "/lib/libc.so.6", | |
| "/etc/nsswitch.conf", | |
| "/bin/env", | |
| "/tmpfs", | |
| } { | |
| if !have[expect] { | |
| return fmt.Errorf("missing expected sandbox file %q; got:\n%s", expect, got) | |
| } | |
| } | |
| return nil | |
| }, | |
| }, | |
| { | |
| 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_test.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: fmt.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: fmt.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") | |
| } | |
| `, | |
| }, | |
| { | |
| name: "multi_file_basic", | |
| prog: ` | |
| package main | |
| const foo = "bar" | |
| -- two.go -- | |
| package main | |
| func main() { | |
| println(foo) | |
| } | |
| `, | |
| wantEvents: []Event{ | |
| {"bar\n", "stderr", 0}, | |
| }, | |
| }, | |
| { | |
| name: "multi_file_use_package", | |
| withVet: true, | |
| prog: ` | |
| package main | |
| import "play.test/foo" | |
| func main() { | |
| foo.Hello() | |
| } | |
| -- go.mod -- | |
| module play.test | |
| -- foo/foo.go -- | |
| package foo | |
| import "fmt" | |
| func Hello() { fmt.Println("hello world") } | |
| `, | |
| }, | |
| { | |
| name: "timeouts_handled_gracefully", | |
| prog: ` | |
| package main | |
| import ( | |
| "time" | |
| ) | |
| func main() { | |
| c := make(chan struct{}) | |
| go func() { | |
| defer close(c) | |
| for { | |
| time.Sleep(10 * time.Millisecond) | |
| } | |
| }() | |
| <-c | |
| } | |
| `, want: "timeout running program"}, | |
| { | |
| name: "timezone_info_exists", | |
| prog: ` | |
| package main | |
| import ( | |
| "fmt" | |
| "time" | |
| ) | |
| func main() { | |
| loc, _ := time.LoadLocation("Europe/Berlin") | |
| // This will look for the name CEST in the Europe/Berlin time zone. | |
| const longForm = "Jan 2, 2006 at 3:04pm (MST)" | |
| t, _ := time.ParseInLocation(longForm, "Jul 9, 2012 at 5:02am (CEST)", loc) | |
| fmt.Println(t) | |
| // Note: without explicit zone, returns time in given location. | |
| const shortForm = "2006-Jan-02" | |
| t, _ = time.ParseInLocation(shortForm, "2012-Jul-09", loc) | |
| fmt.Println(t) | |
| } | |
| `, want: "2012-07-09 05:02:00 +0200 CEST\n2012-07-09 00:00:00 +0200 CEST\n"}, | |
| { | |
| name: "cgo_enabled_0", | |
| prog: ` | |
| package main | |
| import ( | |
| "fmt" | |
| "net" | |
| ) | |
| func main() { | |
| fmt.Println(net.ParseIP("1.2.3.4")) | |
| } | |
| `, want: "1.2.3.4\n"}, | |
| { | |
| name: "fuzz_executed", | |
| prog: ` | |
| package main | |
| import "testing" | |
| func FuzzSanity(f *testing.F) { | |
| f.Add("a") | |
| f.Fuzz(func(t *testing.T, v string) { | |
| }) | |
| } | |
| `, want: `=== RUN FuzzSanity | |
| === RUN FuzzSanity/seed#0 | |
| --- PASS: FuzzSanity (0.00s) | |
| --- PASS: FuzzSanity/seed#0 (0.00s) | |
| PASS`}, | |
| { | |
| name: "test_main", | |
| prog: ` | |
| package main | |
| import ( | |
| "os" | |
| "testing" | |
| ) | |
| func TestMain(m *testing.M) { | |
| os.Exit(m.Run()) | |
| }`, want: `testing: warning: no tests to run | |
| PASS`, | |
| }, | |
| { | |
| name: "multiple_files_no_banner", | |
| prog: ` | |
| package main | |
| func main() { | |
| print() | |
| } | |
| -- foo.go -- | |
| package main | |
| import "fmt" | |
| func print() { | |
| =fmt.Println("Hello, playground") | |
| } | |
| `, errors: `./foo.go:6:2: syntax error: unexpected =, expected } | |
| `, | |
| }, | |
| { | |
| name: "multi_files_tests", | |
| prog: ` | |
| package main | |
| import ( | |
| "testing" | |
| _ "play.ground/pkg" | |
| ) | |
| func TestFoo(t *testing.T) { | |
| } | |
| -- go.mod -- | |
| module play.ground | |
| -- pkg/x.go -- | |
| package pkg | |
| `, want: `=== RUN TestFoo | |
| --- PASS: TestFoo (0.00s) | |
| PASS`}, | |
| } |