Permalink
Browse files

Use temporary file rather than os.Pipe for testing examples. (#590)

This change works around the issues discussed in #529 by using a
temporary file for capturing the stdout of a runnable example,
rather than os.Pipe. This allows examples to work in cases
where they would previously stall.

ioutil.ReadFile and its supporting functions are copied from
ioutil into testing, in order to avoid having "testing" package
import extra packages that the real "testing" package does not.

gopherjs tool testFuncs.load does not take into account the overridden
natives, see https://github.com/gopherjs/gopherjs/blob/b9bcb1da229a59cc1e1d168401662cb6450aae08/tool.go#L827.
So we still need to print the expected output for the example to pass.

Given there are only 2 disabled examples, it's easier to just do this
in short term. But it might be good to eventually improve code for
overriding natives so that examples are properly overridden as well.

Regenerate compiler/natives.
  • Loading branch information...
flimzy authored and dmitshur committed Feb 27, 2017
1 parent 34726b3 commit 7d94d732be268c4904141f8b8604eb2d6d6544a1
View
@@ -18,7 +18,7 @@ test:
- go tool vet *.go # Go package in root directory.
- for d in */; do echo $d; done | grep -v tests/ | grep -v third_party/ | xargs go tool vet # All subdirectories except "tests", "third_party".
- >
- gopherjs test --short --minify --run='^Test'
+ gopherjs test --short --minify
github.com/gopherjs/gopherjs/tests
github.com/gopherjs/gopherjs/tests/main
github.com/gopherjs/gopherjs/js

Large diffs are not rendered by default.

Oops, something went wrong.
@@ -0,0 +1,14 @@
+// +build js
+
+package cookiejar_test
+
+import "fmt"
+
+func ExampleNew() {
+ // network access not supported by GopherJS, and this test depends on httptest.NewServer
+
+ fmt.Println(`After 1st request:
+ Flavor: Chocolate Chip
+After 2nd request:
+ Flavor: Oatmeal Raisin`)
+}
@@ -0,0 +1,14 @@
+// +build js
+
+package reflect_test
+
+import "fmt"
+
+func ExampleStructOf() {
+ // GopherJS does not implement reflect.addReflectOff needed for this test.
+ // See https://github.com/gopherjs/gopherjs/issues/499
+
+ fmt.Println(`value: &{Height:0.4 Age:2}
+json: {"height":0.4,"age":2}
+value: &{Height:1.5 Age:10}`)
+}
@@ -0,0 +1,70 @@
+// +build js
+
+package testing
+
+import (
+ "fmt"
+ "os"
+ "strings"
+ "time"
+)
+
+func runExample(eg InternalExample) (ok bool) {
+ if *chatty {
+ fmt.Printf("=== RUN %s\n", eg.Name)
+ }
+
+ // Capture stdout.
+ stdout := os.Stdout
+ w, err := tempFile("." + eg.Name + ".stdout.")
+ if err != nil {
+ fmt.Fprintln(os.Stderr, err)
+ os.Exit(1)
+ }
+ os.Stdout = w
+
+ start := time.Now()
+ ok = true
+
+ // Clean up in a deferred call so we can recover if the example panics.
+ defer func() {
+ dstr := fmtDuration(time.Now().Sub(start))
+
+ // Close file, restore stdout, get output.
+ w.Close()
+ os.Stdout = stdout
+ out, readFileErr := readFile(w.Name())
+ _ = os.Remove(w.Name())
+ if readFileErr != nil {
+ fmt.Fprintf(os.Stderr, "testing: reading stdout file: %v\n", readFileErr)
+ os.Exit(1)
+ }
+
+ var fail string
+ err := recover()
+ got := strings.TrimSpace(out)
+ want := strings.TrimSpace(eg.Output)
+ if eg.Unordered {
+ if sortLines(got) != sortLines(want) && err == nil {
+ fail = fmt.Sprintf("got:\n%s\nwant (unordered):\n%s\n", out, eg.Output)
+ }
+ } else {
+ if got != want && err == nil {
+ fail = fmt.Sprintf("got:\n%s\nwant:\n%s\n", got, want)
+ }
+ }
+ if fail != "" || err != nil {
+ fmt.Printf("--- FAIL: %s (%s)\n%s", eg.Name, dstr, fail)
+ ok = false
+ } else if *chatty {
+ fmt.Printf("--- PASS: %s (%s)\n", eg.Name, dstr)
+ }
+ if err != nil {
+ panic(err)
+ }
+ }()
+
+ // Run example.
+ eg.F()
+ return
+}
@@ -0,0 +1,66 @@
+// +build js
+
+package testing
+
+import (
+ "bytes"
+ "io"
+ "os"
+ "strconv"
+ "sync"
+ "time"
+)
+
+var rand uint32
+var randmu sync.Mutex
+
+func reseed() uint32 {
+ return uint32(time.Now().UnixNano() + int64(os.Getpid()))
+}
+
+func nextSuffix() string {
+ randmu.Lock()
+ r := rand
+ if r == 0 {
+ r = reseed()
+ }
+ r = r*1664525 + 1013904223 // constants from Numerical Recipes
+ rand = r
+ randmu.Unlock()
+ return strconv.Itoa(int(1e9 + r%1e9))[1:]
+}
+
+// A functional copy of ioutil.TempFile, to avoid extra imports.
+func tempFile(prefix string) (f *os.File, err error) {
+ dir := os.TempDir()
+
+ nconflict := 0
+ for i := 0; i < 10000; i++ {
+ name := dir + string(os.PathSeparator) + prefix + nextSuffix()
+ f, err = os.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600)
+ if os.IsExist(err) {
+ if nconflict++; nconflict > 10 {
+ randmu.Lock()
+ rand = reseed()
+ randmu.Unlock()
+ }
+ continue
+ }
+ break
+ }
+ return
+}
+
+func readFile(filename string) (string, error) {
+ f, err := os.Open(filename)
+ if err != nil {
+ return "", err
+ }
+ defer f.Close()
+ var buf bytes.Buffer
+ _, err = io.Copy(&buf, f)
+ if err != nil {
+ return "", err
+ }
+ return buf.String(), nil
+}

0 comments on commit 7d94d73

Please sign in to comment.