Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add functional tests, fix unset exit codes on panics #42

Merged
merged 16 commits into from
Feb 14, 2023
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,6 @@ jobs:
- name: Install cleanenv
run: go install ./cmd/cleanenv
- name: Test
run: cleanenv -remove-prefix GITHUB_ -remove-prefix JAVA_ -- go test -v -race ./...
run: cleanenv -remove-prefix GITHUB_ -remove-prefix JAVA_ -- go test -v -race -timeout 2m ./...
- name: Install
run: go install
7 changes: 6 additions & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,12 @@
}).catch((err) => {
console.error(err);
});
await go.run(inst);
try {
await go.run(inst);
} catch(e) {
exitCode = 1
console.error(e)
}
document.getElementById("doneButton").disabled = false;
})();
</script>
Expand Down
63 changes: 37 additions & 26 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,44 +24,48 @@ import (
"github.com/chromedp/chromedp"
)

var (
cpuProfile *string
coverageProfile *string
)

func main() {
// NOTE: Since `os.Exit` will cause the process to exit, this defer
// must be at the bottom of the defer stack to allow all other defer calls to
// be called first.
exitCode := 0
exitCode := run(os.Args, os.Stderr, flag.CommandLine)
os.Exit(exitCode)
}

func run(args []string, errOutput io.Writer, flagSet *flag.FlagSet) (exitCode int) {
agnivade marked this conversation as resolved.
Show resolved Hide resolved
logger := log.New(errOutput, "[wasmbrowsertest]: ", log.LstdFlags|log.Lshortfile)
defer func() {
if exitCode != 0 {
os.Exit(exitCode)
r := recover()
if r != nil {
logger.Printf("Panicked: %+v", r)
exitCode = 1
}
}()

logger := log.New(os.Stderr, "[wasmbrowsertest]: ", log.LstdFlags|log.Lshortfile)
if len(os.Args) < 2 {
logger.Fatal("Please pass a wasm file as a parameter")
if len(args) < 2 {
logger.Println("Please pass a wasm file as a parameter")
return 1
}

cpuProfile = flag.String("test.cpuprofile", "", "")
coverageProfile = flag.String("test.coverprofile", "", "")
cpuProfile := flagSet.String("test.cpuprofile", "", "")
coverageProfile := flagSet.String("test.coverprofile", "", "")

wasmFile := os.Args[1]
wasmFile := args[1]
ext := path.Ext(wasmFile)
// net/http code does not take js/wasm path if it is a .test binary.
if ext == ".test" {
wasmFile = strings.Replace(wasmFile, ext, ".wasm", -1)
err := copyFile(os.Args[1], wasmFile)
err := copyFile(args[1], wasmFile)
if err != nil {
logger.Fatal(err)
logger.Println(err)
return 1
}
defer os.Remove(wasmFile)
os.Args[1] = wasmFile
args[1] = wasmFile
}

passon := gentleParse(flag.CommandLine, os.Args[2:])
passon, err := gentleParse(flagSet, args[2:])
if err != nil {
logger.Println(err)
return 1
}
passon = append([]string{wasmFile}, passon...)
if *coverageProfile != "" {
passon = append(passon, "-test.coverprofile="+*coverageProfile)
Expand All @@ -70,21 +74,25 @@ func main() {
// Need to generate a random port every time for tests in parallel to run.
l, err := net.Listen("tcp", "localhost:")
if err != nil {
logger.Fatal(err)
logger.Println(err)
return 1
}
tcpL, ok := l.(*net.TCPListener)
if !ok {
logger.Fatal("net.Listen did not return a TCPListener")
logger.Println("net.Listen did not return a TCPListener")
return 1
}
_, port, err := net.SplitHostPort(tcpL.Addr().String())
if err != nil {
logger.Fatal(err)
logger.Println(err)
return 1
}

// Setup web server.
handler, err := NewWASMServer(wasmFile, passon, *coverageProfile, logger)
if err != nil {
logger.Fatal(err)
logger.Println(err)
return 1
}
httpServer := &http.Server{
Handler: handler,
Expand Down Expand Up @@ -116,7 +124,7 @@ func main() {

done := make(chan struct{})
go func() {
err = httpServer.Serve(l)
err := httpServer.Serve(l)
if err != http.ErrServerClosed {
logger.Println(err)
}
Expand Down Expand Up @@ -167,6 +175,8 @@ func main() {

err = chromedp.Run(ctx, tasks...)
if err != nil {
// Browser did not exit cleanly. Likely failed with an uncaught error.
exitCode = 1
logger.Println(err)
agnivade marked this conversation as resolved.
Show resolved Hide resolved
}
if exitCode != 0 {
Expand All @@ -181,6 +191,7 @@ func main() {
logger.Println(err)
}
<-done
return exitCode
}

func copyFile(src, dst string) error {
Expand Down
194 changes: 194 additions & 0 deletions main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
package main

import (
"bytes"
"flag"
"io"
"os"
"os/exec"
"path/filepath"
"testing"
)

func testRun(t *testing.T, wasmFile string, flags ...string) ([]byte, int) {
var logs bytes.Buffer
output := io.MultiWriter(testLogger(t), &logs)
exitCode := run(append([]string{"go_js_wasm_exec", wasmFile, "-test.v"}, flags...), output, testFlagSet(t))
return logs.Bytes(), exitCode
}

func testFlagSet(t *testing.T) *flag.FlagSet {
return flag.NewFlagSet("wasmbrowsertest", flag.ContinueOnError)
}
agnivade marked this conversation as resolved.
Show resolved Hide resolved

type testWriter struct {
agnivade marked this conversation as resolved.
Show resolved Hide resolved
testingT *testing.T
}

func testLogger(t *testing.T) io.Writer {
return &testWriter{t}
}

func (w *testWriter) Write(b []byte) (int, error) {
w.testingT.Helper()
w.testingT.Log(string(b))
return len(b), nil
}

// writeFile creates a file at $baseDir/$path with the given contents, where 'path' is slash separated
func writeFile(t *testing.T, baseDir, path, contents string) {
t.Helper()
path = filepath.FromSlash(path)
fullPath := filepath.Join(baseDir, path)
err := os.MkdirAll(filepath.Dir(fullPath), 0755)
if err != nil {
t.Fatal("Failed to create file's base directory:", err)
}
err = os.WriteFile(fullPath, []byte(contents), 0600)
if err != nil {
t.Fatal("Failed to create file:", err)
}
}

// buildTestWasm builds the given Go package's test binary and returns the output Wasm file
func buildTestWasm(t *testing.T, path string) string {
t.Helper()
outputFile := filepath.Join(t.TempDir(), "out.wasm")
cmd := exec.Command("go", "test", "-c", "-o", outputFile, ".")
cmd.Dir = path
cmd.Env = append(os.Environ(),
"GOOS=js",
"GOARCH=wasm",
)
output, err := cmd.CombinedOutput()
if len(output) > 0 {
t.Log(string(output))
}
if err != nil {
t.Fatal("Failed to build Wasm binary:", err)
}
return outputFile
}

func TestRunPassing(t *testing.T) {
t.Parallel()
agnivade marked this conversation as resolved.
Show resolved Hide resolved
dir := t.TempDir()
writeFile(t, dir, "go.mod", `
module foo
`)
writeFile(t, dir, "foo_test.go", `
package foo

import "testing"

func TestFoo(t *testing.T) {
if false {
t.Errorf("foo failed")
}
}
`)
wasmFile := buildTestWasm(t, dir)

_, exitCode := testRun(t, wasmFile, "-test.v")
if exitCode != 0 {
t.Errorf("Test run should pass, got exit code %d", exitCode)
}
}

func TestRunFailing(t *testing.T) {
t.Parallel()
dir := t.TempDir()
writeFile(t, dir, "go.mod", `
module foo
`)
writeFile(t, dir, "foo_test.go", `
package foo

import "testing"

func TestFoo(t *testing.T) {
t.Errorf("foo failed")
}
`)
wasmFile := buildTestWasm(t, dir)

_, exitCode := testRun(t, wasmFile)
if exitCode != 1 {
t.Errorf("Test run should fail, got exit code %d", exitCode)
}
}

func TestRunPanicFails(t *testing.T) {
t.Parallel()
dir := t.TempDir()
writeFile(t, dir, "go.mod", `
module foo
`)
writeFile(t, dir, "foo_test.go", `
package foo

import "testing"

func TestFooPanic(t *testing.T) {
panic("foo failed")
}
`)
wasmFile := buildTestWasm(t, dir)

_, exitCode := testRun(t, wasmFile)
if exitCode != 1 {
t.Errorf("Test run should fail, got exit code %d", exitCode)
}
}

func TestRunGoroutinePanicFails(t *testing.T) {
t.Parallel()
dir := t.TempDir()
writeFile(t, dir, "go.mod", `
module foo
`)
writeFile(t, dir, "foo_test.go", `
package foo

import "testing"

func TestFooGoroutinePanic(t *testing.T) {
go panic("foo failed")
}
`)
wasmFile := buildTestWasm(t, dir)

_, exitCode := testRun(t, wasmFile)
if exitCode != 1 {
t.Errorf("Test run should fail, got exit code %d", exitCode)
}
}

func TestRunNextEventLoopPanic(t *testing.T) {
t.Parallel()
dir := t.TempDir()
writeFile(t, dir, "go.mod", `
module foo
`)
writeFile(t, dir, "foo_test.go", `
package foo

import (
"syscall/js"
"testing"
)

func TestFooNextEventLoopPanic(t *testing.T) {
js.Global().Call("setTimeout", js.FuncOf(func(js.Value, []js.Value) interface{} {
panic("bad")
return nil
}), 0)
}
`)
wasmFile := buildTestWasm(t, dir)

_, exitCode := testRun(t, wasmFile)
if exitCode != 1 {
t.Errorf("Test run should fail, got exit code %d", exitCode)
}
}
11 changes: 5 additions & 6 deletions parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,15 @@ import (
"flag"
"fmt"
"io/ioutil"
"os"
"strings"
)

// gentleParse takes a flag.FlagSet, calls Parse to get its flags parsed,
// and collects the arguments the FlagSet does not recognize, returning
// the collected list.
func gentleParse(flagset *flag.FlagSet, args []string) []string {
func gentleParse(flagset *flag.FlagSet, args []string) ([]string, error) {
if len(args) == 0 {
return nil
return nil, nil
}
const prefix = "flag provided but not defined: "

Expand Down Expand Up @@ -48,7 +47,7 @@ func gentleParse(flagset *flag.FlagSet, args []string) []string {
fmt.Fprintf(w, "%s\n", err)
flagset.SetOutput(w)
flagset.Usage()
os.Exit(1)
return nil, err
}

// Check if the call to flagset.Parse ate a "--". If so, we're done
Expand All @@ -58,10 +57,10 @@ func gentleParse(flagset *flag.FlagSet, args []string) []string {
lastabsorbed := next[lastabsorbedpos]
if lastabsorbed == "--" {
r = append(r, "--") // return the "--" too.
return append(r, flagset.Args()...)
return append(r, flagset.Args()...), nil
}
}
next = flagset.Args()
}
return r
return r, nil
}
Loading