From bb77d443e302d06f0d2462336bc2765942e0e862 Mon Sep 17 00:00:00 2001 From: John Starich Date: Tue, 7 Feb 2023 08:24:19 -0600 Subject: [PATCH] Fix Windows CI test failures by cleaning env vars (#43) * Fix Windows CI test failures by cleaning env vars Add script to clean the test environment variables. Uses a Go script for cross-OS portability. * Move cleanenv to publicly installable and documented command * Move error docs to their own h2 section * Address PR docs comments * Add cleanenv package doc * Renamed App.Out to App.StdOut to address comments --- .github/workflows/ci.yml | 4 +- README.md | 25 +++++++++ cmd/cleanenv/main.go | 98 +++++++++++++++++++++++++++++++++ cmd/cleanenv/main_test.go | 113 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 239 insertions(+), 1 deletion(-) create mode 100644 cmd/cleanenv/main.go create mode 100644 cmd/cleanenv/main_test.go diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 86dda6d..02ae1c3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,7 +18,9 @@ jobs: run: | go vet . gofmt -l -s -w . + - name: Install cleanenv + run: go install ./cmd/cleanenv - name: Test - run: go test -v + run: cleanenv -remove-prefix GITHUB_ -remove-prefix JAVA_ -- go test -v -race ./... - name: Install run: go install diff --git a/README.md b/README.md index 8ae8963..1f192a8 100644 --- a/README.md +++ b/README.md @@ -113,3 +113,28 @@ This tool uses the [ChromeDP](https://chromedevtools.github.io/devtools-protocol ### Why not firefox ? Great question. The initial idea was to use a Selenium API and drive any browser to run the tests. But unfortunately, geckodriver does not support the ability to capture console logs - https://github.com/mozilla/geckodriver/issues/284. Hence, the shift to use the ChromeDP protocol circumvents the need to have any external driver binary and just have a browser installed in the machine. + +## Errors + +### `total length of command line and environment variables exceeds limit` + +If the error `total length of command line and environment variables exceeds limit` appears, then +the current environment variables' total size has exceeded the maximum when executing Go Wasm binaries. + +To resolve this issue, install `cleanenv` and use it to prefix your command. + +For example, if these commands are used: +```bash +export GOOS=js GOARCH=wasm +go test -cover ./... +``` +The new commands should be the following: +```bash +go install github.com/agnivade/wasmbrowsertest/cmd/cleanenv@latest + +export GOOS=js GOARCH=wasm +cleanenv -remove-prefix GITHUB_ -- go test -cover ./... +``` + +The `cleanenv` command above removes all environment variables prefixed with `GITHUB_` before running the command after the `--`. +The `-remove-prefix` flag can be repeated multiple times to remove even more environment variables. diff --git a/cmd/cleanenv/main.go b/cmd/cleanenv/main.go new file mode 100644 index 0000000..b96cbdb --- /dev/null +++ b/cmd/cleanenv/main.go @@ -0,0 +1,98 @@ +// Command cleanenv removes all environment variables that match given prefixes before running its arguments as a command. +// +// For example, this is useful in GitHub Actions: +// +// export GOOS=js GOARCH=wasm +// cleanenv -remove-prefix GITHUB_ -- go test -cover ./... +// +// The '-remove-prefix' flag can be repeated multiple times to remove even more environment variables. +package main + +import ( + "errors" + "flag" + "fmt" + "io" + "os" + "os/exec" + "strings" +) + +func main() { + app := App{ + Args: os.Args[1:], + Env: os.Environ(), + StdOut: os.Stdout, + ErrOut: os.Stderr, + } + err := app.Run() + if err != nil { + fmt.Fprintln(app.ErrOut, err) + exitCode := 1 + var exitErr *exec.ExitError + if errors.As(err, &exitErr) { + exitCode = exitErr.ExitCode() + } + os.Exit(exitCode) + } +} + +type App struct { + Args []string + Env []string + StdOut, ErrOut io.Writer +} + +func (a App) Run() error { + set := flag.NewFlagSet("cleanenv", flag.ContinueOnError) + var removePrefixes StringSliceFlag + set.Var(&removePrefixes, "remove-prefix", "Remove one or more environment variables with the given prefixes.") + if err := set.Parse(a.Args); err != nil { + return err + } + + var cleanEnv []string + for _, keyValue := range a.Env { + tokens := strings.SplitN(keyValue, "=", 2) + if allowEnvName(tokens[0], removePrefixes) { + cleanEnv = append(cleanEnv, keyValue) + } + } + + arg0, argv, err := splitArgs(set.Args()) + if err != nil { + return err + } + cmd := exec.Command(arg0, argv...) + cmd.Env = cleanEnv + cmd.Stdout = a.StdOut + cmd.Stderr = a.ErrOut + return cmd.Run() +} + +type StringSliceFlag []string + +func (s *StringSliceFlag) Set(value string) error { + *s = append(*s, value) + return nil +} + +func (s *StringSliceFlag) String() string { + return strings.Join(*s, ", ") +} + +func allowEnvName(name string, removePrefixes []string) bool { + for _, prefix := range removePrefixes { + if strings.HasPrefix(name, prefix) { + return false + } + } + return true +} + +func splitArgs(args []string) (string, []string, error) { + if len(args) == 0 { + return "", nil, errors.New("not enough args to run a command") + } + return args[0], args[1:], nil +} diff --git a/cmd/cleanenv/main_test.go b/cmd/cleanenv/main_test.go new file mode 100644 index 0000000..cda2bb6 --- /dev/null +++ b/cmd/cleanenv/main_test.go @@ -0,0 +1,113 @@ +package main + +import ( + "bytes" + "strings" + "testing" +) + +func TestRun(t *testing.T) { + t.Parallel() + const bashPrintCleanVars = `env | grep CLEAN_ | sort | tr '\n' ' '` + for _, tc := range []struct { + name string + env []string + args []string + expectOutput string + expectErr string + }{ + { + name: "zero args", + expectErr: "not enough args to run a command", + }, + { + name: "all env passed through", + env: []string{ + "CLEAN_BAR=bar", + "CLEAN_FOO=foo", + }, + args: []string{"bash", "-c", bashPrintCleanVars}, + expectOutput: "CLEAN_BAR=bar CLEAN_FOO=foo", + }, + { + name: "remove one variable prefix", + env: []string{ + "CLEAN_BAR=bar", + "CLEAN_FOO=foo", + }, + args: []string{ + "-remove-prefix=CLEAN_BAR", "--", + "bash", "-c", bashPrintCleanVars, + }, + expectOutput: "CLEAN_FOO=foo", + }, + { + name: "remove common variable prefix", + env: []string{ + "CLEAN_COMMON_BAR=bar", + "CLEAN_COMMON_BAZ=baz", + "CLEAN_FOO=foo", + }, + args: []string{ + "-remove-prefix=CLEAN_COMMON_", "--", + "bash", "-c", bashPrintCleanVars, + }, + expectOutput: "CLEAN_FOO=foo", + }, + { + name: "remove multiple prefixes", + env: []string{ + "CLEAN_BAR=bar", + "CLEAN_BAZ=baz", + "CLEAN_FOO=foo", + }, + args: []string{ + "-remove-prefix=CLEAN_BAR", + "-remove-prefix=CLEAN_FOO", "--", + "bash", "-c", bashPrintCleanVars, + }, + expectOutput: "CLEAN_BAZ=baz", + }, + } { + tc := tc // enable parallel sub-tests + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + var output bytes.Buffer + app := App{ + Args: tc.args, + Env: tc.env, + StdOut: &output, + ErrOut: &output, + } + err := app.Run() + assertEqualError(t, tc.expectErr, err) + if tc.expectErr != "" { + return + } + + outputStr := strings.TrimSpace(output.String()) + if outputStr != tc.expectOutput { + t.Errorf("Unexpected output: %q != %q", tc.expectOutput, outputStr) + } + }) + } +} + +func assertEqualError(t *testing.T, expected string, err error) { + t.Helper() + if expected == "" { + if err != nil { + t.Error("Unexpected error:", err) + } + return + } + + if err == nil { + t.Error("Expected error, got nil") + return + } + message := err.Error() + if expected != message { + t.Errorf("Unexpected error message: %q != %q", expected, message) + } +}