Skip to content

Commit

Permalink
Fix Windows CI test failures by cleaning env vars (#43)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
JohnStarich authored Feb 7, 2023
1 parent bba38b6 commit bb77d44
Show file tree
Hide file tree
Showing 4 changed files with 239 additions and 1 deletion.
4 changes: 3 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
98 changes: 98 additions & 0 deletions cmd/cleanenv/main.go
Original file line number Diff line number Diff line change
@@ -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
}
113 changes: 113 additions & 0 deletions cmd/cleanenv/main_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
}

0 comments on commit bb77d44

Please sign in to comment.