├── .circleci └── config.yml ├── .github ├── dependabot.yml └── workflows │ └── ci.yaml ├── .gitignore ├── .project ├── Dockerfile ├── docs │ └── running-without-go.md ├── golangci-lint.yml └── goreleaser.yml ├── LICENSE ├── NOTICE ├── README.md ├── cmd ├── flags.go ├── flags_test.go ├── goversion_go122.go ├── goversion_old.go ├── handler.go ├── handler_test.go ├── internal │ └── signalhandlerdriver │ │ └── main.go ├── main.go ├── main_e2e_test.go ├── main_test.go ├── rerunfails.go ├── rerunfails_test.go ├── testdata │ ├── TestWriteRerunFailsReport-expected │ ├── TestWriteRerunFailsReport_HandlesMissingActionRunEvents-expected │ ├── e2e │ │ ├── expected │ │ │ ├── TestE2E_IgnoresWarnings │ │ │ ├── TestE2E_MaxFails_EndTestRun │ │ │ └── TestE2E_RerunFails │ │ │ │ ├── first_run_has_errors,_abort_rerun │ │ │ │ ├── first_run_has_errors,_abort_rerun-go1.23 │ │ │ │ ├── reruns_continues_to_fail │ │ │ │ ├── reruns_continues_to_fail-go1.23 │ │ │ │ ├── reruns_until_success │ │ │ │ └── reruns_until_success-go1.23 │ │ ├── flaky │ │ │ └── flaky_test.go │ │ └── ignore_warnings │ │ │ ├── ignore_warnings.go │ │ │ └── ignore_warnings_test.go │ ├── event-handler-missing-test-fail-expected │ ├── expected-jsonfile-timing-events │ ├── expected │ │ ├── build-fail-expected │ │ ├── panic-race-1-summary │ │ ├── panic-race-2-summary │ │ └── setup-fail-expected │ ├── go-test-json-flaky-rerun.out │ ├── go-test-missing-run-events.out │ ├── gotestsum-help-text │ ├── input │ │ ├── go-test-build-failed.out │ │ ├── go-test-json-panic-race-1.out │ │ ├── go-test-json-panic-race-2.out │ │ └── go-test-setup-failed.out │ ├── post-run-hook-expected │ └── postrunhook │ │ └── main.go ├── tool │ ├── matrix │ │ ├── matrix.go │ │ └── matrix_test.go │ └── slowest │ │ ├── ast.go │ │ ├── ast_test.go │ │ ├── slowest.go │ │ ├── slowest_test.go │ │ └── testdata │ │ └── cmd-flags-help-text └── watch.go ├── contrib └── notify │ ├── .gitignore │ ├── Makefile │ ├── icons │ ├── README.html │ ├── test-fail.svg │ └── test-pass.svg │ ├── notify_darwin.go │ ├── notify_linux.go │ └── notify_windows.go ├── do ├── go.mod ├── go.sum ├── internal ├── aggregate │ ├── slowest.go │ └── slowest_test.go ├── dotwriter │ ├── LICENSE │ ├── README │ ├── writer.go │ ├── writer_posix.go │ └── writer_windows.go ├── filewatcher │ ├── term_aix.go │ ├── term_bsd.go │ ├── term_illumos.go │ ├── term_linux.go │ ├── term_unix.go │ ├── term_windows.go │ ├── term_zos.go │ ├── watch.go │ ├── watch_test.go │ ├── watch_unix_test.go │ └── watch_unsupported.go ├── junitxml │ ├── report.go │ ├── report_test.go │ └── testdata │ │ ├── junitxml-report-hide-skipped-tests.golden │ │ ├── junitxml-report-skip-empty.golden │ │ └── junitxml-report.golden ├── log │ └── log.go └── text │ └── testing.go ├── main.go └── testjson ├── doc.go ├── dotformat.go ├── dotformat_test.go ├── execution.go ├── execution_test.go ├── format.go ├── format_test.go ├── internal ├── badmain │ └── main_test.go ├── broken │ └── broken.go ├── empty │ ├── empty.go │ └── empty_test.go ├── frenzy │ └── frenzy_test.go ├── good │ ├── good.go │ └── good_test.go ├── parallelfails │ └── fails_test.go └── withfails │ ├── fails_test.go │ └── timeout_test.go ├── pkgpathprefix.go ├── pkgpathprefix_test.go ├── summary.go ├── summary_test.go └── testdata ├── format ├── dots-v1.out ├── dots-v2.out ├── github-actions.out ├── pkgname-codicons.out ├── pkgname-coverage-go1.20.out ├── pkgname-coverage.out ├── pkgname-emoticons.out ├── pkgname-hide-empty.out ├── pkgname-hivis.out ├── pkgname-octicons.out ├── pkgname-shuffle.out ├── pkgname-text.out ├── pkgname.out ├── standard-quiet-coverage-go1.20.out ├── standard-quiet-coverage.out ├── standard-quiet-shuffle.out ├── standard-quiet.out ├── standard-verbose-coverage.out ├── standard-verbose-shuffle.out ├── standard-verbose.out ├── testdox-coverage.out ├── testdox-shuffle.out ├── testdox.out ├── testname-coverage.out ├── testname-shuffle.out └── testname.out ├── go-test-json-missing-test-events.out ├── go-test-json-with-nonjson-stdout.out ├── go-test.err ├── input ├── go-test-json-misattributed.out ├── go-test-json-missing-skip-msg.out ├── go-test-json-missing-test-fail.out ├── go-test-json-with-cover-go1.20.err ├── go-test-json-with-cover-go1.20.out ├── go-test-json-with-cover.err ├── go-test-json-with-cover.out ├── go-test-json-with-parallel-fails.out ├── go-test-json-with-shuffle.err ├── go-test-json-with-shuffle.out ├── go-test-json.err └── go-test-json.out └── summary ├── bug-missing-skip-message ├── bug-repeated-test-case-output ├── misattributed-output ├── missing-test-fail-event ├── parallel-failures ├── root-test-has-subtest-failures └── with-run-id /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | orbs: 4 | go: gotest/tools@0.0.14 5 | 6 | workflows: 7 | ci: 8 | jobs: 9 | - lint 10 | - build 11 | 12 | - update-windows-golden: 13 | filters: 14 | branches: {ignore: '/.*/'} 15 | 16 | - build: 17 | name: release 18 | publish: true 19 | filters: 20 | tags: {only: '/v[0-9]+(\.[0-9]+)*/'} 21 | branches: {ignore: '/.*/'} 22 | 23 | executors: 24 | windows: 25 | machine: 26 | image: windows-server-2019-vs2019:stable 27 | resource_class: windows.medium 28 | shell: bash.exe 29 | 30 | commands: 31 | install-goreleaser: 32 | description: Install goreleaser 33 | steps: 34 | - run: 35 | name: Install goreleaser 36 | command: | 37 | wget https://github.com/goreleaser/goreleaser/releases/download/v1.17.0/goreleaser_Linux_x86_64.tar.gz 38 | echo "9fb13d0b9611794da8d71688a50b1f2ea221fcd5f2f4ad529f8b45ee909b2371 goreleaser_Linux_x86_64.tar.gz" > checksum.txt 39 | sha256sum -c checksum.txt 40 | tar -xf goreleaser_Linux_x86_64.tar.gz 41 | mkdir -p ./bin 42 | mv goreleaser ./bin 43 | 44 | 45 | jobs: 46 | 47 | build: 48 | parameters: 49 | publish: 50 | type: boolean 51 | default: false 52 | executor: 53 | name: go/golang 54 | tag: 1.23-alpine 55 | steps: 56 | - go/install: {package: git} 57 | - go/install-ssh 58 | - checkout 59 | - go/mod-download 60 | - go/mod-tidy-check 61 | - install-goreleaser 62 | - unless: 63 | condition: << parameters.publish >> 64 | steps: 65 | run: 66 | name: build binaries 67 | command: bin/goreleaser --clean --snapshot --config .project/goreleaser.yml 68 | - when: 69 | condition: << parameters.publish >> 70 | steps: 71 | run: 72 | name: build and publish binaries 73 | command: bin/goreleaser --clean --skip-validate --config .project/goreleaser.yml 74 | - store_artifacts: 75 | path: ./dist 76 | destination: dist 77 | 78 | lint: 79 | executor: 80 | name: go/golang 81 | tag: 1.23-alpine 82 | steps: 83 | - checkout 84 | - run: go mod download 85 | - run: 86 | name: Install golangci-lint 87 | command: | 88 | mkdir -p /go/bin 89 | 90 | download=https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh 91 | wget -O- -q "$download" | sh -s -- -b /go/bin/ v1.60.3 92 | - run: 93 | name: Lint 94 | command: | 95 | golangci-lint run -v --concurrency 2 --config .project/golangci-lint.yml 96 | 97 | 98 | update-windows-golden: 99 | executor: windows 100 | steps: 101 | - checkout 102 | - go/install-gotestsum 103 | - run: | 104 | git config --global core.autocrlf false 105 | git config --global core.symlinks true 106 | - run: | 107 | choco upgrade golang 108 | go version 109 | 110 | - run: | 111 | /go/bin/gotestsum ./testjson ./internal/junitxml -test.update-golden 112 | 113 | - store_artifacts: 114 | path: testjson/testdata/ 115 | destination: golden 116 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | updates: 4 | - package-ecosystem: "gomod" 5 | directory: "/" 6 | schedule: 7 | interval: weekly 8 | 9 | - package-ecosystem: "github-actions" 10 | directory: "/" 11 | schedule: 12 | interval: weekly 13 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: 4 | pull_request: 5 | push: 6 | 7 | jobs: 8 | Build: 9 | strategy: 10 | fail-fast: false 11 | matrix: 12 | go-version: [stable, oldstable] 13 | platform: [ubuntu-latest, windows-latest, macos-latest] 14 | runs-on: ${{ matrix.platform }} 15 | steps: 16 | - name: Setup git 17 | # required to run tests on windows 18 | run: | 19 | git config --global core.autocrlf false 20 | git config --global core.symlinks true 21 | - name: Fetch Repository 22 | uses: actions/checkout@v4 23 | - name: Install Go 24 | uses: actions/setup-go@v5 25 | with: 26 | go-version: ${{ matrix.go-version }} 27 | - run: go build . 28 | - run: ./gotestsum -f testname -- ./... -race -count=1 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | vendor/ 2 | dist/ 3 | junit.xml 4 | .plsdo/ 5 | -------------------------------------------------------------------------------- /.project/Dockerfile: -------------------------------------------------------------------------------- 1 | 2 | ARG GOLANG_VERSION 3 | FROM golang:${GOLANG_VERSION:-1.18-alpine} as golang 4 | RUN apk add -U curl git bash 5 | ENV CGO_ENABLED=0 \ 6 | PS1="# " \ 7 | GO111MODULE=on 8 | ARG UID=1000 9 | RUN adduser --uid=${UID} --disabled-password devuser 10 | USER ${UID}:${UID} 11 | 12 | 13 | FROM golang as tools 14 | RUN wget -O- -q https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s && \ 15 | mv bin/golangci-lint /go/bin 16 | 17 | 18 | FROM golang as dev 19 | COPY --from=tools /go/bin/golangci-lint /usr/bin/golangci-lint 20 | 21 | 22 | FROM dev as dev-with-source 23 | COPY . . 24 | -------------------------------------------------------------------------------- /.project/docs/running-without-go.md: -------------------------------------------------------------------------------- 1 | # Running without Go 2 | 3 | `gotestsum` may be run without Go as long as the package to be tested has 4 | already been compiled using `go test -c`, and the `test2json` tool is available. 5 | 6 | The `test2json` tool can be compiled from the Go source tree so that it can be distributed to the environment that needs it. 7 | 8 | ```sh 9 | GOVERSION=1.17.6 10 | OS=$(uname -s | sed 's/.*/\L&/') 11 | mkdir -p gopath 12 | GOPATH=$(realpath gopath) 13 | HOME=$(realpath ./) 14 | curl -L --silent https://go.dev/dl/go${GOVERSION}.${OS}-amd64.tar.gz | tar xz -C ./ 15 | env HOME=$HOME GOOS=linux GOARCH=amd64 CGO_ENABLED=0 GOPATH=$GOPATH ./go/bin/go build -o test2json -ldflags="-s -w" cmd/test2json 16 | mv test2json /usr/local/bin/test2json 17 | ``` 18 | 19 | Or if you have Go installed already: 20 | 21 | ```sh 22 | env GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o test2json -ldflags="-s -w" cmd/test2json 23 | mv test2json /usr/local/bin/test2json 24 | ``` 25 | 26 | Example: running without a Go installation 27 | ``` 28 | export GOVERSION=1.13 29 | gotestsum --raw-command -- test2json -t -p pkgname ./binary.test -test.v 30 | ``` 31 | 32 | Note: Compiled test binaries *do not* cache test results, like the same `go test .` command would. 33 | -------------------------------------------------------------------------------- /.project/golangci-lint.yml: -------------------------------------------------------------------------------- 1 | linters-settings: 2 | goconst: 3 | min-len: 2 4 | min-occurrences: 4 5 | lll: 6 | line-length: 120 7 | 8 | issues: 9 | exclude-use-default: false 10 | exclude-rules: 11 | - linters: [revive] 12 | text: 'should have comment .*or be unexported' 13 | - linters: [revive] 14 | text: 'package-comments: should have a package comment' 15 | - linters: [stylecheck] 16 | text: 'ST1000: at least one file in a package should have a package comment' 17 | - linters: [errcheck] 18 | text: 'Error return value of `.*\.WriteString` is not checked' 19 | - linters: [errcheck] 20 | text: 'Error return value of `fmt.Fprint.*` is not checked' 21 | - linters: [unparam] 22 | text: 'result .* is always' 23 | - linters: [unparam] 24 | text: 'always receives' 25 | 26 | linters: 27 | disable-all: true 28 | enable: 29 | - bodyclose 30 | - errcheck 31 | - goconst 32 | - gofmt 33 | - goimports 34 | - gosimple 35 | - govet 36 | - ineffassign 37 | - lll 38 | - misspell 39 | - nakedret 40 | - nolintlint 41 | - prealloc 42 | - revive 43 | - staticcheck 44 | - stylecheck 45 | - typecheck 46 | - unconvert 47 | - unparam 48 | - unused 49 | - whitespace 50 | -------------------------------------------------------------------------------- /.project/goreleaser.yml: -------------------------------------------------------------------------------- 1 | 2 | project_name: gotestsum 3 | 4 | release: 5 | github: 6 | owner: gotestyourself 7 | name: gotestsum 8 | 9 | builds: 10 | - binary: gotestsum 11 | goos: 12 | - darwin 13 | - freebsd 14 | - windows 15 | - linux 16 | - illumos 17 | goarch: 18 | - amd64 19 | - arm64 20 | - arm 21 | - s390x 22 | - ppc64le 23 | env: [CGO_ENABLED=0] 24 | ldflags: ["-s -w -X gotest.tools/gotestsum/cmd.version={{.Version}}"] 25 | ignore: 26 | - goos: darwin 27 | goarch: s390x 28 | - goos: darwin 29 | goarch: ppc64le 30 | - goos: freebsd 31 | goarch: s390x 32 | - goos: freebsd 33 | goarch: ppc64le 34 | - goos: freebsd 35 | goarch: arm 36 | - goos: windows 37 | goarch: s390x 38 | - goos: windows 39 | goarch: ppc64le 40 | - goos: illumos 41 | goarch: arm 42 | - goos: illumos 43 | goarch: arm64 44 | - goos: illumos 45 | goarch: s390x 46 | - goos: illumos 47 | goarch: ppc64le 48 | 49 | checksum: 50 | name_template: '{{ .ProjectName }}-{{ .Version }}-checksums.txt' 51 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Copyright The gotestsum Authors. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /cmd/flags.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "encoding/csv" 5 | "fmt" 6 | "path" 7 | "strings" 8 | 9 | "github.com/dnephin/pflag" 10 | "github.com/google/shlex" 11 | "gotest.tools/gotestsum/internal/junitxml" 12 | "gotest.tools/gotestsum/testjson" 13 | ) 14 | 15 | type hideSummaryValue struct { 16 | value testjson.Summary 17 | } 18 | 19 | func newHideSummaryValue() *hideSummaryValue { 20 | return &hideSummaryValue{value: testjson.SummarizeAll} 21 | } 22 | 23 | func readAsCSV(val string) ([]string, error) { 24 | if val == "" { 25 | return nil, nil 26 | } 27 | return csv.NewReader(strings.NewReader(val)).Read() 28 | } 29 | 30 | func (s *hideSummaryValue) Set(val string) error { 31 | v, err := readAsCSV(val) 32 | if err != nil { 33 | return err 34 | } 35 | for _, item := range v { 36 | summary, ok := testjson.NewSummary(item) 37 | if !ok { 38 | return fmt.Errorf("value must be one or more of: %s", 39 | testjson.SummarizeAll.String()) 40 | } 41 | s.value -= summary 42 | } 43 | return nil 44 | } 45 | 46 | func (s *hideSummaryValue) Type() string { 47 | return "summary" 48 | } 49 | 50 | func (s *hideSummaryValue) String() string { 51 | // flip all the bits, since the flag value is the negative of what is stored 52 | return (testjson.SummarizeAll ^ s.value).String() 53 | } 54 | 55 | var junitFieldFormatValues = "full, relative, short" 56 | 57 | type junitFieldFormatValue struct { 58 | value junitxml.FormatFunc 59 | } 60 | 61 | func (f *junitFieldFormatValue) Set(val string) error { 62 | switch val { 63 | case "full": 64 | return nil 65 | case "relative": 66 | f.value = testjson.RelativePackagePath 67 | return nil 68 | case "short": 69 | f.value = path.Base 70 | return nil 71 | } 72 | return fmt.Errorf("invalid value: %v, must be one of: "+junitFieldFormatValues, val) 73 | } 74 | 75 | func (f *junitFieldFormatValue) Type() string { 76 | return "field-format" 77 | } 78 | 79 | func (f *junitFieldFormatValue) String() string { 80 | return "full" 81 | } 82 | 83 | func (f *junitFieldFormatValue) Value() junitxml.FormatFunc { 84 | if f == nil { 85 | return nil 86 | } 87 | return f.value 88 | } 89 | 90 | type commandValue struct { 91 | original string 92 | command []string 93 | } 94 | 95 | func (c *commandValue) String() string { 96 | return c.original 97 | } 98 | 99 | func (c *commandValue) Set(raw string) error { 100 | var err error 101 | c.command, err = shlex.Split(raw) 102 | c.original = raw 103 | return err 104 | } 105 | 106 | func (c *commandValue) Type() string { 107 | return "command" 108 | } 109 | 110 | func (c *commandValue) Value() []string { 111 | if c == nil { 112 | return nil 113 | } 114 | return c.command 115 | } 116 | 117 | var _ pflag.Value = (*stringSlice)(nil) 118 | 119 | // stringSlice is a flag.Value which populates the string slice by splitting 120 | // the raw flag value on whitespace. 121 | type stringSlice []string 122 | 123 | func (s *stringSlice) String() string { 124 | return strings.Join(*s, " ") 125 | } 126 | 127 | func (s *stringSlice) Set(raw string) error { 128 | *s = append(*s, strings.Fields(raw)...) 129 | return nil 130 | } 131 | 132 | func (s *stringSlice) Type() string { 133 | return "list" 134 | } 135 | 136 | func truthyFlag(s string) bool { 137 | switch strings.ToLower(s) { 138 | case "true", "yes", "1": 139 | return true 140 | } 141 | return false 142 | } 143 | -------------------------------------------------------------------------------- /cmd/flags_test.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "testing" 5 | 6 | "gotest.tools/v3/assert" 7 | ) 8 | 9 | func TestNoSummaryValue_SetAndString(t *testing.T) { 10 | t.Run("none", func(t *testing.T) { 11 | assert.Equal(t, newHideSummaryValue().String(), "none") 12 | }) 13 | t.Run("one", func(t *testing.T) { 14 | value := newHideSummaryValue() 15 | assert.NilError(t, value.Set("output")) 16 | assert.Equal(t, value.String(), "output") 17 | }) 18 | t.Run("some", func(t *testing.T) { 19 | value := newHideSummaryValue() 20 | assert.NilError(t, value.Set("errors,failed")) 21 | assert.Equal(t, value.String(), "failed,errors") 22 | }) 23 | t.Run("bad value", func(t *testing.T) { 24 | value := newHideSummaryValue() 25 | assert.ErrorContains(t, value.Set("bogus"), "must be one or more of") 26 | }) 27 | } 28 | 29 | func TestStringSlice(t *testing.T) { 30 | value := "one \ntwo three\n\tfour\t five \n" 31 | var v []string 32 | ss := (*stringSlice)(&v) 33 | assert.NilError(t, ss.Set(value)) 34 | assert.DeepEqual(t, v, []string{"one", "two", "three", "four", "five"}) 35 | } 36 | -------------------------------------------------------------------------------- /cmd/goversion_go122.go: -------------------------------------------------------------------------------- 1 | //go:build go1.22 2 | // +build go1.22 3 | 4 | package cmd 5 | 6 | import ( 7 | goversion "go/version" 8 | "runtime" 9 | ) 10 | 11 | func isGoVersionAtLeast(v string) bool { 12 | return goversion.Compare(v, runtime.Version()) < 0 13 | } 14 | -------------------------------------------------------------------------------- /cmd/goversion_old.go: -------------------------------------------------------------------------------- 1 | //go:build !go1.22 2 | // +build !go1.22 3 | 4 | package cmd 5 | 6 | func isGoVersionAtLeast(_ string) bool { 7 | return false 8 | } 9 | -------------------------------------------------------------------------------- /cmd/handler.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "io" 7 | "os" 8 | "os/exec" 9 | "path/filepath" 10 | 11 | "gotest.tools/gotestsum/internal/junitxml" 12 | "gotest.tools/gotestsum/internal/log" 13 | "gotest.tools/gotestsum/testjson" 14 | ) 15 | 16 | type eventHandler struct { 17 | formatter testjson.EventFormatter 18 | err *bufio.Writer 19 | jsonFile writeSyncer 20 | jsonFileTimingEvents writeSyncer 21 | maxFails int 22 | } 23 | 24 | type writeSyncer interface { 25 | io.WriteCloser 26 | Sync() error 27 | } 28 | 29 | //nolint:errcheck 30 | func (h *eventHandler) Err(text string) error { 31 | h.err.WriteString(text) 32 | h.err.WriteRune('\n') 33 | h.err.Flush() 34 | // always return nil, no need to stop scanning if the stderr write fails 35 | return nil 36 | } 37 | 38 | func (h *eventHandler) Event(event testjson.TestEvent, execution *testjson.Execution) error { 39 | if err := writeWithNewline(h.jsonFile, event.Bytes()); err != nil { 40 | return fmt.Errorf("failed to write JSON file: %w", err) 41 | } 42 | if event.Action.IsTerminal() { 43 | if err := writeWithNewline(h.jsonFileTimingEvents, event.Bytes()); err != nil { 44 | return fmt.Errorf("failed to write JSON file: %w", err) 45 | } 46 | } 47 | 48 | err := h.formatter.Format(event, execution) 49 | if err != nil { 50 | return fmt.Errorf("failed to format event: %w", err) 51 | } 52 | 53 | if h.maxFails > 0 && len(execution.Failed()) >= h.maxFails { 54 | return fmt.Errorf("ending test run because max failures was reached") 55 | } 56 | return nil 57 | } 58 | 59 | func writeWithNewline(out io.Writer, b []byte) error { 60 | // ignore artificial events that have len(b) == 0 61 | if out == nil || len(b) == 0 { 62 | return nil 63 | } 64 | if _, err := out.Write(b); err != nil { 65 | return err 66 | } 67 | _, err := out.Write([]byte{'\n'}) 68 | return err 69 | } 70 | 71 | func (h *eventHandler) Flush() { 72 | if h.jsonFile != nil { 73 | if err := h.jsonFile.Sync(); err != nil { 74 | log.Errorf("Failed to sync JSON file: %v", err) 75 | } 76 | } 77 | if h.jsonFileTimingEvents != nil { 78 | if err := h.jsonFileTimingEvents.Sync(); err != nil { 79 | log.Errorf("Failed to sync JSON file: %v", err) 80 | } 81 | } 82 | } 83 | 84 | func (h *eventHandler) Close() error { 85 | if h.jsonFile != nil { 86 | if err := h.jsonFile.Close(); err != nil { 87 | log.Errorf("Failed to close JSON file: %v", err) 88 | } 89 | } 90 | if h.jsonFileTimingEvents != nil { 91 | if err := h.jsonFileTimingEvents.Close(); err != nil { 92 | log.Errorf("Failed to close JSON file: %v", err) 93 | } 94 | } 95 | return nil 96 | } 97 | 98 | var _ testjson.EventHandler = &eventHandler{} 99 | 100 | func newEventHandler(opts *options) (*eventHandler, error) { 101 | formatter := testjson.NewEventFormatter(opts.stdout, opts.format, opts.formatOptions) 102 | if formatter == nil { 103 | return nil, fmt.Errorf("unknown format %s", opts.format) 104 | } 105 | handler := &eventHandler{ 106 | formatter: formatter, 107 | err: bufio.NewWriter(opts.stderr), 108 | maxFails: opts.maxFails, 109 | } 110 | 111 | switch opts.format { 112 | case "dots", "dots-v1", "dots-v2": 113 | // Discard the error from the handler to prevent extra lines. The 114 | // error will be printed in the summary. 115 | handler.err = bufio.NewWriter(io.Discard) 116 | } 117 | 118 | var err error 119 | if opts.jsonFile != "" { 120 | _ = os.MkdirAll(filepath.Dir(opts.jsonFile), 0o755) 121 | handler.jsonFile, err = os.Create(opts.jsonFile) 122 | if err != nil { 123 | return handler, fmt.Errorf("failed to create file: %w", err) 124 | } 125 | } 126 | if opts.jsonFileTimingEvents != "" { 127 | _ = os.MkdirAll(filepath.Dir(opts.jsonFileTimingEvents), 0o755) 128 | handler.jsonFileTimingEvents, err = os.Create(opts.jsonFileTimingEvents) 129 | if err != nil { 130 | return handler, fmt.Errorf("failed to create file: %w", err) 131 | } 132 | } 133 | return handler, nil 134 | } 135 | 136 | func writeJUnitFile(opts *options, execution *testjson.Execution) error { 137 | if opts.junitFile == "" { 138 | return nil 139 | } 140 | _ = os.MkdirAll(filepath.Dir(opts.junitFile), 0o755) 141 | junitFile, err := os.Create(opts.junitFile) 142 | if err != nil { 143 | return fmt.Errorf("failed to open JUnit file: %v", err) 144 | } 145 | defer func() { 146 | if err := junitFile.Close(); err != nil { 147 | log.Errorf("Failed to close JUnit file: %v", err) 148 | } 149 | }() 150 | 151 | return junitxml.Write(junitFile, execution, junitxml.Config{ 152 | ProjectName: opts.junitProjectName, 153 | FormatTestSuiteName: opts.junitTestSuiteNameFormat.Value(), 154 | FormatTestCaseClassname: opts.junitTestCaseClassnameFormat.Value(), 155 | HideEmptyPackages: opts.junitHideEmptyPackages, 156 | HideSkippedTests: opts.junitHideSkippedTests, 157 | }) 158 | } 159 | 160 | func postRunHook(opts *options, execution *testjson.Execution) error { 161 | command := opts.postRunHookCmd.Value() 162 | if len(command) == 0 { 163 | return nil 164 | } 165 | log.Debugf("exec: %s", command) 166 | 167 | cmd := exec.Command(command[0], command[1:]...) 168 | cmd.Stdout = opts.stdout 169 | cmd.Stderr = opts.stderr 170 | cmd.Env = append( 171 | os.Environ(), 172 | "GOTESTSUM_JSONFILE="+opts.jsonFile, 173 | "GOTESTSUM_JSONFILE_TIMING_EVENTS="+opts.jsonFileTimingEvents, 174 | "GOTESTSUM_JUNITFILE="+opts.junitFile, 175 | fmt.Sprintf("GOTESTSUM_ELAPSED=%.3fs", execution.Elapsed().Seconds()), 176 | fmt.Sprintf("TESTS_TOTAL=%d", execution.Total()), 177 | fmt.Sprintf("TESTS_FAILED=%d", len(execution.Failed())), 178 | fmt.Sprintf("TESTS_SKIPPED=%d", len(execution.Skipped())), 179 | fmt.Sprintf("TESTS_ERRORS=%d", len(execution.Errors())), 180 | ) 181 | return cmd.Run() 182 | } 183 | -------------------------------------------------------------------------------- /cmd/internal/signalhandlerdriver/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "os/signal" 7 | "strconv" 8 | "syscall" 9 | "time" 10 | ) 11 | 12 | func main() { 13 | if len(os.Args) < 2 { 14 | log("missing required filename argument") 15 | os.Exit(1) 16 | } 17 | 18 | pid := []byte(strconv.Itoa(os.Getpid())) 19 | if err := os.WriteFile(os.Args[1], pid, 0644); err != nil { 20 | log("failed to write file:", err.Error()) 21 | os.Exit(1) 22 | } 23 | 24 | c := make(chan os.Signal, 1) 25 | signal.Notify(c) 26 | 27 | var s os.Signal 28 | select { 29 | case s = <-c: 30 | case <-time.After(time.Minute): 31 | log("timeout waiting for signal") 32 | os.Exit(1) 33 | } 34 | 35 | log("Received signal:", s) 36 | switch n := s.(type) { 37 | case syscall.Signal: 38 | os.Exit(100 + int(n)) 39 | default: 40 | log("failed to parse signal number") 41 | os.Exit(3) 42 | } 43 | } 44 | 45 | func log(v ...interface{}) { 46 | fmt.Fprintln(os.Stderr, v...) 47 | } 48 | -------------------------------------------------------------------------------- /cmd/rerunfails.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | "regexp" 8 | "sort" 9 | "strings" 10 | 11 | "gotest.tools/gotestsum/testjson" 12 | ) 13 | 14 | type rerunOpts struct { 15 | runFlag string 16 | pkg string 17 | } 18 | 19 | func (o rerunOpts) Args() []string { 20 | var result []string 21 | if o.runFlag != "" { 22 | result = append(result, o.runFlag) 23 | } 24 | if o.pkg != "" { 25 | result = append(result, o.pkg) 26 | } 27 | return result 28 | } 29 | 30 | func newRerunOptsFromTestCase(tc testjson.TestCase) rerunOpts { 31 | return rerunOpts{ 32 | runFlag: goTestRunFlagForTestCase(tc.Test), 33 | pkg: tc.Package, 34 | } 35 | } 36 | 37 | type testCaseFilter func([]testjson.TestCase) []testjson.TestCase 38 | 39 | func rerunFailsFilter(o *options) testCaseFilter { 40 | if o.rerunFailsRunRootCases { 41 | return func(tcs []testjson.TestCase) []testjson.TestCase { 42 | var result []testjson.TestCase 43 | for _, tc := range tcs { 44 | if !tc.Test.IsSubTest() { 45 | result = append(result, tc) 46 | } 47 | } 48 | return result 49 | } 50 | } 51 | return testjson.FilterFailedUnique 52 | } 53 | 54 | func rerunFailed(ctx context.Context, opts *options, scanConfig testjson.ScanConfig) error { 55 | ctx, cancel := context.WithCancel(ctx) 56 | defer cancel() 57 | tcFilter := rerunFailsFilter(opts) 58 | 59 | rec := newFailureRecorderFromExecution(scanConfig.Execution) 60 | for attempts := 0; rec.count() > 0 && attempts < opts.rerunFailsMaxAttempts; attempts++ { 61 | testjson.PrintSummary(opts.stdout, scanConfig.Execution, testjson.SummarizeNone) 62 | opts.stdout.Write([]byte("\n")) //nolint:errcheck 63 | 64 | nextRec := newFailureRecorder(scanConfig.Handler) 65 | for _, tc := range tcFilter(rec.failures) { 66 | goTestProc, err := startGoTestFn(ctx, "", goTestCmdArgs(opts, newRerunOptsFromTestCase(tc))) 67 | if err != nil { 68 | return err 69 | } 70 | 71 | cfg := testjson.ScanConfig{ 72 | RunID: attempts + 1, 73 | Stdout: goTestProc.stdout, 74 | Stderr: goTestProc.stderr, 75 | Handler: nextRec, 76 | Execution: scanConfig.Execution, 77 | Stop: cancel, 78 | } 79 | if _, err := testjson.ScanTestOutput(cfg); err != nil { 80 | return err 81 | } 82 | exitErr := goTestProc.cmd.Wait() 83 | if exitErr != nil { 84 | nextRec.lastErr = exitErr 85 | } 86 | if err := hasErrors(exitErr, scanConfig.Execution, opts); err != nil { 87 | return err 88 | } 89 | } 90 | rec = nextRec 91 | } 92 | return rec.lastErr 93 | } 94 | 95 | // startGoTestFn is a shim for testing 96 | var startGoTestFn = startGoTest 97 | 98 | func hasErrors(err error, exec *testjson.Execution, opts *options) error { 99 | switch { 100 | case len(exec.Errors()) > 0: 101 | return fmt.Errorf("rerun aborted because previous run had errors") 102 | // Exit code 0 and 1 are expected. 103 | case ExitCodeWithDefault(err) > 1: 104 | return fmt.Errorf("unexpected go test exit code: %v", err) 105 | case exec.HasPanic(): 106 | return fmt.Errorf("rerun aborted because previous run had a suspected panic and some test may not have run") 107 | case exec.HasDataRace() && opts.rerunFailsMaxAttempts > 0 && opts.rerunFailsAbortOnDataRace: 108 | return fmt.Errorf("rerun aborted because previous run had a data race") 109 | default: 110 | return nil 111 | } 112 | } 113 | 114 | type failureRecorder struct { 115 | testjson.EventHandler 116 | failures []testjson.TestCase 117 | lastErr error 118 | } 119 | 120 | func newFailureRecorder(handler testjson.EventHandler) *failureRecorder { 121 | return &failureRecorder{EventHandler: handler} 122 | } 123 | 124 | func newFailureRecorderFromExecution(exec *testjson.Execution) *failureRecorder { 125 | return &failureRecorder{failures: exec.Failed()} 126 | } 127 | 128 | func (r *failureRecorder) Event(event testjson.TestEvent, execution *testjson.Execution) error { 129 | if !event.PackageEvent() && event.Action == testjson.ActionFail { 130 | pkg := execution.Package(event.Package) 131 | tc := pkg.LastFailedByName(event.Test) 132 | r.failures = append(r.failures, tc) 133 | } 134 | return r.EventHandler.Event(event, execution) 135 | } 136 | 137 | func (r *failureRecorder) count() int { 138 | return len(r.failures) 139 | } 140 | 141 | func goTestRunFlagForTestCase(test testjson.TestName) string { 142 | if test.IsSubTest() { 143 | parts := strings.Split(string(test), "/") 144 | var sb strings.Builder 145 | sb.WriteString("-test.run=") 146 | for i, p := range parts { 147 | if i > 0 { 148 | sb.WriteByte('/') 149 | } 150 | sb.WriteByte('^') 151 | sb.WriteString(regexp.QuoteMeta(p)) 152 | sb.WriteByte('$') 153 | } 154 | return sb.String() 155 | } 156 | return "-test.run=^" + regexp.QuoteMeta(test.Name()) + "$" 157 | } 158 | 159 | func writeRerunFailsReport(opts *options, exec *testjson.Execution) error { 160 | if opts.rerunFailsMaxAttempts == 0 || opts.rerunFailsReportFile == "" { 161 | return nil 162 | } 163 | 164 | type testCaseCounts struct { 165 | total int 166 | failed int 167 | } 168 | 169 | names := []string{} 170 | results := map[string]testCaseCounts{} 171 | for _, failure := range exec.Failed() { 172 | name := failure.Package + "." + failure.Test.Name() 173 | if _, ok := results[name]; ok { 174 | continue 175 | } 176 | names = append(names, name) 177 | 178 | pkg := exec.Package(failure.Package) 179 | counts := testCaseCounts{} 180 | 181 | for _, tc := range pkg.Failed { 182 | if tc.Test == failure.Test { 183 | counts.total++ 184 | counts.failed++ 185 | } 186 | } 187 | for _, tc := range pkg.Passed { 188 | if tc.Test == failure.Test { 189 | counts.total++ 190 | } 191 | } 192 | // Skipped tests are not counted, but presumably skipped tests can not fail 193 | results[name] = counts 194 | } 195 | 196 | fh, err := os.Create(opts.rerunFailsReportFile) 197 | if err != nil { 198 | return err 199 | } 200 | 201 | defer func() { 202 | _ = fh.Close() 203 | }() 204 | 205 | sort.Strings(names) 206 | for _, name := range names { 207 | counts := results[name] 208 | fmt.Fprintf(fh, "%s: %d runs, %d failures\n", name, counts.total, counts.failed) 209 | } 210 | return nil 211 | } 212 | -------------------------------------------------------------------------------- /cmd/testdata/TestWriteRerunFailsReport-expected: -------------------------------------------------------------------------------- 1 | gotest.tools/gotestsum/testdata/e2e/flaky.TestFailsOften: 4 runs, 3 failures 2 | gotest.tools/gotestsum/testdata/e2e/flaky.TestFailsRarely: 2 runs, 1 failures 3 | gotest.tools/gotestsum/testdata/e2e/flaky.TestFailsSometimes: 3 runs, 2 failures 4 | -------------------------------------------------------------------------------- /cmd/testdata/TestWriteRerunFailsReport_HandlesMissingActionRunEvents-expected: -------------------------------------------------------------------------------- 1 | github.com/hashicorp/consul/test/integration/connect/envoy.TestEnvoy: 5 runs, 5 failures 2 | github.com/hashicorp/consul/test/integration/connect/envoy.TestEnvoy/case-ent-cross-namespaces: 3 runs, 3 failures 3 | github.com/hashicorp/consul/test/integration/connect/envoy.TestEnvoy/case-ent-intra-namespace: 3 runs, 3 failures 4 | -------------------------------------------------------------------------------- /cmd/testdata/e2e/expected/TestE2E_IgnoresWarnings: -------------------------------------------------------------------------------- 1 | === RUN TestIgnoreWarnings 2 | --- FAIL: TestIgnoreWarnings 3 | FAIL cmd/testdata/e2e/ignore_warnings.TestIgnoreWarnings 4 | coverage: [no statements] 5 | FAIL cmd/testdata/e2e/ignore_warnings 6 | 7 | DONE 1 tests, 1 failure 8 | 9 | === RUN TestIgnoreWarnings 10 | --- FAIL: TestIgnoreWarnings 11 | FAIL cmd/testdata/e2e/ignore_warnings.TestIgnoreWarnings (re-run 1) 12 | coverage: [no statements] 13 | FAIL cmd/testdata/e2e/ignore_warnings 14 | 15 | === Failed 16 | === FAIL: cmd/testdata/e2e/ignore_warnings TestIgnoreWarnings 17 | 18 | === FAIL: cmd/testdata/e2e/ignore_warnings TestIgnoreWarnings (re-run 1) 19 | 20 | DONE 2 runs, 2 tests, 2 failures 21 | -------------------------------------------------------------------------------- /cmd/testdata/e2e/expected/TestE2E_MaxFails_EndTestRun: -------------------------------------------------------------------------------- 1 | 2 | === Failed 3 | === FAIL: cmd/testdata/e2e/flaky TestFailsRarely 4 | SEED: 0 5 | flaky_test.go:51: not this time 6 | 7 | === FAIL: cmd/testdata/e2e/flaky TestFailsSometimes 8 | SEED: 0 9 | flaky_test.go:58: not this time 10 | 11 | DONE 3 tests, 2 failures 12 | -------------------------------------------------------------------------------- /cmd/testdata/e2e/expected/TestE2E_RerunFails/first_run_has_errors,_abort_rerun: -------------------------------------------------------------------------------- 1 | FAIL testjson/internal/broken 2 | 3 | === Failed 4 | === FAIL: testjson/internal/broken 5 | FAIL gotest.tools/gotestsum/testjson/internal/broken [build failed] 6 | 7 | === Errors 8 | ../testjson/internal/broken/broken.go:5:21: undefined: somepackage 9 | 10 | 11 | DONE 0 tests, 1 failure, 1 error 12 | -------------------------------------------------------------------------------- /cmd/testdata/e2e/expected/TestE2E_RerunFails/first_run_has_errors,_abort_rerun-go1.23: -------------------------------------------------------------------------------- 1 | FAIL testjson/internal/broken 2 | 3 | === Failed 4 | === FAIL: testjson/internal/broken 5 | FAIL gotest.tools/gotestsum/testjson/internal/broken [build failed] 6 | 7 | === Errors 8 | ../testjson/internal/broken/broken.go:5:21: undefined: somepackage 9 | 10 | DONE 0 tests, 1 failure, 1 error 11 | -------------------------------------------------------------------------------- /cmd/testdata/e2e/expected/TestE2E_RerunFails/reruns_continues_to_fail: -------------------------------------------------------------------------------- 1 | PASS cmd/testdata/e2e/flaky.TestAlwaysPasses 2 | === RUN TestFailsRarely 3 | SEED: 0 4 | flaky_test.go:51: not this time 5 | --- FAIL: TestFailsRarely 6 | FAIL cmd/testdata/e2e/flaky.TestFailsRarely 7 | === RUN TestFailsSometimes 8 | SEED: 0 9 | flaky_test.go:58: not this time 10 | --- FAIL: TestFailsSometimes 11 | FAIL cmd/testdata/e2e/flaky.TestFailsSometimes 12 | PASS cmd/testdata/e2e/flaky.TestFailsOften/subtest_always_passes 13 | === RUN TestFailsOften/subtest_may_fail 14 | flaky_test.go:68: not this time 15 | --- FAIL: TestFailsOften/subtest_may_fail 16 | FAIL cmd/testdata/e2e/flaky.TestFailsOften/subtest_may_fail 17 | === RUN TestFailsOften 18 | SEED: 0 19 | --- FAIL: TestFailsOften 20 | FAIL cmd/testdata/e2e/flaky.TestFailsOften 21 | PASS cmd/testdata/e2e/flaky.TestFailsOftenDoesNotPrefixMatch 22 | PASS cmd/testdata/e2e/flaky.TestFailsSometimesDoesNotPrefixMatch 23 | FAIL cmd/testdata/e2e/flaky 24 | 25 | DONE 8 tests, 4 failures 26 | 27 | PASS cmd/testdata/e2e/flaky.TestFailsRarely (re-run 1) 28 | PASS cmd/testdata/e2e/flaky 29 | PASS cmd/testdata/e2e/flaky.TestFailsSometimes (re-run 1) 30 | PASS cmd/testdata/e2e/flaky 31 | === RUN TestFailsOften/subtest_may_fail 32 | flaky_test.go:68: not this time 33 | --- FAIL: TestFailsOften/subtest_may_fail 34 | FAIL cmd/testdata/e2e/flaky.TestFailsOften/subtest_may_fail (re-run 1) 35 | === RUN TestFailsOften 36 | SEED: 3 37 | --- FAIL: TestFailsOften 38 | FAIL cmd/testdata/e2e/flaky.TestFailsOften (re-run 1) 39 | FAIL cmd/testdata/e2e/flaky 40 | 41 | DONE 2 runs, 12 tests, 6 failures 42 | 43 | === RUN TestFailsOften/subtest_may_fail 44 | flaky_test.go:68: not this time 45 | --- FAIL: TestFailsOften/subtest_may_fail 46 | FAIL cmd/testdata/e2e/flaky.TestFailsOften/subtest_may_fail (re-run 2) 47 | === RUN TestFailsOften 48 | SEED: 4 49 | --- FAIL: TestFailsOften 50 | FAIL cmd/testdata/e2e/flaky.TestFailsOften (re-run 2) 51 | FAIL cmd/testdata/e2e/flaky 52 | 53 | === Failed 54 | === FAIL: cmd/testdata/e2e/flaky TestFailsRarely 55 | SEED: 0 56 | flaky_test.go:51: not this time 57 | 58 | === FAIL: cmd/testdata/e2e/flaky TestFailsSometimes 59 | SEED: 0 60 | flaky_test.go:58: not this time 61 | 62 | === FAIL: cmd/testdata/e2e/flaky TestFailsOften/subtest_may_fail 63 | flaky_test.go:68: not this time 64 | 65 | === FAIL: cmd/testdata/e2e/flaky TestFailsOften 66 | SEED: 0 67 | 68 | === FAIL: cmd/testdata/e2e/flaky TestFailsOften/subtest_may_fail (re-run 1) 69 | flaky_test.go:68: not this time 70 | 71 | === FAIL: cmd/testdata/e2e/flaky TestFailsOften (re-run 1) 72 | SEED: 3 73 | 74 | === FAIL: cmd/testdata/e2e/flaky TestFailsOften/subtest_may_fail (re-run 2) 75 | flaky_test.go:68: not this time 76 | 77 | === FAIL: cmd/testdata/e2e/flaky TestFailsOften (re-run 2) 78 | SEED: 4 79 | 80 | DONE 3 runs, 14 tests, 8 failures 81 | -------------------------------------------------------------------------------- /cmd/testdata/e2e/expected/TestE2E_RerunFails/reruns_continues_to_fail-go1.23: -------------------------------------------------------------------------------- 1 | PASS cmd/testdata/e2e/flaky.TestAlwaysPasses 2 | === RUN TestFailsRarely 3 | SEED: 0 4 | flaky_test.go:51: not this time 5 | --- FAIL: TestFailsRarely 6 | FAIL cmd/testdata/e2e/flaky.TestFailsRarely 7 | === RUN TestFailsSometimes 8 | SEED: 0 9 | flaky_test.go:58: not this time 10 | --- FAIL: TestFailsSometimes 11 | FAIL cmd/testdata/e2e/flaky.TestFailsSometimes 12 | PASS cmd/testdata/e2e/flaky.TestFailsOften/subtest_always_passes 13 | === RUN TestFailsOften/subtest_may_fail 14 | flaky_test.go:68: not this time 15 | --- FAIL: TestFailsOften/subtest_may_fail 16 | FAIL cmd/testdata/e2e/flaky.TestFailsOften/subtest_may_fail 17 | === RUN TestFailsOften 18 | SEED: 0 19 | --- FAIL: TestFailsOften 20 | FAIL cmd/testdata/e2e/flaky.TestFailsOften 21 | PASS cmd/testdata/e2e/flaky.TestFailsOftenDoesNotPrefixMatch 22 | PASS cmd/testdata/e2e/flaky.TestFailsSometimesDoesNotPrefixMatch 23 | FAIL cmd/testdata/e2e/flaky 24 | 25 | DONE 8 tests, 4 failures 26 | 27 | PASS cmd/testdata/e2e/flaky.TestFailsRarely (re-run 1) 28 | PASS cmd/testdata/e2e/flaky 29 | PASS cmd/testdata/e2e/flaky.TestFailsSometimes (re-run 1) 30 | PASS cmd/testdata/e2e/flaky 31 | === RUN TestFailsOften/subtest_may_fail 32 | flaky_test.go:68: not this time 33 | --- FAIL: TestFailsOften/subtest_may_fail 34 | FAIL cmd/testdata/e2e/flaky.TestFailsOften/subtest_may_fail (re-run 1) 35 | === RUN TestFailsOften 36 | SEED: 3 37 | --- FAIL: TestFailsOften 38 | FAIL cmd/testdata/e2e/flaky.TestFailsOften (re-run 1) 39 | FAIL cmd/testdata/e2e/flaky 40 | 41 | DONE 2 runs, 12 tests, 6 failures 42 | 43 | === RUN TestFailsOften/subtest_may_fail 44 | flaky_test.go:68: not this time 45 | --- FAIL: TestFailsOften/subtest_may_fail 46 | FAIL cmd/testdata/e2e/flaky.TestFailsOften/subtest_may_fail (re-run 2) 47 | === RUN TestFailsOften 48 | SEED: 4 49 | --- FAIL: TestFailsOften 50 | FAIL cmd/testdata/e2e/flaky.TestFailsOften (re-run 2) 51 | FAIL cmd/testdata/e2e/flaky 52 | 53 | === Failed 54 | === FAIL: cmd/testdata/e2e/flaky TestFailsRarely 55 | SEED: 0 56 | flaky_test.go:51: not this time 57 | 58 | === FAIL: cmd/testdata/e2e/flaky TestFailsSometimes 59 | SEED: 0 60 | flaky_test.go:58: not this time 61 | 62 | === FAIL: cmd/testdata/e2e/flaky TestFailsOften/subtest_may_fail 63 | flaky_test.go:68: not this time 64 | 65 | === FAIL: cmd/testdata/e2e/flaky TestFailsOften 66 | SEED: 0 67 | 68 | === FAIL: cmd/testdata/e2e/flaky TestFailsOften/subtest_may_fail (re-run 1) 69 | flaky_test.go:68: not this time 70 | 71 | === FAIL: cmd/testdata/e2e/flaky TestFailsOften (re-run 1) 72 | SEED: 3 73 | 74 | === FAIL: cmd/testdata/e2e/flaky TestFailsOften/subtest_may_fail (re-run 2) 75 | flaky_test.go:68: not this time 76 | 77 | === FAIL: cmd/testdata/e2e/flaky TestFailsOften (re-run 2) 78 | SEED: 4 79 | 80 | DONE 3 runs, 14 tests, 8 failures 81 | -------------------------------------------------------------------------------- /cmd/testdata/e2e/expected/TestE2E_RerunFails/reruns_until_success: -------------------------------------------------------------------------------- 1 | PASS cmd/testdata/e2e/flaky.TestAlwaysPasses 2 | === RUN TestFailsRarely 3 | SEED: 0 4 | flaky_test.go:51: not this time 5 | --- FAIL: TestFailsRarely 6 | FAIL cmd/testdata/e2e/flaky.TestFailsRarely 7 | === RUN TestFailsSometimes 8 | SEED: 0 9 | flaky_test.go:58: not this time 10 | --- FAIL: TestFailsSometimes 11 | FAIL cmd/testdata/e2e/flaky.TestFailsSometimes 12 | PASS cmd/testdata/e2e/flaky.TestFailsOften/subtest_always_passes 13 | === RUN TestFailsOften/subtest_may_fail 14 | flaky_test.go:68: not this time 15 | --- FAIL: TestFailsOften/subtest_may_fail 16 | FAIL cmd/testdata/e2e/flaky.TestFailsOften/subtest_may_fail 17 | === RUN TestFailsOften 18 | SEED: 0 19 | --- FAIL: TestFailsOften 20 | FAIL cmd/testdata/e2e/flaky.TestFailsOften 21 | PASS cmd/testdata/e2e/flaky.TestFailsOftenDoesNotPrefixMatch 22 | PASS cmd/testdata/e2e/flaky.TestFailsSometimesDoesNotPrefixMatch 23 | FAIL cmd/testdata/e2e/flaky 24 | 25 | DONE 8 tests, 4 failures 26 | 27 | PASS cmd/testdata/e2e/flaky.TestFailsRarely (re-run 1) 28 | PASS cmd/testdata/e2e/flaky 29 | PASS cmd/testdata/e2e/flaky.TestFailsSometimes (re-run 1) 30 | PASS cmd/testdata/e2e/flaky 31 | === RUN TestFailsOften/subtest_may_fail 32 | flaky_test.go:68: not this time 33 | --- FAIL: TestFailsOften/subtest_may_fail 34 | FAIL cmd/testdata/e2e/flaky.TestFailsOften/subtest_may_fail (re-run 1) 35 | === RUN TestFailsOften 36 | SEED: 3 37 | --- FAIL: TestFailsOften 38 | FAIL cmd/testdata/e2e/flaky.TestFailsOften (re-run 1) 39 | FAIL cmd/testdata/e2e/flaky 40 | 41 | DONE 2 runs, 12 tests, 6 failures 42 | 43 | === RUN TestFailsOften/subtest_may_fail 44 | flaky_test.go:68: not this time 45 | --- FAIL: TestFailsOften/subtest_may_fail 46 | FAIL cmd/testdata/e2e/flaky.TestFailsOften/subtest_may_fail (re-run 2) 47 | === RUN TestFailsOften 48 | SEED: 4 49 | --- FAIL: TestFailsOften 50 | FAIL cmd/testdata/e2e/flaky.TestFailsOften (re-run 2) 51 | FAIL cmd/testdata/e2e/flaky 52 | 53 | DONE 3 runs, 14 tests, 8 failures 54 | 55 | === RUN TestFailsOften/subtest_may_fail 56 | flaky_test.go:68: not this time 57 | --- FAIL: TestFailsOften/subtest_may_fail 58 | FAIL cmd/testdata/e2e/flaky.TestFailsOften/subtest_may_fail (re-run 3) 59 | === RUN TestFailsOften 60 | SEED: 5 61 | --- FAIL: TestFailsOften 62 | FAIL cmd/testdata/e2e/flaky.TestFailsOften (re-run 3) 63 | FAIL cmd/testdata/e2e/flaky 64 | 65 | DONE 4 runs, 16 tests, 10 failures 66 | 67 | PASS cmd/testdata/e2e/flaky.TestFailsOften/subtest_may_fail (re-run 4) 68 | PASS cmd/testdata/e2e/flaky.TestFailsOften (re-run 4) 69 | PASS cmd/testdata/e2e/flaky 70 | 71 | === Failed 72 | === FAIL: cmd/testdata/e2e/flaky TestFailsRarely 73 | SEED: 0 74 | flaky_test.go:51: not this time 75 | 76 | === FAIL: cmd/testdata/e2e/flaky TestFailsSometimes 77 | SEED: 0 78 | flaky_test.go:58: not this time 79 | 80 | === FAIL: cmd/testdata/e2e/flaky TestFailsOften/subtest_may_fail 81 | flaky_test.go:68: not this time 82 | 83 | === FAIL: cmd/testdata/e2e/flaky TestFailsOften 84 | SEED: 0 85 | 86 | === FAIL: cmd/testdata/e2e/flaky TestFailsOften/subtest_may_fail (re-run 1) 87 | flaky_test.go:68: not this time 88 | 89 | === FAIL: cmd/testdata/e2e/flaky TestFailsOften (re-run 1) 90 | SEED: 3 91 | 92 | === FAIL: cmd/testdata/e2e/flaky TestFailsOften/subtest_may_fail (re-run 2) 93 | flaky_test.go:68: not this time 94 | 95 | === FAIL: cmd/testdata/e2e/flaky TestFailsOften (re-run 2) 96 | SEED: 4 97 | 98 | === FAIL: cmd/testdata/e2e/flaky TestFailsOften/subtest_may_fail (re-run 3) 99 | flaky_test.go:68: not this time 100 | 101 | === FAIL: cmd/testdata/e2e/flaky TestFailsOften (re-run 3) 102 | SEED: 5 103 | 104 | DONE 5 runs, 18 tests, 10 failures 105 | -------------------------------------------------------------------------------- /cmd/testdata/e2e/expected/TestE2E_RerunFails/reruns_until_success-go1.23: -------------------------------------------------------------------------------- 1 | PASS cmd/testdata/e2e/flaky.TestAlwaysPasses 2 | === RUN TestFailsRarely 3 | SEED: 0 4 | flaky_test.go:51: not this time 5 | --- FAIL: TestFailsRarely 6 | FAIL cmd/testdata/e2e/flaky.TestFailsRarely 7 | === RUN TestFailsSometimes 8 | SEED: 0 9 | flaky_test.go:58: not this time 10 | --- FAIL: TestFailsSometimes 11 | FAIL cmd/testdata/e2e/flaky.TestFailsSometimes 12 | PASS cmd/testdata/e2e/flaky.TestFailsOften/subtest_always_passes 13 | === RUN TestFailsOften/subtest_may_fail 14 | flaky_test.go:68: not this time 15 | --- FAIL: TestFailsOften/subtest_may_fail 16 | FAIL cmd/testdata/e2e/flaky.TestFailsOften/subtest_may_fail 17 | === RUN TestFailsOften 18 | SEED: 0 19 | --- FAIL: TestFailsOften 20 | FAIL cmd/testdata/e2e/flaky.TestFailsOften 21 | PASS cmd/testdata/e2e/flaky.TestFailsOftenDoesNotPrefixMatch 22 | PASS cmd/testdata/e2e/flaky.TestFailsSometimesDoesNotPrefixMatch 23 | FAIL cmd/testdata/e2e/flaky 24 | 25 | DONE 8 tests, 4 failures 26 | 27 | PASS cmd/testdata/e2e/flaky.TestFailsRarely (re-run 1) 28 | PASS cmd/testdata/e2e/flaky 29 | PASS cmd/testdata/e2e/flaky.TestFailsSometimes (re-run 1) 30 | PASS cmd/testdata/e2e/flaky 31 | === RUN TestFailsOften/subtest_may_fail 32 | flaky_test.go:68: not this time 33 | --- FAIL: TestFailsOften/subtest_may_fail 34 | FAIL cmd/testdata/e2e/flaky.TestFailsOften/subtest_may_fail (re-run 1) 35 | === RUN TestFailsOften 36 | SEED: 3 37 | --- FAIL: TestFailsOften 38 | FAIL cmd/testdata/e2e/flaky.TestFailsOften (re-run 1) 39 | FAIL cmd/testdata/e2e/flaky 40 | 41 | DONE 2 runs, 12 tests, 6 failures 42 | 43 | === RUN TestFailsOften/subtest_may_fail 44 | flaky_test.go:68: not this time 45 | --- FAIL: TestFailsOften/subtest_may_fail 46 | FAIL cmd/testdata/e2e/flaky.TestFailsOften/subtest_may_fail (re-run 2) 47 | === RUN TestFailsOften 48 | SEED: 4 49 | --- FAIL: TestFailsOften 50 | FAIL cmd/testdata/e2e/flaky.TestFailsOften (re-run 2) 51 | FAIL cmd/testdata/e2e/flaky 52 | 53 | DONE 3 runs, 14 tests, 8 failures 54 | 55 | === RUN TestFailsOften/subtest_may_fail 56 | flaky_test.go:68: not this time 57 | --- FAIL: TestFailsOften/subtest_may_fail 58 | FAIL cmd/testdata/e2e/flaky.TestFailsOften/subtest_may_fail (re-run 3) 59 | === RUN TestFailsOften 60 | SEED: 5 61 | --- FAIL: TestFailsOften 62 | FAIL cmd/testdata/e2e/flaky.TestFailsOften (re-run 3) 63 | FAIL cmd/testdata/e2e/flaky 64 | 65 | DONE 4 runs, 16 tests, 10 failures 66 | 67 | PASS cmd/testdata/e2e/flaky.TestFailsOften/subtest_may_fail (re-run 4) 68 | PASS cmd/testdata/e2e/flaky.TestFailsOften (re-run 4) 69 | PASS cmd/testdata/e2e/flaky 70 | 71 | === Failed 72 | === FAIL: cmd/testdata/e2e/flaky TestFailsRarely 73 | SEED: 0 74 | flaky_test.go:51: not this time 75 | 76 | === FAIL: cmd/testdata/e2e/flaky TestFailsSometimes 77 | SEED: 0 78 | flaky_test.go:58: not this time 79 | 80 | === FAIL: cmd/testdata/e2e/flaky TestFailsOften/subtest_may_fail 81 | flaky_test.go:68: not this time 82 | 83 | === FAIL: cmd/testdata/e2e/flaky TestFailsOften 84 | SEED: 0 85 | 86 | === FAIL: cmd/testdata/e2e/flaky TestFailsOften/subtest_may_fail (re-run 1) 87 | flaky_test.go:68: not this time 88 | 89 | === FAIL: cmd/testdata/e2e/flaky TestFailsOften (re-run 1) 90 | SEED: 3 91 | 92 | === FAIL: cmd/testdata/e2e/flaky TestFailsOften/subtest_may_fail (re-run 2) 93 | flaky_test.go:68: not this time 94 | 95 | === FAIL: cmd/testdata/e2e/flaky TestFailsOften (re-run 2) 96 | SEED: 4 97 | 98 | === FAIL: cmd/testdata/e2e/flaky TestFailsOften/subtest_may_fail (re-run 3) 99 | flaky_test.go:68: not this time 100 | 101 | === FAIL: cmd/testdata/e2e/flaky TestFailsOften (re-run 3) 102 | SEED: 5 103 | 104 | DONE 5 runs, 18 tests, 10 failures 105 | -------------------------------------------------------------------------------- /cmd/testdata/e2e/flaky/flaky_test.go: -------------------------------------------------------------------------------- 1 | //go:build testdata 2 | // +build testdata 3 | 4 | package flaky 5 | 6 | import ( 7 | "fmt" 8 | "os" 9 | "strconv" 10 | "sync" 11 | "testing" 12 | ) 13 | 14 | var seed int 15 | var seedfile = seedFile() 16 | var once = new(sync.Once) 17 | 18 | func setup(t *testing.T) { 19 | once.Do(func() { 20 | raw, err := os.ReadFile(seedfile) 21 | if err != nil { 22 | t.Fatalf("failed to read seed: %v", err) 23 | } 24 | n, err := strconv.ParseInt(string(raw), 10, 64) 25 | if err != nil { 26 | t.Fatalf("failed to parse seed: %v", err) 27 | } 28 | seed = int(n) 29 | 30 | err = os.WriteFile(seedfile, []byte(strconv.Itoa(seed+1)), 0644) 31 | if err != nil { 32 | t.Fatalf("failed to write seed: %v", err) 33 | } 34 | }) 35 | fmt.Fprintln(os.Stderr, "SEED: ", seed) 36 | } 37 | 38 | func seedFile() string { 39 | if name, ok := os.LookupEnv("TEST_SEEDFILE"); ok { 40 | return name 41 | } 42 | return "/tmp/gotestsum-flaky-seedfile" 43 | } 44 | 45 | func TestAlwaysPasses(t *testing.T) { 46 | } 47 | 48 | func TestFailsRarely(t *testing.T) { 49 | setup(t) 50 | if seed%20 != 1 { 51 | t.Fatal("not this time") 52 | } 53 | } 54 | 55 | func TestFailsSometimes(t *testing.T) { 56 | setup(t) 57 | if seed%4 != 2 { 58 | t.Fatal("not this time") 59 | } 60 | } 61 | 62 | func TestFailsOften(t *testing.T) { 63 | setup(t) 64 | 65 | t.Run("subtest always passes", func(t *testing.T) {}) 66 | t.Run("subtest may fail", func(t *testing.T) { 67 | if seed%20 != 6 { 68 | t.Fatal("not this time") 69 | } 70 | }) 71 | } 72 | 73 | func TestFailsOftenDoesNotPrefixMatch(t *testing.T) {} 74 | 75 | func TestFailsSometimesDoesNotPrefixMatch(t *testing.T) {} 76 | -------------------------------------------------------------------------------- /cmd/testdata/e2e/ignore_warnings/ignore_warnings.go: -------------------------------------------------------------------------------- 1 | package ignore_warnings 2 | -------------------------------------------------------------------------------- /cmd/testdata/e2e/ignore_warnings/ignore_warnings_test.go: -------------------------------------------------------------------------------- 1 | package ignore_warnings 2 | 3 | import "testing" 4 | 5 | func TestIgnoreWarnings(t *testing.T) { 6 | t.Fail() 7 | } 8 | -------------------------------------------------------------------------------- /cmd/testdata/event-handler-missing-test-fail-expected: -------------------------------------------------------------------------------- 1 | FAIL gotest.tools/v3/poll 2 | === RUN TestWaitOn_WithCompare 3 | panic: runtime error: index out of range [1] with length 1 4 | 5 | goroutine 7 [running]: 6 | gotest.tools/v3/internal/assert.ArgsFromComparisonCall(0xc0000552a0, 0x1, 0x1, 0x1, 0x0, 0x0) 7 | /home/daniel/pers/code/gotest.tools/internal/assert/result.go:102 +0x9f 8 | gotest.tools/v3/internal/assert.runComparison(0x6bcb80, 0xc00000e180, 0x67dee8, 0xc00007a9f0, 0x0, 0x0, 0x0, 0x7f7f4fb6d108) 9 | /home/daniel/pers/code/gotest.tools/internal/assert/result.go:34 +0x2b1 10 | gotest.tools/v3/internal/assert.Eval(0x6bcb80, 0xc00000e180, 0x67dee8, 0x627660, 0xc00007a9f0, 0x0, 0x0, 0x0, 0x642c60) 11 | /home/daniel/pers/code/gotest.tools/internal/assert/assert.go:56 +0x2e4 12 | gotest.tools/v3/poll.Compare(0xc00007a9f0, 0x6b74a0, 0x618a60) 13 | /home/daniel/pers/code/gotest.tools/poll/poll.go:151 +0x81 14 | gotest.tools/v3/poll.TestWaitOn_WithCompare.func1(0x6be4c0, 0xc00016c240, 0xc00016c240, 0x6be4c0) 15 | /home/daniel/pers/code/gotest.tools/poll/poll_test.go:81 +0x58 16 | gotest.tools/v3/poll.WaitOn.func1(0xc00001e3c0, 0x67df50, 0x6c1960, 0xc00016c240) 17 | /home/daniel/pers/code/gotest.tools/poll/poll.go:125 +0x62 18 | created by gotest.tools/v3/poll.WaitOn 19 | /home/daniel/pers/code/gotest.tools/poll/poll.go:124 +0x16f 20 | FAIL gotest.tools/v3/poll.TestWaitOn_WithCompare (-1.00s) 21 | -------------------------------------------------------------------------------- /cmd/testdata/expected/build-fail-expected: -------------------------------------------------------------------------------- 1 | 2 | === Failed 3 | === FAIL: example.com/internal/cacher (0.00s) 4 | FAIL example.com/internal/cacher [build failed] 5 | 6 | === Errors 7 | ./directory_test.go:321:10: undefined: assert.foo 8 | 9 | 10 | DONE 0 tests, 1 failure, 1 error 11 | -------------------------------------------------------------------------------- /cmd/testdata/expected/panic-race-1-summary: -------------------------------------------------------------------------------- 1 | 2 | === Failed 3 | === FAIL: github.com/mafredri/test (0.00s) 4 | panic: test timed out after 1s 5 | running tests: 6 | TestHello (1s) 7 | 8 | goroutine 33 [running]: 9 | testing.(*M).startAlarm.func1() 10 | /home/mafredri/sdk/go1.20rc1/src/testing/testing.go:2240 +0x3b9 11 | created by time.goFunc 12 | /home/mafredri/sdk/go1.20rc1/src/time/sleep.go:176 +0x32 13 | 14 | goroutine 1 [runnable]: 15 | testing.(*T).Run(0xc000083040, {0x5be88c?, 0x4ce6c5?}, 0x6072a0) 16 | /home/mafredri/sdk/go1.20rc1/src/testing/testing.go:1629 +0x405 17 | testing.runTests.func1(0x7438e0?) 18 | /home/mafredri/sdk/go1.20rc1/src/testing/testing.go:2035 +0x45 19 | testing.tRunner(0xc000083040, 0xc00025fc88) 20 | /home/mafredri/sdk/go1.20rc1/src/testing/testing.go:1575 +0x10b 21 | testing.runTests(0xc0000c0500?, {0x739320, 0x2, 0x2}, {0x0?, 0x100c0000ab938?, 0x743080?}) 22 | /home/mafredri/sdk/go1.20rc1/src/testing/testing.go:2033 +0x489 23 | testing.(*M).Run(0xc0000c0500) 24 | /home/mafredri/sdk/go1.20rc1/src/testing/testing.go:1905 +0x63a 25 | main.main() 26 | _testmain.go:49 +0x1aa 27 | 28 | goroutine 20 [runnable]: 29 | runtime.goexit1() 30 | /home/mafredri/sdk/go1.20rc1/src/runtime/proc.go:3616 +0x54 31 | runtime.goexit() 32 | /home/mafredri/sdk/go1.20rc1/src/runtime/asm_amd64.s:1599 +0x6 33 | created by testing.(*T).Run 34 | /home/mafredri/sdk/go1.20rc1/src/testing/testing.go:1628 +0x3ea 35 | FAIL github.com/mafredri/test 1.012s 36 | 37 | DONE 1 tests, 1 failure 38 | -------------------------------------------------------------------------------- /cmd/testdata/expected/panic-race-2-summary: -------------------------------------------------------------------------------- 1 | 2 | === Failed 3 | === FAIL: example (0.00s) 4 | panic: test timed out after 2s 5 | running tests: 6 | TestSleepsTooLong (2s) 7 | 8 | goroutine 17 [running]: 9 | testing.(*M).startAlarm.func1() 10 | /usr/lib/go/src/testing/testing.go:2241 +0x3c5 11 | created by time.goFunc 12 | /usr/lib/go/src/time/sleep.go:176 +0x32 13 | 14 | goroutine 1 [chan receive]: 15 | testing.(*T).Run(0xc0000076c0, {0x52afd7?, 0x4baa25?}, 0x533d98) 16 | /usr/lib/go/src/testing/testing.go:1630 +0x405 17 | testing.runTests.func1(0x6102c0?) 18 | /usr/lib/go/src/testing/testing.go:2036 +0x45 19 | testing.tRunner(0xc0000076c0, 0xc000096c88) 20 | /usr/lib/go/src/testing/testing.go:1576 +0x10b 21 | testing.runTests(0xc000026140?, {0x606c80, 0x1, 0x1}, {0x0?, 0x100c0000a6598?, 0x60fae0?}) 22 | /usr/lib/go/src/testing/testing.go:2034 +0x489 23 | testing.(*M).Run(0xc000026140) 24 | /usr/lib/go/src/testing/testing.go:1906 +0x63a 25 | main.main() 26 | _testmain.go:47 +0x1aa 27 | 28 | goroutine 6 [sleep]: 29 | time.Sleep(0x4a817c800) 30 | /usr/lib/go/src/runtime/time.go:195 +0x135 31 | gotest.tools/gotestsum/example.TestSleepsTooLong(0x0?) 32 | /home/daniel/pers/code/gotestsum/example/testing_test.go:9 +0x25 33 | testing.tRunner(0xc000007860, 0x533d98) 34 | /usr/lib/go/src/testing/testing.go:1576 +0x10b 35 | created by testing.(*T).Run 36 | /usr/lib/go/src/testing/testing.go:1629 +0x3ea 37 | FAIL gotest.tools/gotestsum/example 2.003s 38 | 39 | === FAIL: example TestSleepsTooLong (unknown) 40 | 41 | DONE 1 tests, 2 failures 42 | -------------------------------------------------------------------------------- /cmd/testdata/expected/setup-fail-expected: -------------------------------------------------------------------------------- 1 | 2 | === Failed 3 | === FAIL: example.com/internal/cacher (0.00s) 4 | FAIL example.com/internal/cacher [setup failed] 5 | 6 | === FAIL: example.com/internal/cacher/subpkg (0.00s) 7 | FAIL example.com/internal/cacher/subpkg [setup failed] 8 | 9 | === Errors 10 | directory_test.go:321:13: expected ';', found o 11 | 12 | subpkg/main_test.go:1:1: expected 'package', found aldfjadskfs 13 | 14 | 15 | DONE 0 tests, 2 failures, 2 errors 16 | -------------------------------------------------------------------------------- /cmd/testdata/gotestsum-help-text: -------------------------------------------------------------------------------- 1 | Usage: 2 | gotestsum [flags] [--] [go test flags] 3 | gotestsum [command] 4 | 5 | See https://pkg.go.dev/gotest.tools/gotestsum#section-readme for detailed documentation. 6 | 7 | Flags: 8 | --debug enabled debug logging 9 | -f, --format string print format of test input (default "pkgname") 10 | --format-hide-empty-pkg do not print empty packages in compact formats 11 | --format-icons string use different icons, see help for options 12 | --hide-summary summary hide sections of the summary: skipped,failed,errors,output (default none) 13 | --jsonfile string write all TestEvents to file 14 | --jsonfile-timing-events string write only the pass, skip, and fail TestEvents to the file 15 | --junitfile string write a JUnit XML file 16 | --junitfile-hide-empty-pkg omit packages with no tests from the junit.xml file 17 | --junitfile-hide-skipped-tests omit skipped tests from the junit.xml file 18 | --junitfile-project-name string name of the project used in the junit.xml file 19 | --junitfile-testcase-classname field-format format the testcase classname field as: full, relative, short (default full) 20 | --junitfile-testsuite-name field-format format the testsuite name field as: full, relative, short (default full) 21 | --max-fails int end the test run after this number of failures 22 | --no-color disable color output 23 | --packages list space separated list of package to test 24 | --post-run-command command command to run after the tests have completed 25 | --raw-command don't prepend 'go test -json' to the 'go test' command 26 | --rerun-fails int[=2] rerun failed tests until they all pass, or attempts exceeds maximum. Defaults to max 2 reruns when enabled 27 | --rerun-fails-abort-on-data-race do not rerun tests if a data race is detected 28 | --rerun-fails-max-failures int do not rerun any tests if the initial run has more than this number of failures (default 10) 29 | --rerun-fails-report string write a report to the file, of the tests that were rerun 30 | --rerun-fails-run-root-test rerun the entire root testcase when any of its subtests fail, instead of only the failed subtest 31 | --version show version and exit 32 | --watch watch go files, and run tests when a file is modified 33 | --watch-chdir in watch mode change the working directory to the directory with the modified file before running tests 34 | 35 | Formats: 36 | dots print a character for each test 37 | dots-v2 experimental dots format, one package per line 38 | pkgname print a line for each package 39 | pkgname-and-test-fails print a line for each package and failed test output 40 | testname print a line for each test and package 41 | testdox print a sentence for each test using gotestdox 42 | github-actions testname format with github actions log grouping 43 | standard-quiet standard go test format 44 | standard-verbose standard go test -v format 45 | 46 | Format icons: 47 | default the original unicode (✓, ∅, ✖) 48 | hivis higher visibility unicode (✅, ➖, ❌) 49 | text simple text characters (PASS, SKIP, FAIL) 50 | codicons requires a font from https://www.nerdfonts.com/ (  ) 51 | octicons requires a font from https://www.nerdfonts.com/ (  ) 52 | emoticons requires a font from https://www.nerdfonts.com/ (󰇵 󰇶 󰇸) 53 | 54 | Commands: 55 | gotestsum tool slowest find or skip the slowest tests 56 | gotestsum help print this help text 57 | -------------------------------------------------------------------------------- /cmd/testdata/input/go-test-build-failed.out: -------------------------------------------------------------------------------- 1 | {"ImportPath":"example.com/internal/cacher [example.com/internal/cacher.test]","Action":"build-output","Output":"# example.com/internal/cacher [example.com/internal/cacher.test]\n"} 2 | {"ImportPath":"example.com/internal/cacher [example.com/internal/cacher.test]","Action":"build-output","Output":"./directory_test.go:321:10: undefined: assert.foo\n"} 3 | {"ImportPath":"example.com/internal/cacher [example.com/internal/cacher.test]","Action":"build-fail"} 4 | {"Time":"2025-03-10T17:34:31.493978-07:00","Action":"start","Package":"example.com/internal/cacher"} 5 | {"Time":"2025-03-10T17:34:31.494027-07:00","Action":"output","Package":"example.com/internal/cacher","Output":"FAIL\texample.com/internal/cacher [build failed]\n"} 6 | {"Time":"2025-03-10T17:34:31.494039-07:00","Action":"fail","Package":"example.com/internal/cacher","Elapsed":0,"FailedBuild":"example.com/internal/cacher [example.com/internal/cacher.test]"} -------------------------------------------------------------------------------- /cmd/testdata/input/go-test-json-panic-race-1.out: -------------------------------------------------------------------------------- 1 | {"Time":"2022-12-14T09:49:01.562401799Z","Action":"start","Package":"github.com/mafredri/test"} 2 | {"Time":"2022-12-14T09:49:01.569546938Z","Action":"run","Package":"github.com/mafredri/test","Test":"TestHello"} 3 | {"Time":"2022-12-14T09:49:01.569700427Z","Action":"output","Package":"github.com/mafredri/test","Test":"TestHello","Output":"=== RUN TestHello\n"} 4 | {"Time":"2022-12-14T09:49:02.569759117Z","Action":"output","Package":"github.com/mafredri/test","Test":"TestHello","Output":" main_test.go:11: Hello\n"} 5 | {"Time":"2022-12-14T09:49:02.56982657Z","Action":"output","Package":"github.com/mafredri/test","Test":"TestHello","Output":"--- PASS: TestHello (1.00s)\n"} 6 | {"Time":"2022-12-14T09:49:02.572963923Z","Action":"output","Package":"github.com/mafredri/test","Test":"TestHello","Output":"panic: test timed out after 1s\n"} 7 | {"Time":"2022-12-14T09:49:02.572982687Z","Action":"output","Package":"github.com/mafredri/test","Test":"TestHello","Output":"running tests:\n"} 8 | {"Time":"2022-12-14T09:49:02.572992095Z","Action":"output","Package":"github.com/mafredri/test","Test":"TestHello","Output":"\tTestHello (1s)\n"} 9 | {"Time":"2022-12-14T09:49:02.573000907Z","Action":"output","Package":"github.com/mafredri/test","Test":"TestHello","Output":"\n"} 10 | {"Time":"2022-12-14T09:49:02.573019868Z","Action":"output","Package":"github.com/mafredri/test","Test":"TestHello","Output":"goroutine 33 [running]:\n"} 11 | {"Time":"2022-12-14T09:49:02.573029067Z","Action":"output","Package":"github.com/mafredri/test","Test":"TestHello","Output":"testing.(*M).startAlarm.func1()\n"} 12 | {"Time":"2022-12-14T09:49:02.573038878Z","Action":"output","Package":"github.com/mafredri/test","Test":"TestHello","Output":"\t/home/mafredri/sdk/go1.20rc1/src/testing/testing.go:2240 +0x3b9\n"} 13 | {"Time":"2022-12-14T09:49:02.573064315Z","Action":"output","Package":"github.com/mafredri/test","Test":"TestHello","Output":"created by time.goFunc\n"} 14 | {"Time":"2022-12-14T09:49:02.573079975Z","Action":"output","Package":"github.com/mafredri/test","Test":"TestHello","Output":"\t/home/mafredri/sdk/go1.20rc1/src/time/sleep.go:176 +0x32\n"} 15 | {"Time":"2022-12-14T09:49:02.573097493Z","Action":"output","Package":"github.com/mafredri/test","Test":"TestHello","Output":"\n"} 16 | {"Time":"2022-12-14T09:49:02.573119064Z","Action":"output","Package":"github.com/mafredri/test","Test":"TestHello","Output":"goroutine 1 [runnable]:\n"} 17 | {"Time":"2022-12-14T09:49:02.573141104Z","Action":"output","Package":"github.com/mafredri/test","Test":"TestHello","Output":"testing.(*T).Run(0xc000083040, {0x5be88c?, 0x4ce6c5?}, 0x6072a0)\n"} 18 | {"Time":"2022-12-14T09:49:02.573162696Z","Action":"output","Package":"github.com/mafredri/test","Test":"TestHello","Output":"\t/home/mafredri/sdk/go1.20rc1/src/testing/testing.go:1629 +0x405\n"} 19 | {"Time":"2022-12-14T09:49:02.573178743Z","Action":"output","Package":"github.com/mafredri/test","Test":"TestHello","Output":"testing.runTests.func1(0x7438e0?)\n"} 20 | {"Time":"2022-12-14T09:49:02.573203585Z","Action":"output","Package":"github.com/mafredri/test","Test":"TestHello","Output":"\t/home/mafredri/sdk/go1.20rc1/src/testing/testing.go:2035 +0x45\n"} 21 | {"Time":"2022-12-14T09:49:02.57321895Z","Action":"output","Package":"github.com/mafredri/test","Test":"TestHello","Output":"testing.tRunner(0xc000083040, 0xc00025fc88)\n"} 22 | {"Time":"2022-12-14T09:49:02.573239542Z","Action":"output","Package":"github.com/mafredri/test","Test":"TestHello","Output":"\t/home/mafredri/sdk/go1.20rc1/src/testing/testing.go:1575 +0x10b\n"} 23 | {"Time":"2022-12-14T09:49:02.573342015Z","Action":"output","Package":"github.com/mafredri/test","Test":"TestHello","Output":"testing.runTests(0xc0000c0500?, {0x739320, 0x2, 0x2}, {0x0?, 0x100c0000ab938?, 0x743080?})\n"} 24 | {"Time":"2022-12-14T09:49:02.573376752Z","Action":"output","Package":"github.com/mafredri/test","Test":"TestHello","Output":"\t/home/mafredri/sdk/go1.20rc1/src/testing/testing.go:2033 +0x489\n"} 25 | {"Time":"2022-12-14T09:49:02.573403856Z","Action":"output","Package":"github.com/mafredri/test","Test":"TestHello","Output":"testing.(*M).Run(0xc0000c0500)\n"} 26 | {"Time":"2022-12-14T09:49:02.573433691Z","Action":"output","Package":"github.com/mafredri/test","Test":"TestHello","Output":"\t/home/mafredri/sdk/go1.20rc1/src/testing/testing.go:1905 +0x63a\n"} 27 | {"Time":"2022-12-14T09:49:02.573456763Z","Action":"output","Package":"github.com/mafredri/test","Test":"TestHello","Output":"main.main()\n"} 28 | {"Time":"2022-12-14T09:49:02.573483156Z","Action":"output","Package":"github.com/mafredri/test","Test":"TestHello","Output":"\t_testmain.go:49 +0x1aa\n"} 29 | {"Time":"2022-12-14T09:49:02.573503088Z","Action":"output","Package":"github.com/mafredri/test","Test":"TestHello","Output":"\n"} 30 | {"Time":"2022-12-14T09:49:02.573520911Z","Action":"output","Package":"github.com/mafredri/test","Test":"TestHello","Output":"goroutine 20 [runnable]:\n"} 31 | {"Time":"2022-12-14T09:49:02.573539195Z","Action":"output","Package":"github.com/mafredri/test","Test":"TestHello","Output":"runtime.goexit1()\n"} 32 | {"Time":"2022-12-14T09:49:02.573576101Z","Action":"output","Package":"github.com/mafredri/test","Test":"TestHello","Output":"\t/home/mafredri/sdk/go1.20rc1/src/runtime/proc.go:3616 +0x54\n"} 33 | {"Time":"2022-12-14T09:49:02.573596375Z","Action":"output","Package":"github.com/mafredri/test","Test":"TestHello","Output":"runtime.goexit()\n"} 34 | {"Time":"2022-12-14T09:49:02.573620424Z","Action":"output","Package":"github.com/mafredri/test","Test":"TestHello","Output":"\t/home/mafredri/sdk/go1.20rc1/src/runtime/asm_amd64.s:1599 +0x6\n"} 35 | {"Time":"2022-12-14T09:49:02.573637148Z","Action":"output","Package":"github.com/mafredri/test","Test":"TestHello","Output":"created by testing.(*T).Run\n"} 36 | {"Time":"2022-12-14T09:49:02.573690092Z","Action":"output","Package":"github.com/mafredri/test","Test":"TestHello","Output":"\t/home/mafredri/sdk/go1.20rc1/src/testing/testing.go:1628 +0x3ea\n"} 37 | {"Time":"2022-12-14T09:49:02.574702109Z","Action":"pass","Package":"github.com/mafredri/test","Test":"TestHello","Elapsed":1} 38 | {"Time":"2022-12-14T09:49:02.57473959Z","Action":"output","Package":"github.com/mafredri/test","Output":"FAIL\tgithub.com/mafredri/test\t1.012s\n"} 39 | {"Time":"2022-12-14T09:49:02.574754586Z","Action":"fail","Package":"github.com/mafredri/test","Elapsed":1.012} 40 | -------------------------------------------------------------------------------- /cmd/testdata/input/go-test-json-panic-race-2.out: -------------------------------------------------------------------------------- 1 | {"Time":"2023-08-12T12:54:44.132409933-04:00","Action":"start","Package":"gotest.tools/gotestsum/example"} 2 | {"Time":"2023-08-12T12:54:44.133131471-04:00","Action":"run","Package":"gotest.tools/gotestsum/example","Test":"TestSleepsTooLong"} 3 | {"Time":"2023-08-12T12:54:44.133140584-04:00","Action":"output","Package":"gotest.tools/gotestsum/example","Test":"TestSleepsTooLong","Output":"=== RUN TestSleepsTooLong\n"} 4 | {"Time":"2023-08-12T12:54:46.135570065-04:00","Action":"output","Package":"gotest.tools/gotestsum/example","Test":"TestSleepsTooLong","Output":"panic: test timed out after 2s\n"} 5 | {"Time":"2023-08-12T12:54:46.135604434-04:00","Action":"output","Package":"gotest.tools/gotestsum/example","Test":"TestSleepsTooLong","Output":"running tests:\n"} 6 | {"Time":"2023-08-12T12:54:46.135608775-04:00","Action":"output","Package":"gotest.tools/gotestsum/example","Test":"TestSleepsTooLong","Output":"\tTestSleepsTooLong (2s)\n"} 7 | {"Time":"2023-08-12T12:54:46.135611536-04:00","Action":"output","Package":"gotest.tools/gotestsum/example","Test":"TestSleepsTooLong","Output":"\n"} 8 | {"Time":"2023-08-12T12:54:46.135614121-04:00","Action":"output","Package":"gotest.tools/gotestsum/example","Test":"TestSleepsTooLong","Output":"goroutine 17 [running]:\n"} 9 | {"Time":"2023-08-12T12:54:46.135643208-04:00","Action":"output","Package":"gotest.tools/gotestsum/example","Test":"TestSleepsTooLong","Output":"testing.(*M).startAlarm.func1()\n"} 10 | {"Time":"2023-08-12T12:54:46.135647115-04:00","Action":"output","Package":"gotest.tools/gotestsum/example","Test":"TestSleepsTooLong","Output":"\t/usr/lib/go/src/testing/testing.go:2241 +0x3c5\n"} 11 | {"Time":"2023-08-12T12:54:46.135652292-04:00","Action":"output","Package":"gotest.tools/gotestsum/example","Test":"TestSleepsTooLong","Output":"created by time.goFunc\n"} 12 | {"Time":"2023-08-12T12:54:46.135655313-04:00","Action":"output","Package":"gotest.tools/gotestsum/example","Test":"TestSleepsTooLong","Output":"\t/usr/lib/go/src/time/sleep.go:176 +0x32\n"} 13 | {"Time":"2023-08-12T12:54:46.135657739-04:00","Action":"output","Package":"gotest.tools/gotestsum/example","Test":"TestSleepsTooLong","Output":"\n"} 14 | {"Time":"2023-08-12T12:54:46.135660238-04:00","Action":"output","Package":"gotest.tools/gotestsum/example","Test":"TestSleepsTooLong","Output":"goroutine 1 [chan receive]:\n"} 15 | {"Time":"2023-08-12T12:54:46.135662906-04:00","Action":"output","Package":"gotest.tools/gotestsum/example","Test":"TestSleepsTooLong","Output":"testing.(*T).Run(0xc0000076c0, {0x52afd7?, 0x4baa25?}, 0x533d98)\n"} 16 | {"Time":"2023-08-12T12:54:46.135666381-04:00","Action":"output","Package":"gotest.tools/gotestsum/example","Test":"TestSleepsTooLong","Output":"\t/usr/lib/go/src/testing/testing.go:1630 +0x405\n"} 17 | {"Time":"2023-08-12T12:54:46.135668821-04:00","Action":"output","Package":"gotest.tools/gotestsum/example","Test":"TestSleepsTooLong","Output":"testing.runTests.func1(0x6102c0?)\n"} 18 | {"Time":"2023-08-12T12:54:46.135671151-04:00","Action":"output","Package":"gotest.tools/gotestsum/example","Test":"TestSleepsTooLong","Output":"\t/usr/lib/go/src/testing/testing.go:2036 +0x45\n"} 19 | {"Time":"2023-08-12T12:54:46.135673732-04:00","Action":"output","Package":"gotest.tools/gotestsum/example","Test":"TestSleepsTooLong","Output":"testing.tRunner(0xc0000076c0, 0xc000096c88)\n"} 20 | {"Time":"2023-08-12T12:54:46.135676164-04:00","Action":"output","Package":"gotest.tools/gotestsum/example","Test":"TestSleepsTooLong","Output":"\t/usr/lib/go/src/testing/testing.go:1576 +0x10b\n"} 21 | {"Time":"2023-08-12T12:54:46.135678759-04:00","Action":"output","Package":"gotest.tools/gotestsum/example","Test":"TestSleepsTooLong","Output":"testing.runTests(0xc000026140?, {0x606c80, 0x1, 0x1}, {0x0?, 0x100c0000a6598?, 0x60fae0?})\n"} 22 | {"Time":"2023-08-12T12:54:46.135684642-04:00","Action":"output","Package":"gotest.tools/gotestsum/example","Test":"TestSleepsTooLong","Output":"\t/usr/lib/go/src/testing/testing.go:2034 +0x489\n"} 23 | {"Time":"2023-08-12T12:54:46.135687261-04:00","Action":"output","Package":"gotest.tools/gotestsum/example","Test":"TestSleepsTooLong","Output":"testing.(*M).Run(0xc000026140)\n"} 24 | {"Time":"2023-08-12T12:54:46.135715549-04:00","Action":"output","Package":"gotest.tools/gotestsum/example","Test":"TestSleepsTooLong","Output":"\t/usr/lib/go/src/testing/testing.go:1906 +0x63a\n"} 25 | {"Time":"2023-08-12T12:54:46.135718294-04:00","Action":"output","Package":"gotest.tools/gotestsum/example","Test":"TestSleepsTooLong","Output":"main.main()\n"} 26 | {"Time":"2023-08-12T12:54:46.135726996-04:00","Action":"output","Package":"gotest.tools/gotestsum/example","Test":"TestSleepsTooLong","Output":"\t_testmain.go:47 +0x1aa\n"} 27 | {"Time":"2023-08-12T12:54:46.135729722-04:00","Action":"output","Package":"gotest.tools/gotestsum/example","Test":"TestSleepsTooLong","Output":"\n"} 28 | {"Time":"2023-08-12T12:54:46.135732073-04:00","Action":"output","Package":"gotest.tools/gotestsum/example","Test":"TestSleepsTooLong","Output":"goroutine 6 [sleep]:\n"} 29 | {"Time":"2023-08-12T12:54:46.135734314-04:00","Action":"output","Package":"gotest.tools/gotestsum/example","Test":"TestSleepsTooLong","Output":"time.Sleep(0x4a817c800)\n"} 30 | {"Time":"2023-08-12T12:54:46.13573659-04:00","Action":"output","Package":"gotest.tools/gotestsum/example","Test":"TestSleepsTooLong","Output":"\t/usr/lib/go/src/runtime/time.go:195 +0x135\n"} 31 | {"Time":"2023-08-12T12:54:46.135739011-04:00","Action":"output","Package":"gotest.tools/gotestsum/example","Test":"TestSleepsTooLong","Output":"gotest.tools/gotestsum/example.TestSleepsTooLong(0x0?)\n"} 32 | {"Time":"2023-08-12T12:54:46.135760842-04:00","Action":"output","Package":"gotest.tools/gotestsum/example","Test":"TestSleepsTooLong","Output":"\t/home/daniel/pers/code/gotestsum/example/testing_test.go:9 +0x25\n"} 33 | {"Time":"2023-08-12T12:54:46.135763588-04:00","Action":"output","Package":"gotest.tools/gotestsum/example","Test":"TestSleepsTooLong","Output":"testing.tRunner(0xc000007860, 0x533d98)\n"} 34 | {"Time":"2023-08-12T12:54:46.135766232-04:00","Action":"output","Package":"gotest.tools/gotestsum/example","Test":"TestSleepsTooLong","Output":"\t/usr/lib/go/src/testing/testing.go:1576 +0x10b\n"} 35 | {"Time":"2023-08-12T12:54:46.135768744-04:00","Action":"output","Package":"gotest.tools/gotestsum/example","Test":"TestSleepsTooLong","Output":"created by testing.(*T).Run\n"} 36 | {"Time":"2023-08-12T12:54:46.135771535-04:00","Action":"output","Package":"gotest.tools/gotestsum/example","Test":"TestSleepsTooLong","Output":"\t/usr/lib/go/src/testing/testing.go:1629 +0x3ea\n"} 37 | {"Time":"2023-08-12T12:54:46.135869063-04:00","Action":"output","Package":"gotest.tools/gotestsum/example","Output":"FAIL\tgotest.tools/gotestsum/example\t2.003s\n"} 38 | {"Time":"2023-08-12T12:54:46.135881864-04:00","Action":"fail","Package":"gotest.tools/gotestsum/example","Elapsed":2.003} 39 | -------------------------------------------------------------------------------- /cmd/testdata/input/go-test-setup-failed.out: -------------------------------------------------------------------------------- 1 | {"ImportPath":"example.com/internal/cacher.test","Action":"build-output","Output":"# example.com/internal/cacher\n"} 2 | {"ImportPath":"example.com/internal/cacher.test","Action":"build-output","Output":"directory_test.go:321:13: expected ';', found o\n"} 3 | {"ImportPath":"example.com/internal/cacher.test","Action":"build-fail"} 4 | {"Time":"2025-03-11T10:08:34.978868-07:00","Action":"start","Package":"example.com/internal/cacher"} 5 | {"Time":"2025-03-11T10:08:34.978905-07:00","Action":"output","Package":"example.com/internal/cacher","Output":"FAIL\texample.com/internal/cacher [setup failed]\n"} 6 | {"Time":"2025-03-11T10:08:34.978914-07:00","Action":"fail","Package":"example.com/internal/cacher","Elapsed":0,"FailedBuild":"example.com/internal/cacher.test"} 7 | {"ImportPath":"example.com/internal/cacher/subpkg","Action":"build-output","Output":"# example.com/internal/cacher/subpkg\n"} 8 | {"ImportPath":"example.com/internal/cacher/subpkg","Action":"build-output","Output":"subpkg/main_test.go:1:1: expected 'package', found aldfjadskfs\n"} 9 | {"ImportPath":"example.com/internal/cacher/subpkg","Action":"build-fail"} 10 | {"Time":"2025-03-11T10:08:34.978928-07:00","Action":"start","Package":"example.com/internal/cacher/subpkg"} 11 | {"Time":"2025-03-11T10:08:34.979019-07:00","Action":"output","Package":"example.com/internal/cacher/subpkg","Output":"FAIL\texample.com/internal/cacher/subpkg [setup failed]\n"} 12 | {"Time":"2025-03-11T10:08:34.979028-07:00","Action":"fail","Package":"example.com/internal/cacher/subpkg","Elapsed":0,"FailedBuild":"example.com/internal/cacher/subpkg"} -------------------------------------------------------------------------------- /cmd/testdata/post-run-hook-expected: -------------------------------------------------------------------------------- 1 | GOTESTSUM_ELAPSED=0.157s 2 | GOTESTSUM_FORMAT=short 3 | GOTESTSUM_FORMAT_ICONS=default 4 | GOTESTSUM_JSONFILE=events.json 5 | GOTESTSUM_JSONFILE_TIMING_EVENTS=timing.json 6 | GOTESTSUM_JUNITFILE=junit.xml 7 | TESTS_ERRORS=0 8 | TESTS_FAILED=13 9 | TESTS_SKIPPED=5 10 | TESTS_TOTAL=59 11 | -------------------------------------------------------------------------------- /cmd/testdata/postrunhook/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "os" 7 | "sort" 8 | "strings" 9 | ) 10 | 11 | func main() { 12 | if err := run(); err != nil { 13 | fmt.Fprintln(os.Stderr, err.Error()) 14 | os.Exit(1) 15 | } 16 | } 17 | 18 | func run() error { 19 | environ := os.Environ() 20 | sort.Strings(environ) 21 | for _, v := range environ { 22 | for _, prefix := range []string{"TESTS_", "GOTESTSUM_"} { 23 | if strings.HasPrefix(v, prefix) { 24 | fmt.Println(v) 25 | } 26 | } 27 | } 28 | 29 | err := os.Getenv("TEST_STUB_ERROR") 30 | if err != "" { 31 | return errors.New(err) 32 | } 33 | return nil 34 | } 35 | -------------------------------------------------------------------------------- /cmd/tool/slowest/ast.go: -------------------------------------------------------------------------------- 1 | package slowest 2 | 3 | import ( 4 | "fmt" 5 | "go/ast" 6 | "go/format" 7 | "go/parser" 8 | "go/token" 9 | "os" 10 | "strings" 11 | 12 | "golang.org/x/tools/go/packages" 13 | "gotest.tools/gotestsum/internal/log" 14 | "gotest.tools/gotestsum/testjson" 15 | ) 16 | 17 | func writeTestSkip(tcs []testjson.TestCase, skipStmt ast.Stmt) error { 18 | fset := token.NewFileSet() 19 | cfg := packages.Config{ 20 | Mode: modeAll(), 21 | Tests: true, 22 | Fset: fset, 23 | BuildFlags: buildFlags(), 24 | } 25 | pkgNames, index := testNamesByPkgName(tcs) 26 | pkgs, err := packages.Load(&cfg, pkgNames...) 27 | if err != nil { 28 | return fmt.Errorf("failed to load packages: %v", err) 29 | } 30 | 31 | for _, pkg := range pkgs { 32 | if len(pkg.Errors) > 0 { 33 | return errPkgLoad(pkg) 34 | } 35 | tcs, ok := index[normalizePkgName(pkg.PkgPath)] 36 | if !ok { 37 | log.Debugf("skipping %v, no slow tests", pkg.PkgPath) 38 | continue 39 | } 40 | 41 | log.Debugf("rewriting %v for %d test cases", pkg.PkgPath, len(tcs)) 42 | for _, file := range pkg.Syntax { 43 | path := fset.File(file.Pos()).Name() 44 | log.Debugf("looking for test cases in: %v", path) 45 | if !rewriteAST(file, tcs, skipStmt) { 46 | continue 47 | } 48 | if err := writeFile(path, file, fset); err != nil { 49 | return fmt.Errorf("failed to write ast to file %v: %v", path, err) 50 | } 51 | } 52 | } 53 | return errTestCasesNotFound(index) 54 | } 55 | 56 | // normalizePkgName removes the _test suffix from a package name. External test 57 | // packages (those named package_test) may contain tests, but the test2json output 58 | // always uses the non-external package name. The _test suffix must be removed 59 | // so that any slow tests in an external test package can be found. 60 | func normalizePkgName(name string) string { 61 | return strings.TrimSuffix(name, "_test") 62 | } 63 | 64 | func writeFile(path string, file *ast.File, fset *token.FileSet) error { 65 | fh, err := os.Create(path) 66 | if err != nil { 67 | return err 68 | } 69 | defer func() { 70 | if err := fh.Close(); err != nil { 71 | log.Errorf("Failed to close file %v: %v", path, err) 72 | } 73 | }() 74 | return format.Node(fh, fset, file) 75 | } 76 | 77 | func parseSkipStatement(text string) (ast.Stmt, error) { 78 | switch text { 79 | case "default", "testing.Short": 80 | text = ` 81 | if testing.Short() { 82 | t.Skip("too slow for testing.Short") 83 | } 84 | ` 85 | } 86 | // Add some required boilerplate around the statement to make it a valid file 87 | text = "package stub\nfunc Stub() {\n" + text + "\n}\n" 88 | file, err := parser.ParseFile(token.NewFileSet(), "fragment", text, 0) 89 | if err != nil { 90 | return nil, err 91 | } 92 | stmt := file.Decls[0].(*ast.FuncDecl).Body.List[0] 93 | return stmt, nil 94 | } 95 | 96 | func rewriteAST(file *ast.File, testNames set, skipStmt ast.Stmt) bool { 97 | var modified bool 98 | for _, decl := range file.Decls { 99 | fd, ok := decl.(*ast.FuncDecl) 100 | if !ok { 101 | continue 102 | } 103 | name := fd.Name.Name // TODO: can this be nil? 104 | if _, ok := testNames[name]; !ok { 105 | continue 106 | } 107 | 108 | fd.Body.List = append([]ast.Stmt{skipStmt}, fd.Body.List...) 109 | modified = true 110 | delete(testNames, name) 111 | } 112 | return modified 113 | } 114 | 115 | type set map[string]struct{} 116 | 117 | // testNamesByPkgName strips subtest names from test names, then builds 118 | // and returns a slice of all the packages names, and a mapping of package name 119 | // to set of failed tests in that package. 120 | // 121 | // subtests are removed because the AST lookup currently only works for top-level 122 | // functions, not t.Run subtests. 123 | func testNamesByPkgName(tcs []testjson.TestCase) ([]string, map[string]set) { 124 | var pkgs []string 125 | index := make(map[string]set) 126 | for _, tc := range tcs { 127 | testName := tc.Test.Name() 128 | if tc.Test.IsSubTest() { 129 | root, _ := tc.Test.Split() 130 | testName = root 131 | } 132 | if len(index[tc.Package]) == 0 { 133 | pkgs = append(pkgs, tc.Package) 134 | index[tc.Package] = make(map[string]struct{}) 135 | } 136 | index[tc.Package][testName] = struct{}{} 137 | } 138 | return pkgs, index 139 | } 140 | 141 | func errPkgLoad(pkg *packages.Package) error { 142 | buf := new(strings.Builder) 143 | for _, err := range pkg.Errors { 144 | buf.WriteString("\n" + err.Error()) 145 | } 146 | return fmt.Errorf("failed to load package %v %v", pkg.PkgPath, buf.String()) 147 | } 148 | 149 | func errTestCasesNotFound(index map[string]set) error { 150 | var missed []string 151 | for pkg, tcs := range index { 152 | for tc := range tcs { 153 | missed = append(missed, fmt.Sprintf("%v.%v", pkg, tc)) 154 | } 155 | } 156 | if len(missed) == 0 { 157 | return nil 158 | } 159 | return fmt.Errorf("failed to find source for test cases:\n%v", strings.Join(missed, "\n")) 160 | } 161 | 162 | func modeAll() packages.LoadMode { 163 | mode := packages.NeedName | packages.NeedFiles | packages.NeedCompiledGoFiles 164 | mode = mode | packages.NeedImports | packages.NeedDeps 165 | mode = mode | packages.NeedTypes | packages.NeedTypesSizes 166 | mode = mode | packages.NeedSyntax | packages.NeedTypesInfo 167 | return mode 168 | } 169 | 170 | func buildFlags() []string { 171 | flags := os.Getenv("GOFLAGS") 172 | if len(flags) == 0 { 173 | return nil 174 | } 175 | return strings.Split(os.Getenv("GOFLAGS"), " ") 176 | } 177 | -------------------------------------------------------------------------------- /cmd/tool/slowest/ast_test.go: -------------------------------------------------------------------------------- 1 | package slowest 2 | 3 | import ( 4 | "bytes" 5 | "go/format" 6 | "go/token" 7 | "testing" 8 | 9 | "gotest.tools/v3/assert" 10 | ) 11 | 12 | func TestParseSkipStatement_Preset_testingShort(t *testing.T) { 13 | stmt, err := parseSkipStatement("testing.Short") 14 | assert.NilError(t, err) 15 | expected := `if testing.Short() { 16 | t.Skip("too slow for testing.Short") 17 | }` 18 | buf := new(bytes.Buffer) 19 | err = format.Node(buf, token.NewFileSet(), stmt) 20 | assert.NilError(t, err) 21 | assert.DeepEqual(t, buf.String(), expected) 22 | } 23 | -------------------------------------------------------------------------------- /cmd/tool/slowest/slowest.go: -------------------------------------------------------------------------------- 1 | package slowest 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "os" 7 | "time" 8 | 9 | "github.com/dnephin/pflag" 10 | "gotest.tools/gotestsum/internal/aggregate" 11 | "gotest.tools/gotestsum/internal/log" 12 | "gotest.tools/gotestsum/testjson" 13 | ) 14 | 15 | // Run the command 16 | func Run(name string, args []string) error { 17 | flags, opts := setupFlags(name) 18 | switch err := flags.Parse(args); { 19 | case err == pflag.ErrHelp: 20 | return nil 21 | case err != nil: 22 | usage(os.Stderr, name, flags) 23 | return err 24 | } 25 | return run(opts) 26 | } 27 | 28 | func setupFlags(name string) (*pflag.FlagSet, *options) { 29 | opts := &options{} 30 | flags := pflag.NewFlagSet(name, pflag.ContinueOnError) 31 | flags.SetInterspersed(false) 32 | flags.Usage = func() { 33 | usage(os.Stdout, name, flags) 34 | } 35 | flags.StringVar(&opts.jsonfile, "jsonfile", os.Getenv("GOTESTSUM_JSONFILE"), 36 | "path to test2json output, defaults to stdin") 37 | flags.DurationVar(&opts.threshold, "threshold", 100*time.Millisecond, 38 | "test cases with elapsed time greater than threshold are slow tests") 39 | flags.IntVar(&opts.topN, "num", 0, 40 | "print at most num slowest tests, instead of all tests above the threshold") 41 | flags.StringVar(&opts.skipStatement, "skip-stmt", "", 42 | "add this go statement to slow tests, instead of printing the list of slow tests") 43 | flags.BoolVar(&opts.debug, "debug", false, 44 | "enable debug logging.") 45 | return flags, opts 46 | } 47 | 48 | func usage(out io.Writer, name string, flags *pflag.FlagSet) { 49 | fmt.Fprintf(out, `Usage: 50 | %[1]s [flags] 51 | 52 | Read a json file and print or update tests which are slower than threshold. 53 | The json file may be created with 'gotestsum --jsonfile' or 'go test -json'. 54 | If a TestCase appears more than once in the json file, it will only appear once 55 | in the output, and the median value of all the elapsed times will be used. 56 | 57 | By default this command will print the list of tests slower than threshold to stdout. 58 | The list will be sorted from slowest to fastest. 59 | 60 | If --skip-stmt is set, instead of printing the list to stdout, the AST for the 61 | Go source code in the working directory tree will be modified. The value of 62 | --skip-stmt will be added to Go test files as the first statement in all the test 63 | functions which are slower than threshold. 64 | 65 | The --skip-stmt flag may be set to the name of a predefined statement, or to 66 | Go source code which will be parsed as a go/ast.Stmt. Currently there is only one 67 | predefined statement, --skip-stmt=testing.Short, which uses this Go statement: 68 | 69 | if testing.Short() { 70 | t.Skip("too slow for testing.Short") 71 | } 72 | 73 | 74 | Alternatively, a custom --skip-stmt may be provided as a string: 75 | 76 | skip_stmt=' 77 | if os.GetEnv("TEST_FAST") != "" { 78 | t.Skip("too slow for TEST_FAST") 79 | } 80 | ' 81 | go test -json -short ./... | %[1]s --skip-stmt "$skip_stmt" 82 | 83 | Note that this tool does not add imports, so using a custom statement may require 84 | you to add imports to the file. 85 | 86 | Go build flags, such as build tags, may be set using the GOFLAGS environment 87 | variable, following the same rules as the go toolchain. See 88 | https://golang.org/cmd/go/#hdr-Environment_variables. 89 | 90 | Flags: 91 | `, name) 92 | flags.SetOutput(out) 93 | flags.PrintDefaults() 94 | } 95 | 96 | type options struct { 97 | threshold time.Duration 98 | topN int 99 | jsonfile string 100 | skipStatement string 101 | debug bool 102 | } 103 | 104 | func run(opts *options) error { 105 | if opts.debug { 106 | log.SetLevel(log.DebugLevel) 107 | } 108 | in, err := jsonfileReader(opts.jsonfile) 109 | if err != nil { 110 | return fmt.Errorf("failed to read jsonfile: %v", err) 111 | } 112 | defer func() { 113 | if err := in.Close(); err != nil { 114 | log.Errorf("Failed to close file %v: %v", opts.jsonfile, err) 115 | } 116 | }() 117 | 118 | exec, err := testjson.ScanTestOutput(testjson.ScanConfig{Stdout: in}) 119 | if err != nil { 120 | return fmt.Errorf("failed to scan testjson: %v", err) 121 | } 122 | 123 | tcs := aggregate.Slowest(exec, opts.threshold, opts.topN) 124 | if opts.skipStatement != "" { 125 | skipStmt, err := parseSkipStatement(opts.skipStatement) 126 | if err != nil { 127 | return fmt.Errorf("failed to parse skip expr: %v", err) 128 | } 129 | return writeTestSkip(tcs, skipStmt) 130 | } 131 | for _, tc := range tcs { 132 | fmt.Printf("%s %s %v\n", tc.Package, tc.Test, tc.Elapsed) 133 | } 134 | 135 | return nil 136 | } 137 | 138 | func jsonfileReader(v string) (io.ReadCloser, error) { 139 | switch v { 140 | case "", "-": 141 | return io.NopCloser(os.Stdin), nil 142 | default: 143 | return os.Open(v) 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /cmd/tool/slowest/slowest_test.go: -------------------------------------------------------------------------------- 1 | package slowest 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | 7 | "gotest.tools/v3/env" 8 | "gotest.tools/v3/golden" 9 | ) 10 | 11 | func TestUsage_WithFlagsFromSetupFlags(t *testing.T) { 12 | env.PatchAll(t, nil) 13 | 14 | name := "gotestsum tool slowest" 15 | flags, _ := setupFlags(name) 16 | buf := new(bytes.Buffer) 17 | usage(buf, name, flags) 18 | 19 | golden.Assert(t, buf.String(), "cmd-flags-help-text") 20 | } 21 | -------------------------------------------------------------------------------- /cmd/tool/slowest/testdata/cmd-flags-help-text: -------------------------------------------------------------------------------- 1 | Usage: 2 | gotestsum tool slowest [flags] 3 | 4 | Read a json file and print or update tests which are slower than threshold. 5 | The json file may be created with 'gotestsum --jsonfile' or 'go test -json'. 6 | If a TestCase appears more than once in the json file, it will only appear once 7 | in the output, and the median value of all the elapsed times will be used. 8 | 9 | By default this command will print the list of tests slower than threshold to stdout. 10 | The list will be sorted from slowest to fastest. 11 | 12 | If --skip-stmt is set, instead of printing the list to stdout, the AST for the 13 | Go source code in the working directory tree will be modified. The value of 14 | --skip-stmt will be added to Go test files as the first statement in all the test 15 | functions which are slower than threshold. 16 | 17 | The --skip-stmt flag may be set to the name of a predefined statement, or to 18 | Go source code which will be parsed as a go/ast.Stmt. Currently there is only one 19 | predefined statement, --skip-stmt=testing.Short, which uses this Go statement: 20 | 21 | if testing.Short() { 22 | t.Skip("too slow for testing.Short") 23 | } 24 | 25 | 26 | Alternatively, a custom --skip-stmt may be provided as a string: 27 | 28 | skip_stmt=' 29 | if os.GetEnv("TEST_FAST") != "" { 30 | t.Skip("too slow for TEST_FAST") 31 | } 32 | ' 33 | go test -json -short ./... | gotestsum tool slowest --skip-stmt "$skip_stmt" 34 | 35 | Note that this tool does not add imports, so using a custom statement may require 36 | you to add imports to the file. 37 | 38 | Go build flags, such as build tags, may be set using the GOFLAGS environment 39 | variable, following the same rules as the go toolchain. See 40 | https://golang.org/cmd/go/#hdr-Environment_variables. 41 | 42 | Flags: 43 | --debug enable debug logging. 44 | --jsonfile string path to test2json output, defaults to stdin 45 | --num int print at most num slowest tests, instead of all tests above the threshold 46 | --skip-stmt string add this go statement to slow tests, instead of printing the list of slow tests 47 | --threshold duration test cases with elapsed time greater than threshold are slow tests (default 100ms) 48 | -------------------------------------------------------------------------------- /cmd/watch.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "bufio" 5 | "context" 6 | "fmt" 7 | "os" 8 | "os/exec" 9 | 10 | "gotest.tools/gotestsum/internal/filewatcher" 11 | "gotest.tools/gotestsum/testjson" 12 | ) 13 | 14 | func runWatcher(opts *options) error { 15 | ctx, cancel := context.WithCancel(context.Background()) 16 | defer cancel() 17 | 18 | w := &watchRuns{opts: *opts} 19 | return filewatcher.Watch(ctx, opts.packages, w.run) 20 | } 21 | 22 | type watchRuns struct { 23 | opts options 24 | prevExec *testjson.Execution 25 | } 26 | 27 | func (w *watchRuns) run(event filewatcher.Event) error { 28 | if event.Debug { 29 | path, cleanup, err := delveInitFile(w.prevExec) 30 | if err != nil { 31 | return fmt.Errorf("failed to write delve init file: %w", err) 32 | } 33 | defer cleanup() 34 | o := delveOpts{ 35 | pkgPath: event.PkgPath, 36 | args: w.opts.args, 37 | initFilePath: path, 38 | } 39 | if err := runDelve(o); !IsExitCoder(err) { 40 | return fmt.Errorf("delve failed: %w", err) 41 | } 42 | return nil 43 | } 44 | 45 | var dir string 46 | if w.opts.watchChdir { 47 | dir, event.PkgPath = event.PkgPath, "./" 48 | } 49 | 50 | opts := w.opts // shallow copy opts 51 | opts.packages = append([]string{}, opts.packages...) 52 | opts.packages = append(opts.packages, event.PkgPath) 53 | opts.packages = append(opts.packages, event.Args...) 54 | 55 | var err error 56 | if w.prevExec, err = runSingle(&opts, dir); !IsExitCoder(err) { 57 | return err 58 | } 59 | return nil 60 | } 61 | 62 | // runSingle is similar to run. It doesn't support rerun-fails. It may be 63 | // possible to share runSingle with run, but the defer close on the handler 64 | // would require at least 3 return values, so for now it is a copy. 65 | func runSingle(opts *options, dir string) (*testjson.Execution, error) { 66 | ctx, cancel := context.WithCancel(context.Background()) 67 | defer cancel() 68 | 69 | if err := opts.Validate(); err != nil { 70 | return nil, err 71 | } 72 | 73 | goTestProc, err := startGoTestFn(ctx, dir, goTestCmdArgs(opts, rerunOpts{})) 74 | if err != nil { 75 | return nil, err 76 | } 77 | 78 | handler, err := newEventHandler(opts) 79 | if err != nil { 80 | return nil, err 81 | } 82 | defer handler.Close() //nolint:errcheck 83 | cfg := testjson.ScanConfig{ 84 | Stdout: goTestProc.stdout, 85 | Stderr: goTestProc.stderr, 86 | Handler: handler, 87 | Stop: cancel, 88 | } 89 | exec, err := testjson.ScanTestOutput(cfg) 90 | handler.Flush() 91 | if err != nil { 92 | return exec, finishRun(opts, exec, err) 93 | } 94 | err = goTestProc.cmd.Wait() 95 | return exec, finishRun(opts, exec, err) 96 | } 97 | 98 | func delveInitFile(exec *testjson.Execution) (string, func(), error) { 99 | fh, err := os.CreateTemp("", "gotestsum-delve-init") 100 | if err != nil { 101 | return "", nil, err 102 | } 103 | remove := func() { 104 | os.Remove(fh.Name()) //nolint:errcheck 105 | } 106 | 107 | buf := bufio.NewWriter(fh) 108 | for _, tc := range exec.Failed() { 109 | fmt.Fprintf(buf, "break %s\n", tc.Test.Name()) 110 | } 111 | buf.WriteString("continue\n") 112 | if err := buf.Flush(); err != nil { 113 | remove() 114 | return "", nil, err 115 | } 116 | return fh.Name(), remove, nil 117 | } 118 | 119 | type delveOpts struct { 120 | pkgPath string 121 | args []string 122 | initFilePath string 123 | } 124 | 125 | func runDelve(opts delveOpts) error { 126 | pkg := opts.pkgPath 127 | args := []string{"dlv", "test", "--wd", pkg} 128 | args = append(args, "--output", "gotestsum-watch-debug.test") 129 | args = append(args, "--init", opts.initFilePath) 130 | args = append(args, pkg, "--") 131 | args = append(args, opts.args...) 132 | 133 | cmd := exec.Command(args[0], args[1:]...) 134 | cmd.Stdin = os.Stdin 135 | cmd.Stdout = os.Stdout 136 | cmd.Stderr = os.Stderr 137 | 138 | return cmd.Run() 139 | } 140 | -------------------------------------------------------------------------------- /contrib/notify/.gitignore: -------------------------------------------------------------------------------- 1 | notify 2 | -------------------------------------------------------------------------------- /contrib/notify/Makefile: -------------------------------------------------------------------------------- 1 | GO = go 2 | INSTALL = install 3 | 4 | ICONS = icons/test-pass.svg icons/test-fail.svg 5 | ICONDIR = $(HOME)/.icons # or /usr/share/icons 6 | 7 | build: 8 | $(GO) build 9 | 10 | install: $(ICONS) 11 | $(GO) install 12 | $(INSTALL) -d $(ICONDIR) 13 | $(INSTALL) $^ $(ICONDIR) 14 | -------------------------------------------------------------------------------- /contrib/notify/icons/README.html: -------------------------------------------------------------------------------- 1 | Pass Fail Vectors by Vecteezy 2 | -------------------------------------------------------------------------------- /contrib/notify/icons/test-fail.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /contrib/notify/icons/test-pass.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /contrib/notify/notify_darwin.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | "os/exec" 8 | "strconv" 9 | ) 10 | 11 | func main() { 12 | total := envInt("TOTAL") 13 | skipped := envInt("SKIPPED") 14 | failed := envInt("FAILED") 15 | errors := envInt("ERRORS") 16 | 17 | emoji := "✅" 18 | title := "Passed" 19 | switch { 20 | case errors > 0: 21 | emoji = "⚠️" 22 | title = "Errored" 23 | case failed > 0: 24 | emoji = "❌" 25 | title = "Failed" 26 | case skipped > 0: 27 | title = "Passed with skipped" 28 | } 29 | 30 | subtitle := fmt.Sprintf("%d Tests Run", total) 31 | if errors > 0 { 32 | subtitle += fmt.Sprintf(", %d Errored", errors) 33 | } 34 | if failed > 0 { 35 | subtitle += fmt.Sprintf(", %d Failed", failed) 36 | } 37 | if skipped > 0 { 38 | subtitle += fmt.Sprintf(", %d Skipped", skipped) 39 | } 40 | 41 | args := []string{ 42 | "-title", emoji + " " + title, 43 | "-group", "gotestsum", 44 | "-subtitle", subtitle, 45 | } 46 | cmd := exec.Command("terminal-notifier", args...) 47 | log.Printf("%#v", cmd.Args) 48 | cmd.Stdout = os.Stdout 49 | cmd.Stderr = os.Stderr 50 | if err := cmd.Run(); err != nil { 51 | log.Fatalf("Failed to exec: %v", err) 52 | } 53 | } 54 | 55 | func envInt(name string) int { 56 | val := os.Getenv("TESTS_" + name) 57 | n, err := strconv.Atoi(val) 58 | if err != nil { 59 | return 0 60 | } 61 | return n 62 | } 63 | -------------------------------------------------------------------------------- /contrib/notify/notify_linux.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | "os/exec" 8 | "strconv" 9 | ) 10 | 11 | func main() { 12 | total := envInt("TOTAL") 13 | skipped := envInt("SKIPPED") 14 | failed := envInt("FAILED") 15 | errors := envInt("ERRORS") 16 | 17 | icon := "test-pass" 18 | title := "Passed" 19 | switch { 20 | case errors > 0: 21 | icon = "dialog-warning" 22 | title = "Errored" 23 | case failed > 0: 24 | icon = "test-fail" 25 | title = "Failed" 26 | case skipped > 0: 27 | title = "Passed with skipped" 28 | } 29 | 30 | subtitle := fmt.Sprintf("%d Tests Run", total) 31 | if errors > 0 { 32 | subtitle += fmt.Sprintf(", %d Errored", errors) 33 | } 34 | if failed > 0 { 35 | subtitle += fmt.Sprintf(", %d Failed", failed) 36 | } 37 | if skipped > 0 { 38 | subtitle += fmt.Sprintf(", %d Skipped", skipped) 39 | } 40 | 41 | cmd := exec.Command("notify-send", "--icon", icon, title, subtitle) 42 | log.Printf("%#v", cmd.Args) 43 | cmd.Stdout = os.Stdout 44 | cmd.Stderr = os.Stderr 45 | if err := cmd.Run(); err != nil { 46 | log.Fatalf("Failed to exec: %v", err) 47 | } 48 | } 49 | 50 | func envInt(name string) int { 51 | val := os.Getenv("TESTS_" + name) 52 | n, err := strconv.Atoi(val) 53 | if err != nil { 54 | return 0 55 | } 56 | return n 57 | } 58 | -------------------------------------------------------------------------------- /contrib/notify/notify_windows.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | "os/exec" 8 | "strconv" 9 | ) 10 | 11 | func main() { 12 | total := envInt("TOTAL") 13 | skipped := envInt("SKIPPED") 14 | failed := envInt("FAILED") 15 | errors := envInt("ERRORS") 16 | 17 | icon := "info" // Info 🛈 18 | title := "Passed" 19 | switch { 20 | case errors > 0: 21 | icon = "important" // Warning ⚠ 22 | title = "Errored" 23 | case failed > 0: 24 | icon = "error" // Error ⮾ 25 | title = "Failed" 26 | case skipped > 0: 27 | title = "Passed with skipped" 28 | } 29 | 30 | subtitle := fmt.Sprintf("%d Tests Run", total) 31 | if errors > 0 { 32 | subtitle += fmt.Sprintf(", %d Errored", errors) 33 | } 34 | if failed > 0 { 35 | subtitle += fmt.Sprintf(", %d Failed", failed) 36 | } 37 | if skipped > 0 { 38 | subtitle += fmt.Sprintf(", %d Skipped", skipped) 39 | } 40 | 41 | cmd := exec.Command("notify-send.exe", "-i", icon, title, subtitle) 42 | log.Printf("%#v", cmd.Args) 43 | cmd.Stdout = os.Stdout 44 | cmd.Stderr = os.Stderr 45 | if err := cmd.Run(); err != nil { 46 | log.Fatalf("Failed to exec: %v", err) 47 | } 48 | } 49 | 50 | func envInt(name string) int { 51 | val := os.Getenv("TESTS_" + name) 52 | n, err := strconv.Atoi(val) 53 | if err != nil { 54 | return 0 55 | } 56 | return n 57 | } 58 | -------------------------------------------------------------------------------- /do: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -o errexit -o nounset -o pipefail 3 | 4 | declare -A help 5 | 6 | binary() { 7 | mkdir -p dist 8 | go build -o dist/gotestsum . 9 | } 10 | 11 | binary-static() { 12 | echo "building static binary: dist/gotestsum" 13 | CGO_ENABLED=0 binary 14 | } 15 | 16 | update-golden() { 17 | gotestsum -- ./... -update 18 | } 19 | 20 | lint() { 21 | golangci-lint run -v --config .project/golangci-lint.yml 22 | } 23 | 24 | go-mod-tidy() { 25 | go mod tidy 26 | git diff --stat --exit-code go.mod go.sum 27 | } 28 | 29 | help[shell]='Run a shell in a golang docker container. 30 | 31 | Env vars: 32 | 33 | GOLANG_VERSION - the docker image tag used to build the image. 34 | ' 35 | shell() { 36 | local image; image="$(_docker-build-dev)" 37 | docker run \ 38 | --tty --interactive --rm \ 39 | -v "$PWD:/work" \ 40 | -v ~/.cache/go-build:/root/.cache/go-build \ 41 | -v ~/go/pkg/mod:/go/pkg/mod \ 42 | -w /work \ 43 | "$image" \ 44 | "${@-bash}" 45 | } 46 | 47 | _docker-build-dev() { 48 | set -e 49 | local idfile=".plsdo/docker-build-dev-image-id-${GOLANG_VERSION-default}" 50 | local dockerfile=.project/Dockerfile 51 | local tag=gotest.tools/gotestsum/builder 52 | if [ -f "$idfile" ] && [ "$dockerfile" -ot "$idfile" ]; then 53 | cat "$idfile" 54 | return 0 55 | fi 56 | 57 | mkdir -p .plsdo 58 | >&2 docker build \ 59 | --iidfile "$idfile" \ 60 | --file "$dockerfile" \ 61 | --build-arg "UID=$UID" \ 62 | --build-arg GOLANG_VERSION \ 63 | --target "dev" \ 64 | .plsdo 65 | cat "$idfile" 66 | } 67 | 68 | help[godoc]="Run godoc locally to preview package documentation." 69 | godoc() { 70 | local url; url="http://localhost:6060/pkg/$(go list)/" 71 | command -v xdg-open && xdg-open "$url" & 72 | command -v open && open "$url" & 73 | command godoc -http=:6060 74 | } 75 | 76 | help[list]="Print the list of tasks" 77 | list() { 78 | declare -F | awk '{print $3}' | grep -v '^_' 79 | } 80 | 81 | _plsdo_help() { 82 | local topic="${1-}" 83 | # print help for the topic 84 | if [ -n "$topic" ]; then 85 | if ! command -v "$topic" > /dev/null ; then 86 | >&2 echo "No such task: $topic" 87 | return 1 88 | fi 89 | 90 | printf "\nUsage:\n %s %s\n\n%s\n" "$0" "$topic" "${help[$topic]-}" 91 | return 0 92 | fi 93 | 94 | # print list of tasks and their help line. 95 | [ -n "${banner-}" ] && echo "$banner" && echo 96 | for i in $(list); do 97 | printf "%-12s\t%s\n" "$i" "${help[$i]-}" | head -1 98 | done 99 | } 100 | 101 | _plsdo_run() { 102 | case "${1-}" in 103 | ""|help) 104 | _plsdo_help "${2-}" ;; 105 | *) 106 | "$@" ;; 107 | esac 108 | } 109 | 110 | _plsdo_run "$@" 111 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module gotest.tools/gotestsum 2 | 3 | go 1.23.0 4 | 5 | require ( 6 | github.com/bitfield/gotestdox v0.2.2 7 | github.com/dnephin/pflag v1.0.7 8 | github.com/fatih/color v1.18.0 9 | github.com/fsnotify/fsnotify v1.8.0 10 | github.com/google/go-cmp v0.7.0 11 | github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 12 | golang.org/x/sync v0.14.0 13 | golang.org/x/sys v0.33.0 14 | golang.org/x/term v0.32.0 15 | golang.org/x/tools v0.33.0 16 | gotest.tools/v3 v3.5.2 17 | ) 18 | 19 | require ( 20 | github.com/mattn/go-colorable v0.1.13 // indirect 21 | github.com/mattn/go-isatty v0.0.20 // indirect 22 | golang.org/x/mod v0.24.0 // indirect 23 | golang.org/x/text v0.17.0 // indirect 24 | ) 25 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/bitfield/gotestdox v0.2.2 h1:x6RcPAbBbErKLnapz1QeAlf3ospg8efBsedU93CDsnE= 2 | github.com/bitfield/gotestdox v0.2.2/go.mod h1:D+gwtS0urjBrzguAkTM2wodsTQYFHdpx8eqRJ3N+9pY= 3 | github.com/dnephin/pflag v1.0.7 h1:oxONGlWxhmUct0YzKTgrpQv9AUA1wtPBn7zuSjJqptk= 4 | github.com/dnephin/pflag v1.0.7/go.mod h1:uxE91IoWURlOiTUIA8Mq5ZZkAv3dPUfZNaT80Zm7OQE= 5 | github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= 6 | github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= 7 | github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= 8 | github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= 9 | github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 10 | github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 11 | github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= 12 | github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= 13 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= 14 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= 15 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 16 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 17 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 18 | github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= 19 | github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= 20 | golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= 21 | golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= 22 | golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ= 23 | golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= 24 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 25 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 26 | golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= 27 | golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 28 | golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg= 29 | golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ= 30 | golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= 31 | golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= 32 | golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc= 33 | golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI= 34 | gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= 35 | gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= 36 | -------------------------------------------------------------------------------- /internal/aggregate/slowest.go: -------------------------------------------------------------------------------- 1 | package aggregate 2 | 3 | import ( 4 | "sort" 5 | "time" 6 | 7 | "gotest.tools/gotestsum/testjson" 8 | ) 9 | 10 | // Slowest returns a slice of all tests with an elapsed time greater than 11 | // threshold. The slice is sorted by Elapsed time in descending order (slowest 12 | // test first). 13 | // 14 | // If there are multiple runs of a TestCase, all of them will be represented 15 | // by a single TestCase with the median elapsed time in the returned slice. 16 | func Slowest(exec *testjson.Execution, threshold time.Duration, num int) []testjson.TestCase { 17 | if threshold == 0 && num == 0 { 18 | return nil 19 | } 20 | pkgs := exec.Packages() 21 | tests := make([]testjson.TestCase, 0, len(pkgs)) 22 | for _, pkg := range pkgs { 23 | pkgTests := ByElapsed(exec.Package(pkg).TestCases(), median) 24 | tests = append(tests, pkgTests...) 25 | } 26 | sort.Slice(tests, func(i, j int) bool { 27 | return tests[i].Elapsed > tests[j].Elapsed 28 | }) 29 | if num >= len(tests) { 30 | return tests 31 | } 32 | if num > 0 { 33 | return tests[:num] 34 | } 35 | 36 | end := sort.Search(len(tests), func(i int) bool { 37 | return tests[i].Elapsed < threshold 38 | }) 39 | return tests[:end] 40 | } 41 | 42 | // ByElapsed maps all test cases by name, and if there is more than one 43 | // instance of a TestCase, uses fn to select the elapsed time for the group. 44 | // 45 | // All cases are assumed to be part of the same package. 46 | func ByElapsed(cases []testjson.TestCase, fn func(times []time.Duration) time.Duration) []testjson.TestCase { 47 | if len(cases) <= 1 { 48 | return cases 49 | } 50 | pkg := cases[0].Package 51 | m := make(map[testjson.TestName][]time.Duration) 52 | for _, tc := range cases { 53 | m[tc.Test] = append(m[tc.Test], tc.Elapsed) 54 | } 55 | result := make([]testjson.TestCase, 0, len(m)) 56 | for name, timing := range m { 57 | result = append(result, testjson.TestCase{ 58 | Package: pkg, 59 | Test: name, 60 | Elapsed: fn(timing), 61 | }) 62 | } 63 | return result 64 | } 65 | 66 | func median(times []time.Duration) time.Duration { 67 | switch len(times) { 68 | case 0: 69 | return 0 70 | case 1: 71 | return times[0] 72 | } 73 | sort.Slice(times, func(i, j int) bool { 74 | return times[i] < times[j] 75 | }) 76 | return times[len(times)/2] 77 | } 78 | -------------------------------------------------------------------------------- /internal/aggregate/slowest_test.go: -------------------------------------------------------------------------------- 1 | package aggregate 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "strings" 7 | "testing" 8 | "time" 9 | 10 | "github.com/google/go-cmp/cmp" 11 | "github.com/google/go-cmp/cmp/cmpopts" 12 | "gotest.tools/gotestsum/testjson" 13 | "gotest.tools/v3/assert" 14 | ) 15 | 16 | func TestSlowest(t *testing.T) { 17 | newEvent := func(pkg, test string, elapsed float64) testjson.TestEvent { 18 | return testjson.TestEvent{ 19 | Package: pkg, 20 | Test: test, 21 | Action: testjson.ActionPass, 22 | Elapsed: elapsed, 23 | } 24 | } 25 | 26 | exec := newExecutionFromEvents(t, 27 | newEvent("one", "TestOmega", 22.2), 28 | newEvent("one", "TestOmega", 1.5), 29 | newEvent("one", "TestOmega", 0.6), 30 | newEvent("one", "TestOnion", 0.5), 31 | newEvent("two", "TestTents", 2.5), 32 | newEvent("two", "TestTin", 0.3), 33 | newEvent("two", "TestTunnel", 1.1)) 34 | 35 | cmpCasesShallow := cmp.Comparer(func(x, y testjson.TestCase) bool { 36 | return x.Package == y.Package && x.Test == y.Test 37 | }) 38 | 39 | type testCase struct { 40 | name string 41 | threshold time.Duration 42 | num int 43 | expected []testjson.TestCase 44 | } 45 | 46 | run := func(t *testing.T, tc testCase) { 47 | actual := Slowest(exec, tc.threshold, tc.num) 48 | assert.DeepEqual(t, actual, tc.expected, cmpCasesShallow) 49 | } 50 | 51 | testCases := []testCase{ 52 | { 53 | name: "threshold only", 54 | threshold: time.Second, 55 | expected: []testjson.TestCase{ 56 | {Package: "two", Test: "TestTents"}, 57 | {Package: "one", Test: "TestOmega"}, 58 | {Package: "two", Test: "TestTunnel"}, 59 | }, 60 | }, 61 | { 62 | name: "threshold only 2s", 63 | threshold: 2 * time.Second, 64 | expected: []testjson.TestCase{ 65 | {Package: "two", Test: "TestTents"}, 66 | }, 67 | }, 68 | { 69 | name: "threshold and num", 70 | threshold: 400 * time.Millisecond, 71 | num: 2, 72 | expected: []testjson.TestCase{ 73 | {Package: "two", Test: "TestTents"}, 74 | {Package: "one", Test: "TestOmega"}, 75 | }, 76 | }, 77 | { 78 | name: "num only", 79 | num: 4, 80 | expected: []testjson.TestCase{ 81 | {Package: "two", Test: "TestTents"}, 82 | {Package: "one", Test: "TestOmega"}, 83 | {Package: "two", Test: "TestTunnel"}, 84 | {Package: "one", Test: "TestOnion"}, 85 | }, 86 | }, 87 | } 88 | 89 | for _, tc := range testCases { 90 | t.Run(tc.name, func(t *testing.T) { 91 | run(t, tc) 92 | }) 93 | } 94 | } 95 | 96 | func newExecutionFromEvents(t *testing.T, events ...testjson.TestEvent) *testjson.Execution { 97 | t.Helper() 98 | 99 | buf := new(bytes.Buffer) 100 | encoder := json.NewEncoder(buf) 101 | for i, event := range events { 102 | assert.NilError(t, encoder.Encode(event), "event %d", i) 103 | } 104 | 105 | exec, err := testjson.ScanTestOutput(testjson.ScanConfig{ 106 | Stdout: buf, 107 | Stderr: strings.NewReader(""), 108 | }) 109 | assert.NilError(t, err) 110 | return exec 111 | } 112 | 113 | func TestByElapsed_WithMedian(t *testing.T) { 114 | cases := []testjson.TestCase{ 115 | {Test: "TestOne", Package: "pkg", Elapsed: time.Second}, 116 | {Test: "TestTwo", Package: "pkg", Elapsed: 2 * time.Second}, 117 | {Test: "TestOne", Package: "pkg", Elapsed: 3 * time.Second}, 118 | {Test: "TestTwo", Package: "pkg", Elapsed: 4 * time.Second}, 119 | {Test: "TestOne", Package: "pkg", Elapsed: 5 * time.Second}, 120 | {Test: "TestTwo", Package: "pkg", Elapsed: 6 * time.Second}, 121 | } 122 | actual := ByElapsed(cases, median) 123 | expected := []testjson.TestCase{ 124 | {Test: "TestOne", Package: "pkg", Elapsed: 3 * time.Second}, 125 | {Test: "TestTwo", Package: "pkg", Elapsed: 4 * time.Second}, 126 | } 127 | assert.DeepEqual(t, actual, expected, 128 | cmpopts.SortSlices(func(x, y testjson.TestCase) bool { 129 | return strings.Compare(x.Test.Name(), y.Test.Name()) == -1 130 | }), 131 | cmpopts.IgnoreUnexported(testjson.TestCase{})) 132 | } 133 | 134 | func TestMedian(t *testing.T) { 135 | var testcases = []struct { 136 | name string 137 | times []time.Duration 138 | expected time.Duration 139 | }{ 140 | { 141 | name: "one item slice", 142 | times: []time.Duration{time.Minute}, 143 | expected: time.Minute, 144 | }, 145 | { 146 | name: "odd number of items", 147 | times: []time.Duration{time.Millisecond, time.Hour, time.Second}, 148 | expected: time.Second, 149 | }, 150 | { 151 | name: "even number of items", 152 | times: []time.Duration{time.Second, time.Millisecond, time.Microsecond, time.Hour}, 153 | expected: time.Second, 154 | }, 155 | } 156 | 157 | for _, tc := range testcases { 158 | t.Run(tc.name, func(t *testing.T) { 159 | actual := median(tc.times) 160 | assert.Equal(t, actual, tc.expected) 161 | }) 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /internal/dotwriter/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | =========== 3 | 4 | Copyright (c) 2015, Greg Osuri 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is furnished 11 | to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /internal/dotwriter/README: -------------------------------------------------------------------------------- 1 | This package contains a striped down and modified version of 2 | https://github.com/gosuri/uilive. The original package did not work with 3 | terminal colors, and had some bits that were unnecessary for gotestsum. 4 | -------------------------------------------------------------------------------- /internal/dotwriter/writer.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package dotwriter implements a buffered Writer for updating progress on the 3 | terminal. 4 | */ 5 | package dotwriter 6 | 7 | import ( 8 | "bytes" 9 | "io" 10 | ) 11 | 12 | // ESC is the ASCII code for escape character 13 | const ESC = 27 14 | 15 | // Writer buffers writes until Flush is called. Flush clears previously written 16 | // lines before writing new lines from the buffer. 17 | // The main logic is platform specific, see the related files. 18 | type Writer struct { 19 | out io.Writer 20 | buf bytes.Buffer 21 | lineCount int 22 | } 23 | 24 | // New returns a new Writer 25 | func New(out io.Writer) *Writer { 26 | return &Writer{out: out} 27 | } 28 | 29 | // Write saves buf to a buffer 30 | func (w *Writer) Write(buf []byte) (int, error) { 31 | return w.buf.Write(buf) 32 | } 33 | -------------------------------------------------------------------------------- /internal/dotwriter/writer_posix.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | // +build !windows 3 | 4 | package dotwriter 5 | 6 | import ( 7 | "bytes" 8 | "fmt" 9 | ) 10 | 11 | // hide cursor 12 | var hide = fmt.Sprintf("%c[?25l", ESC) 13 | 14 | // show cursor 15 | var show = fmt.Sprintf("%c[?25h", ESC) 16 | 17 | // Flush the buffer, writing all buffered lines to out 18 | func (w *Writer) Flush() error { 19 | if w.buf.Len() == 0 { 20 | return nil 21 | } 22 | // Hide cursor during write to avoid it moving around the screen 23 | defer w.hideCursor()() 24 | 25 | // Move up to the top of our last output. 26 | w.up(w.lineCount) 27 | lines := bytes.Split(w.buf.Bytes(), []byte{'\n'}) 28 | w.lineCount = len(lines) - 1 // Record how many lines we will write for the next Flush() 29 | for i, line := range lines { 30 | // For each line, write the contents and clear everything else on the line 31 | _, err := w.out.Write(line) 32 | if err != nil { 33 | return err 34 | } 35 | w.clearRest() 36 | // Add a newline if this isn't the last line 37 | if i != len(lines)-1 { 38 | _, err := w.out.Write([]byte{'\n'}) 39 | if err != nil { 40 | return err 41 | } 42 | } 43 | } 44 | w.buf.Reset() 45 | return nil 46 | } 47 | 48 | func (w *Writer) up(count int) { 49 | if count == 0 { 50 | return 51 | } 52 | _, _ = fmt.Fprintf(w.out, "%c[%dA", ESC, count) 53 | } 54 | 55 | func (w *Writer) clearRest() { 56 | _, _ = fmt.Fprintf(w.out, "%c[0K", ESC) 57 | } 58 | 59 | // hideCursor hides the cursor and returns a function to restore the cursor back. 60 | func (w *Writer) hideCursor() func() { 61 | _, _ = fmt.Fprint(w.out, hide) 62 | return func() { 63 | _, _ = fmt.Fprint(w.out, show) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /internal/dotwriter/writer_windows.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | // +build windows 3 | 4 | package dotwriter 5 | 6 | import ( 7 | "bytes" 8 | "fmt" 9 | "io" 10 | "strings" 11 | "syscall" 12 | "unsafe" 13 | 14 | "golang.org/x/sys/windows" 15 | ) 16 | 17 | var kernel32 = syscall.NewLazyDLL("kernel32.dll") 18 | 19 | var ( 20 | procSetConsoleCursorPosition = kernel32.NewProc("SetConsoleCursorPosition") 21 | procFillConsoleOutputCharacter = kernel32.NewProc("FillConsoleOutputCharacterW") 22 | ) 23 | 24 | // clear the line and move the cursor up 25 | var clear = fmt.Sprintf("%c[%dA%c[2K\r", ESC, 0, ESC) 26 | 27 | type dword uint32 28 | 29 | type coord struct { 30 | x int16 31 | y int16 32 | } 33 | 34 | type fdWriter interface { 35 | io.Writer 36 | Fd() uintptr 37 | } 38 | 39 | // Flush implementation on windows is not ideal; we clear the entire screen before writing, which can result in flashing output 40 | // Windows likely can adopt the same approach as posix if someone invests some effort 41 | func (w *Writer) Flush() error { 42 | if w.buf.Len() == 0 { 43 | return nil 44 | } 45 | w.clearLines(w.lineCount) 46 | w.lineCount = bytes.Count(w.buf.Bytes(), []byte{'\n'}) 47 | _, err := w.out.Write(w.buf.Bytes()) 48 | w.buf.Reset() 49 | return err 50 | } 51 | 52 | func (w *Writer) clearLines(count int) { 53 | f, ok := w.out.(fdWriter) 54 | if ok && !isConsole(f.Fd()) { 55 | ok = false 56 | } 57 | if !ok { 58 | _, _ = fmt.Fprint(w.out, strings.Repeat(clear, count)) 59 | return 60 | } 61 | fd := f.Fd() 62 | 63 | var csbi windows.ConsoleScreenBufferInfo 64 | if err := windows.GetConsoleScreenBufferInfo(windows.Handle(fd), &csbi); err != nil { 65 | return 66 | } 67 | 68 | for i := 0; i < count; i++ { 69 | // move the cursor up 70 | csbi.CursorPosition.Y-- 71 | _, _, _ = procSetConsoleCursorPosition.Call(fd, uintptr(*(*int32)(unsafe.Pointer(&csbi.CursorPosition)))) 72 | // clear the line 73 | cursor := coord{ 74 | x: csbi.Window.Left, 75 | y: csbi.Window.Top + csbi.CursorPosition.Y, 76 | } 77 | var count, w dword 78 | count = dword(csbi.Size.X) 79 | _, _, _ = procFillConsoleOutputCharacter.Call(fd, uintptr(' '), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&w))) 80 | } 81 | } 82 | 83 | func isConsole(fd uintptr) bool { 84 | var mode uint32 85 | err := windows.GetConsoleMode(windows.Handle(fd), &mode) 86 | return err == nil 87 | } 88 | -------------------------------------------------------------------------------- /internal/filewatcher/term_aix.go: -------------------------------------------------------------------------------- 1 | //go:build aix 2 | // +build aix 3 | 4 | package filewatcher 5 | 6 | import "golang.org/x/sys/unix" 7 | 8 | const tcGet = unix.TCGETA 9 | const tcSet = unix.TCSETA 10 | -------------------------------------------------------------------------------- /internal/filewatcher/term_bsd.go: -------------------------------------------------------------------------------- 1 | //go:build darwin || dragonfly || freebsd || netbsd || openbsd 2 | // +build darwin dragonfly freebsd netbsd openbsd 3 | 4 | package filewatcher 5 | 6 | import "golang.org/x/sys/unix" 7 | 8 | const tcGet = unix.TIOCGETA 9 | const tcSet = unix.TIOCSETA 10 | -------------------------------------------------------------------------------- /internal/filewatcher/term_illumos.go: -------------------------------------------------------------------------------- 1 | package filewatcher 2 | 3 | import "golang.org/x/sys/unix" 4 | 5 | const tcGet = unix.TCGETS 6 | const tcSet = unix.TCSETS 7 | -------------------------------------------------------------------------------- /internal/filewatcher/term_linux.go: -------------------------------------------------------------------------------- 1 | package filewatcher 2 | 3 | import "golang.org/x/sys/unix" 4 | 5 | const tcGet = unix.TCGETS 6 | const tcSet = unix.TCSETS 7 | -------------------------------------------------------------------------------- /internal/filewatcher/term_unix.go: -------------------------------------------------------------------------------- 1 | //go:build !windows && !aix 2 | // +build !windows,!aix 3 | 4 | package filewatcher 5 | 6 | import ( 7 | "bufio" 8 | "context" 9 | "fmt" 10 | "io" 11 | "os" 12 | 13 | "golang.org/x/sys/unix" 14 | "gotest.tools/gotestsum/internal/log" 15 | ) 16 | 17 | type terminal struct { 18 | ch chan Event 19 | reset func() 20 | } 21 | 22 | func newTerminal() *terminal { 23 | h := &terminal{ch: make(chan Event)} 24 | h.Start() 25 | return h 26 | } 27 | 28 | // Start the terminal is non-blocking read mode. The terminal can be reset to 29 | // normal mode by calling Reset. If os.Stdin is not a terminal or cannot use 30 | // non-blocking reads then a warning is logged and the terminal is not reset. 31 | func (r *terminal) Start() { 32 | if r == nil { 33 | return 34 | } 35 | fd := int(os.Stdin.Fd()) 36 | reset, err := enableNonBlockingRead(fd) 37 | if err != nil { 38 | log.Warnf("no terminal input -- keyboard shortcuts disabled: %v", err) 39 | return 40 | } 41 | r.reset = reset 42 | } 43 | 44 | func enableNonBlockingRead(fd int) (func(), error) { 45 | term, err := unix.IoctlGetTermios(fd, tcGet) 46 | if err != nil { 47 | return nil, err 48 | } 49 | 50 | state := *term 51 | reset := func() { 52 | if err := unix.IoctlSetTermios(fd, tcSet, &state); err != nil { 53 | log.Debugf("failed to reset fd %d: %v", fd, err) 54 | } 55 | } 56 | 57 | term.Lflag &^= unix.ECHO | unix.ICANON 58 | term.Cc[unix.VMIN] = 1 59 | term.Cc[unix.VTIME] = 0 60 | if err := unix.IoctlSetTermios(fd, tcSet, term); err != nil { 61 | reset() 62 | return nil, err 63 | } 64 | return reset, nil 65 | } 66 | 67 | var stdin io.Reader = os.Stdin 68 | 69 | // Monitor the terminal for key presses. If the key press is associated with an 70 | // action, an event will be sent to channel returned by Events. 71 | func (r *terminal) Monitor(ctx context.Context) { 72 | if r == nil { 73 | return 74 | } 75 | in := bufio.NewReader(stdin) 76 | for { 77 | char, err := in.ReadByte() 78 | if err != nil { 79 | log.Warnf("failed to read input: %v", err) 80 | return 81 | } 82 | log.Debugf("received byte %v (%v)", char, string(char)) 83 | 84 | chResume := make(chan struct{}) 85 | switch char { 86 | case 'r': 87 | r.ch <- Event{resume: chResume, useLastPath: true} 88 | case 'd': 89 | r.ch <- Event{resume: chResume, useLastPath: true, Debug: true} 90 | case 'a': 91 | r.ch <- Event{resume: chResume, PkgPath: "./..."} 92 | case 'l': 93 | r.ch <- Event{resume: chResume, reloadPaths: true} 94 | case 'u': 95 | r.ch <- Event{resume: chResume, useLastPath: true, Args: []string{"-update"}} 96 | case '\n': 97 | fmt.Println() 98 | continue 99 | default: 100 | continue 101 | } 102 | 103 | select { 104 | case <-ctx.Done(): 105 | return 106 | case <-chResume: 107 | } 108 | } 109 | } 110 | 111 | // Events returns a channel which will receive events when keys are pressed. 112 | // When an event is received, the caller must close the resume channel to 113 | // resume monitoring for events. 114 | func (r *terminal) Events() <-chan Event { 115 | if r == nil { 116 | return nil 117 | } 118 | return r.ch 119 | } 120 | 121 | func (r *terminal) Reset() { 122 | if r != nil && r.reset != nil { 123 | r.reset() 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /internal/filewatcher/term_windows.go: -------------------------------------------------------------------------------- 1 | package filewatcher 2 | 3 | import "context" 4 | 5 | type terminal struct{} 6 | 7 | func newTerminal() *terminal { 8 | return nil 9 | } 10 | 11 | func (r *terminal) Monitor(context.Context) {} 12 | 13 | func (r *terminal) Events() <-chan Event { 14 | return nil 15 | } 16 | 17 | func (r *terminal) Start() {} 18 | 19 | func (r *terminal) Reset() {} 20 | -------------------------------------------------------------------------------- /internal/filewatcher/term_zos.go: -------------------------------------------------------------------------------- 1 | package filewatcher 2 | 3 | import "golang.org/x/sys/unix" 4 | 5 | const tcGet = unix.TCGETS 6 | const tcSet = unix.TCSETS 7 | -------------------------------------------------------------------------------- /internal/filewatcher/watch_test.go: -------------------------------------------------------------------------------- 1 | package filewatcher 2 | 3 | import ( 4 | "fmt" 5 | "path/filepath" 6 | "testing" 7 | "time" 8 | 9 | "github.com/fsnotify/fsnotify" 10 | "gotest.tools/v3/assert" 11 | "gotest.tools/v3/env" 12 | "gotest.tools/v3/fs" 13 | ) 14 | 15 | func TestFSEventHandler_HandleEvent(t *testing.T) { 16 | type testCase struct { 17 | name string 18 | last time.Time 19 | expectedRun bool 20 | event fsnotify.Event 21 | } 22 | 23 | fn := func(t *testing.T, tc testCase) { 24 | var ran bool 25 | run := func(Event) error { 26 | ran = true 27 | return nil 28 | } 29 | 30 | h := fsEventHandler{last: tc.last, fn: run} 31 | err := h.handleEvent(tc.event) 32 | assert.NilError(t, err) 33 | assert.Equal(t, ran, tc.expectedRun) 34 | if tc.expectedRun { 35 | assert.Assert(t, !h.last.IsZero()) 36 | } 37 | } 38 | 39 | var testCases = []testCase{ 40 | { 41 | name: "Op is rename", 42 | event: fsnotify.Event{Op: fsnotify.Rename, Name: "file_test.go"}, 43 | expectedRun: true, 44 | }, 45 | { 46 | name: "Op is remove", 47 | event: fsnotify.Event{Op: fsnotify.Remove, Name: "file_test.go"}, 48 | }, 49 | { 50 | name: "Op is chmod", 51 | event: fsnotify.Event{Op: fsnotify.Chmod, Name: "file_test.go"}, 52 | }, 53 | { 54 | name: "Op is write+chmod", 55 | event: fsnotify.Event{Op: fsnotify.Write | fsnotify.Chmod, Name: "file_test.go"}, 56 | expectedRun: true, 57 | }, 58 | { 59 | name: "Op is write", 60 | event: fsnotify.Event{Op: fsnotify.Write, Name: "file_test.go"}, 61 | expectedRun: true, 62 | }, 63 | { 64 | name: "Op is create", 65 | event: fsnotify.Event{Op: fsnotify.Create, Name: "file_test.go"}, 66 | expectedRun: true, 67 | }, 68 | { 69 | name: "file is not a go file", 70 | event: fsnotify.Event{Op: fsnotify.Write, Name: "readme.md"}, 71 | }, 72 | { 73 | name: "under flood threshold", 74 | event: fsnotify.Event{Op: fsnotify.Create, Name: "file_test.go"}, 75 | last: time.Now(), 76 | }, 77 | } 78 | for _, tc := range testCases { 79 | t.Run(tc.name, func(t *testing.T) { 80 | fn(t, tc) 81 | }) 82 | } 83 | } 84 | 85 | func TestHasGoFiles(t *testing.T) { 86 | t.Run("none", func(t *testing.T) { 87 | tmpDir := fs.NewDir(t, t.Name(), fs.WithFile("readme.md", "")) 88 | defer tmpDir.Remove() 89 | assert.Assert(t, !hasGoFiles(tmpDir.Path())) 90 | }) 91 | t.Run("empty", func(t *testing.T) { 92 | tmpDir := fs.NewDir(t, t.Name()) 93 | defer tmpDir.Remove() 94 | assert.Assert(t, !hasGoFiles(tmpDir.Path())) 95 | }) 96 | t.Run("some go files", func(t *testing.T) { 97 | tmpDir := fs.NewDir(t, t.Name(), fs.WithFile("main.go", "")) 98 | defer tmpDir.Remove() 99 | assert.Assert(t, hasGoFiles(tmpDir.Path())) 100 | }) 101 | t.Run("many go files", func(t *testing.T) { 102 | tmpDir := fs.NewDir(t, t.Name()) 103 | for i := 0; i < 47; i++ { 104 | fs.Apply(t, tmpDir, fs.WithFile(fmt.Sprintf("file%d.go", i), "")) 105 | } 106 | defer tmpDir.Remove() 107 | assert.Assert(t, hasGoFiles(tmpDir.Path())) 108 | }) 109 | } 110 | 111 | func TestFindAllDirs(t *testing.T) { 112 | goFile := fs.WithFile("file.go", "") 113 | dirOne := fs.NewDir(t, t.Name(), 114 | goFile, 115 | fs.WithFile("not-a-dir", ""), 116 | fs.WithDir("no-go-files"), 117 | fs.WithDir(".starts-with-dot", goFile)) 118 | defer dirOne.Remove() 119 | var path string 120 | for i := 1; i <= 10; i++ { 121 | path = filepath.Join(path, fmt.Sprintf("%d", i)) 122 | var ops []fs.PathOp 123 | if i != 4 && i != 5 { 124 | ops = []fs.PathOp{goFile} 125 | } 126 | fs.Apply(t, dirOne, fs.WithDir(path, ops...)) 127 | } 128 | 129 | dirTwo := fs.NewDir(t, t.Name(), 130 | goFile, 131 | // subdir should be ignored, dirTwo is used without /... suffix 132 | fs.WithDir("subdir", goFile)) 133 | defer dirTwo.Remove() 134 | 135 | dirs := findAllDirs([]string{dirOne.Path() + "/...", dirTwo.Path()}, maxDepth) 136 | expected := []string{ 137 | dirOne.Path(), 138 | dirOne.Join("1"), 139 | dirOne.Join("1/2"), 140 | dirOne.Join("1/2/3"), 141 | dirOne.Join("1/2/3/4/5/6"), 142 | dirOne.Join("1/2/3/4/5/6/7"), 143 | dirTwo.Path(), 144 | } 145 | assert.DeepEqual(t, dirs, expected) 146 | } 147 | 148 | func TestFindAllDirs_DefaultPath(t *testing.T) { 149 | goFile := fs.WithFile("file.go", "") 150 | dirOne := fs.NewDir(t, t.Name(), 151 | goFile, 152 | fs.WithDir("a", goFile), 153 | fs.WithDir("b", goFile)) 154 | defer dirOne.Remove() 155 | 156 | defer env.ChangeWorkingDir(t, dirOne.Path())() 157 | dirs := findAllDirs([]string{}, maxDepth) 158 | expected := []string{".", "a", "b"} 159 | assert.DeepEqual(t, dirs, expected) 160 | } 161 | -------------------------------------------------------------------------------- /internal/filewatcher/watch_unix_test.go: -------------------------------------------------------------------------------- 1 | //go:build !windows && !aix 2 | // +build !windows,!aix 3 | 4 | package filewatcher 5 | 6 | import ( 7 | "context" 8 | "io" 9 | "testing" 10 | "time" 11 | 12 | "github.com/google/go-cmp/cmp" 13 | "github.com/google/go-cmp/cmp/cmpopts" 14 | "gotest.tools/v3/assert" 15 | "gotest.tools/v3/fs" 16 | ) 17 | 18 | func TestWatch(t *testing.T) { 19 | ctx, cancel := context.WithCancel(context.Background()) 20 | t.Cleanup(cancel) 21 | dir := fs.NewDir(t, t.Name()) 22 | 23 | r, w := io.Pipe() 24 | patchStdin(t, r) 25 | patchFloodThreshold(t, 0) 26 | 27 | chEvents := make(chan Event, 1) 28 | capture := func(event Event) error { 29 | chEvents <- event 30 | return nil 31 | } 32 | 33 | go func() { 34 | err := Watch(ctx, []string{dir.Path()}, capture) 35 | assert.Check(t, err) 36 | }() 37 | 38 | t.Run("run all tests", func(t *testing.T) { 39 | _, err := w.Write([]byte("a")) 40 | assert.NilError(t, err) 41 | 42 | event := <-chEvents 43 | expected := Event{PkgPath: "./..."} 44 | assert.DeepEqual(t, event, expected, cmpEvent) 45 | }) 46 | 47 | t.Run("run tests on file change", func(t *testing.T) { 48 | fs.Apply(t, dir, fs.WithFile("file.go", "")) 49 | 50 | event := <-chEvents 51 | expected := Event{PkgPath: "./" + dir.Path()} 52 | assert.DeepEqual(t, event, expected, cmpEvent) 53 | 54 | t.Run("and rerun", func(t *testing.T) { 55 | _, err := w.Write([]byte("r")) 56 | assert.NilError(t, err) 57 | 58 | event := <-chEvents 59 | expected := Event{PkgPath: "./" + dir.Path(), useLastPath: true} 60 | assert.DeepEqual(t, event, expected, cmpEvent) 61 | }) 62 | 63 | t.Run("and debug", func(t *testing.T) { 64 | _, err := w.Write([]byte("d")) 65 | assert.NilError(t, err) 66 | 67 | event := <-chEvents 68 | expected := Event{ 69 | PkgPath: "./" + dir.Path(), 70 | useLastPath: true, 71 | Debug: true, 72 | } 73 | assert.DeepEqual(t, event, expected, cmpEvent) 74 | }) 75 | 76 | t.Run("and update", func(t *testing.T) { 77 | _, err := w.Write([]byte("u")) 78 | assert.NilError(t, err) 79 | 80 | event := <-chEvents 81 | expected := Event{ 82 | PkgPath: "./" + dir.Path(), 83 | Args: []string{"-update"}, 84 | useLastPath: true, 85 | } 86 | assert.DeepEqual(t, event, expected, cmpEvent) 87 | }) 88 | }) 89 | } 90 | 91 | var cmpEvent = cmp.Options{ 92 | cmp.AllowUnexported(Event{}), 93 | cmpopts.IgnoreTypes(make(chan struct{})), 94 | } 95 | 96 | func patchStdin(t *testing.T, in io.Reader) { 97 | orig := stdin 98 | stdin = in 99 | t.Cleanup(func() { 100 | stdin = orig 101 | }) 102 | } 103 | 104 | func patchFloodThreshold(t *testing.T, d time.Duration) { 105 | orig := floodThreshold 106 | floodThreshold = d 107 | t.Cleanup(func() { 108 | floodThreshold = orig 109 | }) 110 | } 111 | -------------------------------------------------------------------------------- /internal/filewatcher/watch_unsupported.go: -------------------------------------------------------------------------------- 1 | //go:build aix 2 | // +build aix 3 | 4 | package filewatcher 5 | 6 | import ( 7 | "fmt" 8 | "runtime" 9 | ) 10 | 11 | type Event struct { 12 | PkgPath string 13 | Debug bool 14 | } 15 | 16 | func Watch(dirs []string, run func(Event) error) error { 17 | return fmt.Errorf("file watching is not supported on %v/%v", runtime.GOOS, runtime.GOARCH) 18 | } 19 | -------------------------------------------------------------------------------- /internal/junitxml/report_test.go: -------------------------------------------------------------------------------- 1 | package junitxml 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | "os" 8 | "runtime" 9 | "testing" 10 | "time" 11 | 12 | "gotest.tools/gotestsum/testjson" 13 | "gotest.tools/v3/assert" 14 | "gotest.tools/v3/golden" 15 | ) 16 | 17 | func TestWrite(t *testing.T) { 18 | out := new(bytes.Buffer) 19 | exec := createExecution(t) 20 | 21 | t.Setenv("GOVERSION", "go7.7.7") 22 | err := Write(out, exec, Config{ 23 | ProjectName: "test", 24 | customTimestamp: new(time.Time).Format(time.RFC3339), 25 | customElapsed: "2.1", 26 | }) 27 | assert.NilError(t, err) 28 | golden.Assert(t, out.String(), "junitxml-report.golden") 29 | } 30 | 31 | func TestWrite_HideEmptyPackages(t *testing.T) { 32 | out := new(bytes.Buffer) 33 | exec := createExecution(t) 34 | 35 | t.Setenv("GOVERSION", "go7.7.7") 36 | err := Write(out, exec, Config{ 37 | ProjectName: "test", 38 | HideEmptyPackages: true, 39 | customTimestamp: new(time.Time).Format(time.RFC3339), 40 | customElapsed: "2.1", 41 | }) 42 | assert.NilError(t, err) 43 | golden.Assert(t, out.String(), "junitxml-report-skip-empty.golden") 44 | } 45 | 46 | func TestWrite_HideSkippedTests(t *testing.T) { 47 | out := new(bytes.Buffer) 48 | exec := createExecution(t) 49 | 50 | t.Setenv("GOVERSION", "go7.7.7") 51 | err := Write(out, exec, Config{ 52 | ProjectName: "test", 53 | HideSkippedTests: true, 54 | customTimestamp: new(time.Time).Format(time.RFC3339), 55 | customElapsed: "2.1", 56 | }) 57 | assert.NilError(t, err) 58 | golden.Assert(t, out.String(), "junitxml-report-hide-skipped-tests.golden") 59 | } 60 | 61 | func createExecution(t *testing.T) *testjson.Execution { 62 | exec, err := testjson.ScanTestOutput(testjson.ScanConfig{ 63 | Stdout: readTestData(t, "out"), 64 | Stderr: readTestData(t, "err"), 65 | }) 66 | assert.NilError(t, err) 67 | return exec 68 | } 69 | 70 | func readTestData(t *testing.T, stream string) io.Reader { 71 | raw, err := os.ReadFile("../../testjson/testdata/input/go-test-json." + stream) 72 | assert.NilError(t, err) 73 | return bytes.NewReader(raw) 74 | } 75 | 76 | func TestGoVersion(t *testing.T) { 77 | t.Run("unknown", func(t *testing.T) { 78 | t.Setenv("PATH", "/bogus") 79 | assert.Equal(t, goVersion(), "unknown") 80 | }) 81 | 82 | t.Run("current version", func(t *testing.T) { 83 | expected := fmt.Sprintf("%s %s/%s", runtime.Version(), runtime.GOOS, runtime.GOARCH) 84 | assert.Equal(t, goVersion(), expected) 85 | }) 86 | } 87 | -------------------------------------------------------------------------------- /internal/log/log.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/fatih/color" 7 | ) 8 | 9 | type Level uint8 10 | 11 | const ( 12 | ErrorLevel Level = iota 13 | WarnLevel 14 | InfoLevel 15 | DebugLevel 16 | ) 17 | 18 | var ( 19 | level = WarnLevel 20 | out = color.Error 21 | ) 22 | 23 | // SetLevel for the global logger. 24 | func SetLevel(l Level) { 25 | level = l 26 | } 27 | 28 | // Warnf prints the message to stderr, with a yellow WARN prefix. 29 | func Warnf(format string, args ...interface{}) { 30 | if level < WarnLevel { 31 | return 32 | } 33 | fmt.Fprint(out, color.YellowString("WARN ")) 34 | fmt.Fprintf(out, format, args...) 35 | fmt.Fprint(out, "\n") 36 | } 37 | 38 | // Debugf prints the message to stderr, with no prefix. 39 | func Debugf(format string, args ...interface{}) { 40 | if level < DebugLevel { 41 | return 42 | } 43 | fmt.Fprintf(out, format, args...) 44 | fmt.Fprint(out, "\n") 45 | } 46 | 47 | // Infof prints the message to stderr, with no prefix. 48 | func Infof(format string, args ...interface{}) { 49 | if level < InfoLevel { 50 | return 51 | } 52 | fmt.Fprintf(out, format, args...) 53 | fmt.Fprint(out, "\n") 54 | } 55 | 56 | // Errorf prints the message to stderr, with a red ERROR prefix. 57 | func Errorf(format string, args ...interface{}) { 58 | if level < ErrorLevel { 59 | return 60 | } 61 | fmt.Fprint(out, color.RedString("ERROR ")) 62 | fmt.Fprintf(out, format, args...) 63 | fmt.Fprint(out, "\n") 64 | } 65 | 66 | // Error prints the message to stderr, with a red ERROR prefix. 67 | func Error(msg string) { 68 | if level < ErrorLevel { 69 | return 70 | } 71 | fmt.Fprint(out, color.RedString("ERROR ")) 72 | fmt.Fprintln(out, msg) 73 | } 74 | -------------------------------------------------------------------------------- /internal/text/testing.go: -------------------------------------------------------------------------------- 1 | package text 2 | 3 | import ( 4 | "bufio" 5 | "io" 6 | "strings" 7 | 8 | "gotest.tools/v3/assert" 9 | ) 10 | 11 | type TestingT interface { 12 | Helper() 13 | assert.TestingT 14 | } 15 | 16 | // ProcessLines from the Reader by passing each one to ops. The output of each 17 | // op is passed to the next. Returns the string created by joining all the 18 | // processed lines. 19 | func ProcessLines(t TestingT, r io.Reader, ops ...func(string) string) string { 20 | t.Helper() 21 | out := new(strings.Builder) 22 | scan := bufio.NewScanner(r) 23 | for scan.Scan() { 24 | line := scan.Text() 25 | for _, op := range ops { 26 | line = op(line) 27 | } 28 | out.WriteString(line + "\n") 29 | } 30 | assert.NilError(t, scan.Err()) 31 | return out.String() 32 | } 33 | 34 | func OpRemoveSummaryLineElapsedTime(line string) string { 35 | if i := strings.Index(line, " in "); i > 0 { 36 | return line[:i] 37 | } 38 | return line 39 | } 40 | 41 | func OpRemoveTestElapsedTime(line string) string { 42 | if i := strings.Index(line, " (0."); i > 0 && i+8 == len(line) { 43 | return line[:i] 44 | } 45 | return line 46 | } 47 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "gotest.tools/gotestsum/cmd" 8 | "gotest.tools/gotestsum/cmd/tool/matrix" 9 | "gotest.tools/gotestsum/cmd/tool/slowest" 10 | "gotest.tools/gotestsum/internal/log" 11 | ) 12 | 13 | func main() { 14 | err := route(os.Args) 15 | switch { 16 | case err == nil: 17 | return 18 | case cmd.IsExitCoder(err): 19 | // go test should already report the error to stderr, exit with 20 | // the same status code 21 | os.Exit(cmd.ExitCodeWithDefault(err)) 22 | default: 23 | log.Error(err.Error()) 24 | os.Exit(3) 25 | } 26 | } 27 | 28 | func route(args []string) error { 29 | name := args[0] 30 | next, rest := nextArg(args[1:]) 31 | switch next { 32 | case "help", "?": 33 | return cmd.Run(name, []string{"--help"}) 34 | case "tool": 35 | return toolRun(name+" "+next, rest) 36 | default: 37 | return cmd.Run(name, args[1:]) 38 | } 39 | } 40 | 41 | // nextArg splits args into the next positional argument and any remaining args. 42 | func nextArg(args []string) (string, []string) { 43 | switch len(args) { 44 | case 0: 45 | return "", nil 46 | case 1: 47 | return args[0], nil 48 | default: 49 | return args[0], args[1:] 50 | } 51 | } 52 | 53 | func toolRun(name string, args []string) error { 54 | usage := func(name string) string { 55 | return fmt.Sprintf(`Usage: %[1]s COMMAND [flags] 56 | 57 | Commands: 58 | %[1]s slowest find or skip the slowest tests 59 | %[1]s ci-matrix use previous test runtime to place packages into optimal buckets 60 | 61 | Use '%[1]s COMMAND --help' for command specific help. 62 | `, name) 63 | } 64 | 65 | next, rest := nextArg(args) 66 | switch next { 67 | case "", "help", "?": 68 | fmt.Println(usage(name)) 69 | return nil 70 | case "slowest": 71 | return slowest.Run(name+" "+next, rest) 72 | case "ci-matrix": 73 | return matrix.Run(name+" "+next, rest) 74 | default: 75 | fmt.Fprintln(os.Stderr, usage(name)) 76 | return fmt.Errorf("invalid command: %v %v", name, next) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /testjson/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package testjson scans test2json output and builds up a summary of the events. 3 | Events are passed to a formatter for output. 4 | 5 | # Example 6 | 7 | This example reads the test2json output from os.Stdin. It sends every 8 | event to the handler, builds an Execution from the output, then it 9 | prints the number of tests run. 10 | 11 | exec, err := testjson.ScanTestOutput(testjson.ScanConfig{ 12 | // Stdout is a reader that provides the test2json output stream. 13 | Stdout: os.Stdin, 14 | // Handler receives TestEvents and error lines. 15 | Handler: eventHandler, 16 | }) 17 | if err != nil { 18 | return fmt.Errorf("failed to scan testjson: %w", err) 19 | } 20 | fmt.Println("Ran %d tests", exec.Total()) 21 | */ 22 | package testjson // import "gotest.tools/gotestsum/testjson" 23 | -------------------------------------------------------------------------------- /testjson/dotformat.go: -------------------------------------------------------------------------------- 1 | package testjson 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "io" 7 | "os" 8 | "sort" 9 | "strings" 10 | "time" 11 | 12 | "golang.org/x/term" 13 | "gotest.tools/gotestsum/internal/dotwriter" 14 | "gotest.tools/gotestsum/internal/log" 15 | ) 16 | 17 | func dotsFormatV1(out io.Writer) EventFormatter { 18 | buf := bufio.NewWriter(out) 19 | return eventFormatterFunc(func(event TestEvent, exec *Execution) error { 20 | pkg := exec.Package(event.Package) 21 | switch { 22 | case event.PackageEvent(): 23 | return nil 24 | case event.Action == ActionRun && pkg.Total == 1: 25 | buf.WriteString("[" + RelativePackagePath(event.Package) + "]") 26 | return buf.Flush() 27 | } 28 | buf.WriteString(fmtDot(event)) 29 | return buf.Flush() 30 | }) 31 | } 32 | 33 | func fmtDot(event TestEvent) string { 34 | withColor := colorEvent(event) 35 | switch event.Action { 36 | case ActionPass: 37 | return withColor("·") 38 | case ActionFail: 39 | return withColor("✖") 40 | case ActionSkip: 41 | return withColor("↷") 42 | } 43 | return "" 44 | } 45 | 46 | type dotFormatter struct { 47 | pkgs map[string]*dotLine 48 | order []string 49 | writer *dotwriter.Writer 50 | opts FormatOptions 51 | termWidth int 52 | } 53 | 54 | type dotLine struct { 55 | runes int 56 | builder *strings.Builder 57 | lastUpdate time.Time 58 | } 59 | 60 | func (l *dotLine) update(dot string) { 61 | if dot == "" { 62 | return 63 | } 64 | l.builder.WriteString(dot) 65 | l.runes++ 66 | } 67 | 68 | // checkWidth marks the line as full when the width of the line hits the 69 | // terminal width. 70 | func (l *dotLine) checkWidth(prefix, terminal int) { 71 | if prefix+l.runes >= terminal { 72 | l.builder.WriteString("\n" + strings.Repeat(" ", prefix)) 73 | l.runes = 0 74 | } 75 | } 76 | 77 | func newDotFormatter(out io.Writer, opts FormatOptions) EventFormatter { 78 | w, _, err := term.GetSize(int(os.Stdout.Fd())) 79 | if err != nil || w == 0 { 80 | log.Warnf("Failed to detect terminal width for dots format, error: %v", err) 81 | return dotsFormatV1(out) 82 | } 83 | return &dotFormatter{ 84 | pkgs: make(map[string]*dotLine), 85 | writer: dotwriter.New(out), 86 | termWidth: w, 87 | opts: opts, 88 | } 89 | } 90 | 91 | func (d *dotFormatter) Format(event TestEvent, exec *Execution) error { 92 | if d.pkgs[event.Package] == nil { 93 | d.pkgs[event.Package] = &dotLine{builder: new(strings.Builder)} 94 | d.order = append(d.order, event.Package) 95 | } 96 | line := d.pkgs[event.Package] 97 | line.lastUpdate = event.Time 98 | 99 | if !event.PackageEvent() { 100 | line.update(fmtDot(event)) 101 | } 102 | switch event.Action { 103 | case ActionOutput, ActionBench: 104 | return nil 105 | } 106 | 107 | // Add an empty header to work around incorrect line counting 108 | fmt.Fprint(d.writer, "\n\n") 109 | 110 | sort.Slice(d.order, d.orderByLastUpdated) 111 | for _, pkg := range d.order { 112 | if d.opts.HideEmptyPackages && exec.Package(pkg).IsEmpty() { 113 | continue 114 | } 115 | 116 | line := d.pkgs[pkg] 117 | pkgname := RelativePackagePath(pkg) + " " 118 | prefix := fmtDotElapsed(exec.Package(pkg)) 119 | line.checkWidth(len(prefix+pkgname), d.termWidth) 120 | fmt.Fprint(d.writer, prefix+pkgname+line.builder.String()+"\n") 121 | } 122 | PrintSummary(d.writer, exec, SummarizeNone) 123 | return d.writer.Flush() 124 | } 125 | 126 | // orderByLastUpdated so that the most recently updated packages move to the 127 | // bottom of the list, leaving completed package in the same order at the top. 128 | func (d *dotFormatter) orderByLastUpdated(i, j int) bool { 129 | return d.pkgs[d.order[i]].lastUpdate.Before(d.pkgs[d.order[j]].lastUpdate) 130 | } 131 | 132 | func fmtDotElapsed(p *Package) string { 133 | f := func(v string) string { 134 | return fmt.Sprintf(" %5s ", v) 135 | } 136 | 137 | elapsed := p.Elapsed() 138 | switch { 139 | case p.cached: 140 | return f("🖴 ") 141 | case elapsed <= 0: 142 | return f("") 143 | case elapsed >= time.Hour: 144 | return f("⏳ ") 145 | case elapsed < time.Second: 146 | return f(elapsed.String()) 147 | } 148 | 149 | const maxWidth = 7 150 | var steps = []time.Duration{ 151 | time.Millisecond, 152 | 10 * time.Millisecond, 153 | 100 * time.Millisecond, 154 | time.Second, 155 | 10 * time.Second, 156 | time.Minute, 157 | 10 * time.Minute, 158 | } 159 | 160 | for _, trunc := range steps { 161 | r := f(elapsed.Truncate(trunc).String()) 162 | if len(r) <= maxWidth { 163 | return r 164 | } 165 | } 166 | return f("") 167 | } 168 | -------------------------------------------------------------------------------- /testjson/dotformat_test.go: -------------------------------------------------------------------------------- 1 | package testjson 2 | 3 | import ( 4 | "bytes" 5 | "math/rand" 6 | "os" 7 | "runtime" 8 | "testing" 9 | "testing/quick" 10 | "time" 11 | "unicode/utf8" 12 | 13 | "gotest.tools/gotestsum/internal/dotwriter" 14 | "gotest.tools/gotestsum/internal/text" 15 | "gotest.tools/v3/assert" 16 | "gotest.tools/v3/assert/cmp" 17 | "gotest.tools/v3/golden" 18 | "gotest.tools/v3/skip" 19 | ) 20 | 21 | func TestScanTestOutput_WithDotsFormatter(t *testing.T) { 22 | skip.If(t, runtime.GOOS == "windows") 23 | skip.If(t, os.Getenv("CI") == "true", "flaky on Github Actions") 24 | 25 | out := new(bytes.Buffer) 26 | dotfmt := &dotFormatter{ 27 | pkgs: make(map[string]*dotLine), 28 | writer: dotwriter.New(out), 29 | termWidth: 80, 30 | } 31 | shim := newFakeHandler(dotfmt, "input/go-test-json") 32 | _, err := ScanTestOutput(shim.Config(t)) 33 | assert.NilError(t, err) 34 | 35 | actual := text.ProcessLines(t, out, text.OpRemoveSummaryLineElapsedTime) 36 | golden.Assert(t, actual, "format/dots-v2.out") 37 | golden.Assert(t, shim.err.String(), "input/go-test-json.err") 38 | } 39 | 40 | func TestFmtDotElapsed(t *testing.T) { 41 | var testcases = []struct { 42 | cached bool 43 | elapsed time.Duration 44 | expected string 45 | }{ 46 | { 47 | elapsed: 999 * time.Microsecond, 48 | expected: " 999µs ", 49 | }, 50 | { 51 | elapsed: 7 * time.Millisecond, 52 | expected: " 7ms ", 53 | }, 54 | { 55 | cached: true, 56 | elapsed: time.Millisecond, 57 | expected: " 🖴 ", 58 | }, 59 | { 60 | elapsed: 3 * time.Hour, 61 | expected: " ⏳ ", 62 | }, 63 | { 64 | elapsed: 14 * time.Millisecond, 65 | expected: " 14ms ", 66 | }, 67 | { 68 | elapsed: 333 * time.Millisecond, 69 | expected: " 333ms ", 70 | }, 71 | { 72 | elapsed: 1337 * time.Millisecond, 73 | expected: " 1.33s ", 74 | }, 75 | { 76 | elapsed: 14821 * time.Millisecond, 77 | expected: " 14.8s ", 78 | }, 79 | { 80 | elapsed: time.Minute + 59*time.Second, 81 | expected: " 1m59s ", 82 | }, 83 | { 84 | elapsed: 59*time.Minute + 59*time.Second, 85 | expected: " 59m0s ", 86 | }, 87 | { 88 | elapsed: 148213 * time.Millisecond, 89 | expected: " 2m28s ", 90 | }, 91 | { 92 | elapsed: 1482137 * time.Millisecond, 93 | expected: " 24m0s ", 94 | }, 95 | } 96 | 97 | for _, tc := range testcases { 98 | t.Run(tc.expected, func(t *testing.T) { 99 | pkg := &Package{ 100 | cached: tc.cached, 101 | elapsed: tc.elapsed, 102 | } 103 | actual := fmtDotElapsed(pkg) 104 | assert.Check(t, cmp.Equal(utf8.RuneCountInString(actual), 7)) 105 | assert.Equal(t, actual, tc.expected) 106 | }) 107 | } 108 | } 109 | 110 | func TestFmtDotElapsed_RuneCountProperty(t *testing.T) { 111 | f := func(d time.Duration) bool { 112 | pkg := &Package{ 113 | Passed: []TestCase{{Elapsed: d}}, 114 | } 115 | actual := fmtDotElapsed(pkg) 116 | width := utf8.RuneCountInString(actual) 117 | if width == 7 { 118 | return true 119 | } 120 | t.Logf("actual %v (width %d)", actual, width) 121 | return false 122 | } 123 | 124 | seed := time.Now().Unix() 125 | t.Log("seed", seed) 126 | assert.Assert(t, quick.Check(f, &quick.Config{ 127 | MaxCountScale: 2000, 128 | Rand: rand.New(rand.NewSource(seed)), 129 | })) 130 | } 131 | 132 | func TestNewDotFormatter(t *testing.T) { 133 | buf := new(bytes.Buffer) 134 | ef := newDotFormatter(buf, FormatOptions{}) 135 | 136 | d, ok := ef.(*dotFormatter) 137 | skip.If(t, !ok, "no terminal width") 138 | assert.Assert(t, d.termWidth != 0) 139 | } 140 | -------------------------------------------------------------------------------- /testjson/internal/badmain/main_test.go: -------------------------------------------------------------------------------- 1 | // +build stubpkg 2 | 3 | /*Package badmain fails in TestMain 4 | */ 5 | package badmain 6 | 7 | import ( 8 | "fmt" 9 | "os" 10 | "testing" 11 | ) 12 | 13 | func TestMain(m *testing.M) { 14 | fmt.Fprintln(os.Stderr, "sometimes main can exit 2") 15 | os.Exit(2) 16 | } 17 | -------------------------------------------------------------------------------- /testjson/internal/broken/broken.go: -------------------------------------------------------------------------------- 1 | // +build stubpkg 2 | 3 | package broken 4 | 5 | var missingImport = somepackage.Foo() // nolint 6 | -------------------------------------------------------------------------------- /testjson/internal/empty/empty.go: -------------------------------------------------------------------------------- 1 | //go:build stubpkg 2 | // +build stubpkg 3 | 4 | package empty 5 | -------------------------------------------------------------------------------- /testjson/internal/empty/empty_test.go: -------------------------------------------------------------------------------- 1 | //go:build stubpkg 2 | // +build stubpkg 3 | 4 | package empty 5 | -------------------------------------------------------------------------------- /testjson/internal/frenzy/frenzy_test.go: -------------------------------------------------------------------------------- 1 | // +build stubpkg,panic 2 | 3 | package frenzy 4 | 5 | import ( 6 | "fmt" 7 | "testing" 8 | ) 9 | 10 | func TestPassed(t *testing.T) {} 11 | 12 | func TestPassedWithLog(t *testing.T) { 13 | t.Log("this is a log") 14 | } 15 | 16 | func TestPassedWithStdout(t *testing.T) { 17 | fmt.Println("this is a Print") 18 | } 19 | 20 | func TestPanics(t *testing.T) { 21 | panic("this is a panic") 22 | } 23 | -------------------------------------------------------------------------------- /testjson/internal/good/good.go: -------------------------------------------------------------------------------- 1 | //go:build stubpkg 2 | // +build stubpkg 3 | 4 | package good 5 | 6 | func Something() int { 7 | for i := 0; i < 10; i++ { 8 | return i 9 | } 10 | return 0 11 | } 12 | -------------------------------------------------------------------------------- /testjson/internal/good/good_test.go: -------------------------------------------------------------------------------- 1 | //go:build stubpkg 2 | // +build stubpkg 3 | 4 | package good 5 | 6 | import ( 7 | "fmt" 8 | "os" 9 | "testing" 10 | "time" 11 | ) 12 | 13 | func TestPassed(t *testing.T) {} 14 | 15 | func TestPassedWithLog(t *testing.T) { 16 | Something() 17 | t.Log("this is a log") 18 | } 19 | 20 | func TestPassedWithStdout(t *testing.T) { 21 | fmt.Println("this is a Print") 22 | } 23 | 24 | func TestSkipped(t *testing.T) { 25 | t.Skip() 26 | } 27 | 28 | func TestSkippedWitLog(t *testing.T) { 29 | t.Skip("the skip message") 30 | } 31 | 32 | func TestWithStderr(t *testing.T) { 33 | fmt.Fprintln(os.Stderr, "this is stderr") 34 | } 35 | 36 | func TestParallelTheFirst(t *testing.T) { 37 | t.Parallel() 38 | time.Sleep(10 * time.Millisecond) 39 | } 40 | 41 | func TestParallelTheSecond(t *testing.T) { 42 | t.Parallel() 43 | time.Sleep(6 * time.Millisecond) 44 | } 45 | 46 | func TestParallelTheThird(t *testing.T) { 47 | t.Parallel() 48 | time.Sleep(2 * time.Millisecond) 49 | } 50 | 51 | func TestNestedSuccess(t *testing.T) { 52 | for _, name := range []string{"a", "b", "c", "d"} { 53 | t.Run(name, func(t *testing.T) { 54 | t.Run("sub", func(t *testing.T) {}) 55 | }) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /testjson/internal/parallelfails/fails_test.go: -------------------------------------------------------------------------------- 1 | // +build stubpkg 2 | 3 | package fails 4 | 5 | import ( 6 | "fmt" 7 | "os" 8 | "testing" 9 | "time" 10 | ) 11 | 12 | func TestPassed(t *testing.T) {} 13 | 14 | func TestPassedWithLog(t *testing.T) { 15 | t.Log("this is a log") 16 | } 17 | 18 | func TestPassedWithStdout(t *testing.T) { 19 | fmt.Println("this is a Print") 20 | } 21 | 22 | func TestWithStderr(t *testing.T) { 23 | fmt.Fprintln(os.Stderr, "this is stderr") 24 | } 25 | 26 | func TestParallelTheFirst(t *testing.T) { 27 | t.Parallel() 28 | time.Sleep(10 * time.Millisecond) 29 | t.Fatal("failed the first") 30 | } 31 | 32 | func TestParallelTheSecond(t *testing.T) { 33 | t.Parallel() 34 | time.Sleep(6 * time.Millisecond) 35 | t.Fatal("failed the second") 36 | } 37 | 38 | func TestParallelTheThird(t *testing.T) { 39 | t.Parallel() 40 | time.Sleep(2 * time.Millisecond) 41 | t.Fatal("failed the third") 42 | 43 | } 44 | 45 | func TestNestedParallelFailures(t *testing.T) { 46 | for _, name := range []string{"a", "b", "c", "d"} { 47 | name := name 48 | t.Run(name, func(t *testing.T) { 49 | t.Parallel() 50 | t.Fatal("failed sub " + name) 51 | }) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /testjson/internal/withfails/fails_test.go: -------------------------------------------------------------------------------- 1 | // +build stubpkg 2 | 3 | /*Package withfails is used to generate testdata for the testjson package. 4 | */ 5 | package withfails 6 | 7 | import ( 8 | "fmt" 9 | "os" 10 | "strings" 11 | "testing" 12 | "time" 13 | ) 14 | 15 | func TestPassed(t *testing.T) {} 16 | 17 | func TestPassedWithLog(t *testing.T) { 18 | t.Log("this is a log") 19 | } 20 | 21 | func TestPassedWithStdout(t *testing.T) { 22 | fmt.Println("this is a Print") 23 | } 24 | 25 | func TestSkipped(t *testing.T) { 26 | t.Skip() 27 | } 28 | 29 | func TestSkippedWitLog(t *testing.T) { 30 | t.Skip("the skip message") 31 | } 32 | 33 | func TestFailed(t *testing.T) { 34 | t.Fatal("this failed") 35 | } 36 | 37 | func TestWithStderr(t *testing.T) { 38 | fmt.Fprintln(os.Stderr, "this is stderr") 39 | } 40 | 41 | func TestFailedWithStderr(t *testing.T) { 42 | fmt.Fprintln(os.Stderr, "this is stderr") 43 | t.Fatal("also failed") 44 | } 45 | 46 | func TestParallelTheFirst(t *testing.T) { 47 | t.Parallel() 48 | time.Sleep(10 * time.Millisecond) 49 | } 50 | 51 | func TestParallelTheSecond(t *testing.T) { 52 | t.Parallel() 53 | time.Sleep(6 * time.Millisecond) 54 | } 55 | 56 | func TestParallelTheThird(t *testing.T) { 57 | t.Parallel() 58 | time.Sleep(2 * time.Millisecond) 59 | } 60 | 61 | func TestNestedWithFailure(t *testing.T) { 62 | for _, name := range []string{"a", "b", "c", "d"} { 63 | t.Run(name, func(t *testing.T) { 64 | if strings.HasSuffix(t.Name(), "c") { 65 | t.Fatal("failed") 66 | } 67 | t.Run("sub", func(t *testing.T) {}) 68 | }) 69 | } 70 | } 71 | 72 | func TestNestedSuccess(t *testing.T) { 73 | for _, name := range []string{"a", "b", "c", "d"} { 74 | t.Run(name, func(t *testing.T) { 75 | t.Run("sub", func(t *testing.T) {}) 76 | }) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /testjson/internal/withfails/timeout_test.go: -------------------------------------------------------------------------------- 1 | // +build stubpkg 2 | 3 | package withfails 4 | 5 | import ( 6 | "os" 7 | "testing" 8 | "time" 9 | ) 10 | 11 | func TestTimeout(t *testing.T) { 12 | if os.Getenv("TEST_ALL") != "true" { 13 | t.Skip("skipping slow test") 14 | } 15 | time.Sleep(time.Minute) 16 | } 17 | -------------------------------------------------------------------------------- /testjson/pkgpathprefix.go: -------------------------------------------------------------------------------- 1 | package testjson 2 | 3 | import ( 4 | "bytes" 5 | "go/build" 6 | "os" 7 | "path/filepath" 8 | "runtime" 9 | "strconv" 10 | "strings" 11 | ) 12 | 13 | // RelativePackagePath attempts to remove a common prefix from a package path. 14 | // The common prefix is determined either by looking at the GOPATH or reading 15 | // the package value from go.mod file. 16 | // If the pkgpath does not match the common prefix it will be returned 17 | // unmodified. 18 | // If the pkgpath matches the common prefix exactly then '.' will be returned. 19 | func RelativePackagePath(pkgpath string) string { 20 | if pkgpath == pkgPathPrefix { 21 | return "." 22 | } 23 | return strings.TrimPrefix(pkgpath, pkgPathPrefix+"/") 24 | } 25 | 26 | func getPkgPathPrefix() string { 27 | cwd, _ := os.Getwd() 28 | if isGoModuleEnabled() { 29 | prefix := getPkgPathPrefixFromGoModule(cwd) 30 | if prefix != "" { 31 | return prefix 32 | } 33 | } 34 | return getPkgPathPrefixGoPath(cwd) 35 | } 36 | 37 | func isGoModuleEnabled() bool { 38 | version := runtime.Version() 39 | if strings.HasPrefix(version, "go1.10") { 40 | return false 41 | } 42 | // Go modules may not be enabled if env var is unset, or set to auto, however 43 | // we can always fall back to using GOPATH as the prefix if a go.mod is not 44 | // found. 45 | return os.Getenv("GO111MODULE") != "off" 46 | } 47 | 48 | // TODO: might not work on windows 49 | func getPkgPathPrefixGoPath(cwd string) string { 50 | gopaths := strings.Split(build.Default.GOPATH, string(filepath.ListSeparator)) 51 | for _, gopath := range gopaths { 52 | gosrcpath := gopath + "/src/" 53 | if strings.HasPrefix(cwd, gosrcpath) { 54 | return strings.TrimPrefix(cwd, gosrcpath) 55 | } 56 | } 57 | return "" 58 | } 59 | 60 | func getPkgPathPrefixFromGoModule(cwd string) string { 61 | filename := goModuleFilePath(cwd) 62 | if filename == "" { 63 | return "" 64 | } 65 | raw, err := os.ReadFile(filename) 66 | if err != nil { 67 | // TODO: log.Warn 68 | return "" 69 | } 70 | return pkgPathFromGoModuleFile(raw) 71 | } 72 | 73 | var ( 74 | slashSlash = []byte("//") 75 | moduleStr = []byte("module") 76 | ) 77 | 78 | // Copy of ModulePath from golang.org/src/cmd/go/internal/modfile/read.go 79 | func pkgPathFromGoModuleFile(mod []byte) string { 80 | for len(mod) > 0 { 81 | line := mod 82 | mod = nil 83 | if i := bytes.IndexByte(line, '\n'); i >= 0 { 84 | line, mod = line[:i], line[i+1:] 85 | } 86 | if i := bytes.Index(line, slashSlash); i >= 0 { 87 | line = line[:i] 88 | } 89 | line = bytes.TrimSpace(line) 90 | if !bytes.HasPrefix(line, moduleStr) { 91 | continue 92 | } 93 | line = line[len(moduleStr):] 94 | n := len(line) 95 | line = bytes.TrimSpace(line) 96 | if len(line) == n || len(line) == 0 { 97 | continue 98 | } 99 | 100 | if line[0] == '"' || line[0] == '`' { 101 | p, err := strconv.Unquote(string(line)) 102 | if err != nil { 103 | return "" // malformed quoted string or multi-line module path 104 | } 105 | return p 106 | } 107 | 108 | return string(line) 109 | } 110 | return "" // missing module path 111 | } 112 | 113 | // A rough re-implementation of FindModuleRoot from 114 | // golang.org/src/cmd/go/internal/modload/init.go 115 | func goModuleFilePath(cwd string) string { 116 | dir := filepath.Clean(cwd) 117 | 118 | for { 119 | path := filepath.Join(dir, "go.mod") 120 | if _, err := os.Stat(path); err == nil { 121 | return path 122 | } 123 | parent := filepath.Dir(dir) 124 | if parent == dir { 125 | return "" 126 | } 127 | dir = parent 128 | } 129 | } 130 | 131 | var pkgPathPrefix = getPkgPathPrefix() 132 | -------------------------------------------------------------------------------- /testjson/pkgpathprefix_test.go: -------------------------------------------------------------------------------- 1 | package testjson 2 | 3 | import ( 4 | "testing" 5 | 6 | "gotest.tools/v3/assert" 7 | "gotest.tools/v3/skip" 8 | ) 9 | 10 | func TestRelativePackagePath(t *testing.T) { 11 | prefix := "gotest.tools/gotestsum/testjson" 12 | patchPkgPathPrefix(t, prefix) 13 | relPath := RelativePackagePath(prefix + "/extra/relpath") 14 | assert.Equal(t, relPath, "extra/relpath") 15 | 16 | relPath = RelativePackagePath(prefix) 17 | assert.Equal(t, relPath, ".") 18 | } 19 | 20 | func TestGetPkgPathPrefix(t *testing.T) { 21 | t.Run("with go path", func(t *testing.T) { 22 | skip.If(t, isGoModuleEnabled()) 23 | assert.Equal(t, getPkgPathPrefix(), "gotest.tools/gotestsum/testjson") 24 | }) 25 | t.Run("with go modules", func(t *testing.T) { 26 | skip.If(t, !isGoModuleEnabled()) 27 | assert.Equal(t, getPkgPathPrefix(), "gotest.tools/gotestsum") 28 | }) 29 | } 30 | -------------------------------------------------------------------------------- /testjson/summary.go: -------------------------------------------------------------------------------- 1 | package testjson 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "strings" 7 | "time" 8 | "unicode" 9 | "unicode/utf8" 10 | 11 | "github.com/fatih/color" 12 | ) 13 | 14 | // Summary enumerates the sections which can be printed by PrintSummary 15 | type Summary int 16 | 17 | const ( 18 | SummarizeNone Summary = 0 19 | SummarizeSkipped Summary = (1 << iota) / 2 20 | SummarizeFailed 21 | SummarizeErrors 22 | SummarizeOutput 23 | SummarizeAll = SummarizeSkipped | SummarizeFailed | SummarizeErrors | SummarizeOutput 24 | ) 25 | 26 | var summaryValues = map[Summary]string{ 27 | SummarizeSkipped: "skipped", 28 | SummarizeFailed: "failed", 29 | SummarizeErrors: "errors", 30 | SummarizeOutput: "output", 31 | } 32 | 33 | var summaryFromValue = map[string]Summary{ 34 | "none": SummarizeNone, 35 | "skipped": SummarizeSkipped, 36 | "failed": SummarizeFailed, 37 | "errors": SummarizeErrors, 38 | "output": SummarizeOutput, 39 | "all": SummarizeAll, 40 | } 41 | 42 | func (s Summary) String() string { 43 | if s == SummarizeNone { 44 | return "none" 45 | } 46 | var result []string 47 | for v := Summary(1); v <= s; v <<= 1 { 48 | if s.Includes(v) { 49 | result = append(result, summaryValues[v]) 50 | } 51 | } 52 | return strings.Join(result, ",") 53 | } 54 | 55 | // Includes returns true if Summary includes all the values set by other. 56 | func (s Summary) Includes(other Summary) bool { 57 | return s&other == other 58 | } 59 | 60 | // NewSummary returns a new Summary from a string value. If the string does not 61 | // match any known values returns false for the second value. 62 | func NewSummary(value string) (Summary, bool) { 63 | s, ok := summaryFromValue[value] 64 | return s, ok 65 | } 66 | 67 | // PrintSummary of a test Execution. Prints a section for each summary type 68 | // followed by a DONE line to out. 69 | func PrintSummary(out io.Writer, execution *Execution, opts Summary) { 70 | execSummary := newExecSummary(execution, opts) 71 | if opts.Includes(SummarizeSkipped) { 72 | writeTestCaseSummary(out, execSummary, formatSkipped()) 73 | } 74 | if opts.Includes(SummarizeFailed) { 75 | writeTestCaseSummary(out, execSummary, formatFailed()) 76 | } 77 | 78 | errors := execution.Errors() 79 | if opts.Includes(SummarizeErrors) { 80 | writeErrorSummary(out, errors) 81 | } 82 | 83 | fmt.Fprintf(out, "\n%s %d tests%s%s%s in %s\n", 84 | formatExecStatus(execution), 85 | execution.Total(), 86 | formatTestCount(len(execution.Skipped()), "skipped", ""), 87 | formatTestCount(len(execution.Failed()), "failure", "s"), 88 | formatTestCount(countErrors(errors), "error", "s"), 89 | FormatDurationAsSeconds(execution.Elapsed(), 3)) 90 | } 91 | 92 | func formatTestCount(count int, category string, pluralize string) string { 93 | switch count { 94 | case 0: 95 | return "" 96 | case 1: 97 | default: 98 | category += pluralize 99 | } 100 | return fmt.Sprintf(", %d %s", count, category) 101 | } 102 | 103 | func formatExecStatus(exec *Execution) string { 104 | if !exec.done { 105 | return "" 106 | } 107 | var runs string 108 | if exec.lastRunID > 0 { 109 | runs = fmt.Sprintf(" %d runs,", exec.lastRunID+1) 110 | } 111 | return "DONE" + runs 112 | } 113 | 114 | // FormatDurationAsSeconds formats a time.Duration as a float with an s suffix. 115 | func FormatDurationAsSeconds(d time.Duration, precision int) string { 116 | if d == neverFinished { 117 | return "unknown" 118 | } 119 | return fmt.Sprintf("%.[2]*[1]fs", d.Seconds(), precision) 120 | } 121 | 122 | func writeErrorSummary(out io.Writer, errors []string) { 123 | if len(errors) > 0 { 124 | fmt.Fprintln(out, color.MagentaString("\n=== Errors")) 125 | } 126 | for _, err := range errors { 127 | fmt.Fprintln(out, err) 128 | } 129 | } 130 | 131 | // countErrors in stderr lines. Build errors may include multiple lines where 132 | // subsequent lines are indented. 133 | // FIXME: Panics will include multiple lines, and are still overcounted. 134 | func countErrors(errors []string) int { 135 | var count int 136 | for _, line := range errors { 137 | r, _ := utf8.DecodeRuneInString(line) 138 | if !unicode.IsSpace(r) { 139 | count++ 140 | } 141 | } 142 | return count 143 | } 144 | 145 | type executionSummary interface { 146 | Failed() []TestCase 147 | Skipped() []TestCase 148 | OutputLines(TestCase) []string 149 | } 150 | 151 | type noOutputSummary struct { 152 | *Execution 153 | } 154 | 155 | func (s *noOutputSummary) OutputLines(_ TestCase) []string { 156 | return nil 157 | } 158 | 159 | func newExecSummary(execution *Execution, opts Summary) executionSummary { 160 | if opts.Includes(SummarizeOutput) { 161 | return execution 162 | } 163 | return &noOutputSummary{Execution: execution} 164 | } 165 | 166 | func writeTestCaseSummary(out io.Writer, execution executionSummary, conf testCaseFormatConfig) { 167 | testCases := conf.getter(execution) 168 | if len(testCases) == 0 { 169 | return 170 | } 171 | fmt.Fprintln(out, "\n=== "+conf.header) 172 | for idx, tc := range testCases { 173 | fmt.Fprintf(out, "=== %s: %s %s%s (%s)\n", 174 | conf.prefix, 175 | RelativePackagePath(tc.Package), 176 | tc.Test, 177 | formatRunID(tc.RunID), 178 | FormatDurationAsSeconds(tc.Elapsed, 2)) 179 | for _, line := range execution.OutputLines(tc) { 180 | if isFramingLine(line, tc.Test.Name()) { 181 | continue 182 | } 183 | fmt.Fprint(out, line) 184 | } 185 | if _, isNoOutput := execution.(*noOutputSummary); !isNoOutput && idx+1 != len(testCases) { 186 | fmt.Fprintln(out) 187 | } 188 | } 189 | } 190 | 191 | type testCaseFormatConfig struct { 192 | header string 193 | prefix string 194 | getter func(executionSummary) []TestCase 195 | } 196 | 197 | func formatFailed() testCaseFormatConfig { 198 | withColor := color.RedString 199 | return testCaseFormatConfig{ 200 | header: withColor("Failed"), 201 | prefix: withColor("FAIL"), 202 | getter: func(execution executionSummary) []TestCase { 203 | return execution.Failed() 204 | }, 205 | } 206 | } 207 | 208 | func formatSkipped() testCaseFormatConfig { 209 | withColor := color.YellowString 210 | return testCaseFormatConfig{ 211 | header: withColor("Skipped"), 212 | prefix: withColor("SKIP"), 213 | getter: func(execution executionSummary) []TestCase { 214 | return execution.Skipped() 215 | }, 216 | } 217 | } 218 | 219 | func isFramingLine(line string, testName string) bool { 220 | return strings.HasPrefix(line, "=== RUN Test") || 221 | strings.HasPrefix(line, "=== PAUSE Test") || 222 | strings.HasPrefix(line, "=== CONT Test") || 223 | strings.HasPrefix(line, "--- FAIL: "+testName+" ") || 224 | strings.HasPrefix(line, "--- SKIP: "+testName+" ") || 225 | strings.HasPrefix(line, "--- PASS: "+testName+" ") 226 | } 227 | -------------------------------------------------------------------------------- /testjson/testdata/format/dots-v1.out: -------------------------------------------------------------------------------- 1 | [testjson/internal/good]···↷↷·············[testjson/internal/parallelfails]····✖✖✖✖✖✖✖✖[testjson/internal/withfails]···↷↷✖·✖····✖··✖·········↷··· -------------------------------------------------------------------------------- /testjson/testdata/format/pkgname-codicons.out: -------------------------------------------------------------------------------- 1 |  testjson/internal/badmain (1ms) 2 |  testjson/internal/empty (cached) 3 |  testjson/internal/good (cached) 4 |  testjson/internal/parallelfails (20ms) 5 |  testjson/internal/withfails (20ms) 6 | -------------------------------------------------------------------------------- /testjson/testdata/format/pkgname-coverage-go1.20.out: -------------------------------------------------------------------------------- 1 | ✖ gotestsum/testjson/internal/badmain (1ms) 2 | ∅ gotestsum/testjson/internal/empty (cached) 3 | ✓ gotestsum/testjson/internal/good (20ms) (coverage: 66.7% of statements) 4 | ✖ gotestsum/testjson/internal/parallelfails (21ms) 5 | ✖ gotestsum/testjson/internal/withfails (20ms) 6 | -------------------------------------------------------------------------------- /testjson/testdata/format/pkgname-coverage.out: -------------------------------------------------------------------------------- 1 | ✖ gotestsum/testjson/internal/badmain (1ms) 2 | ✓ gotestsum/testjson/internal/good (12ms) (coverage: 0.0% of statements) 3 | ✖ gotestsum/testjson/internal/stub (11ms) (coverage: 0.0% of statements) 4 | -------------------------------------------------------------------------------- /testjson/testdata/format/pkgname-emoticons.out: -------------------------------------------------------------------------------- 1 | 󰇸 testjson/internal/badmain (1ms) 2 | 󰇶 testjson/internal/empty (cached) 3 | 󰇵 testjson/internal/good (cached) 4 | 󰇸 testjson/internal/parallelfails (20ms) 5 | 󰇸 testjson/internal/withfails (20ms) 6 | -------------------------------------------------------------------------------- /testjson/testdata/format/pkgname-hide-empty.out: -------------------------------------------------------------------------------- 1 | ✖ testjson/internal/badmain (1ms) 2 | ✓ testjson/internal/good (cached) 3 | ✖ testjson/internal/parallelfails (20ms) 4 | ✖ testjson/internal/withfails (20ms) 5 | -------------------------------------------------------------------------------- /testjson/testdata/format/pkgname-hivis.out: -------------------------------------------------------------------------------- 1 | ❌ testjson/internal/badmain (1ms) 2 | ➖ testjson/internal/empty (cached) 3 | ✅ testjson/internal/good (cached) 4 | ❌ testjson/internal/parallelfails (20ms) 5 | ❌ testjson/internal/withfails (20ms) 6 | -------------------------------------------------------------------------------- /testjson/testdata/format/pkgname-octicons.out: -------------------------------------------------------------------------------- 1 |  testjson/internal/badmain (1ms) 2 |  testjson/internal/empty (cached) 3 |  testjson/internal/good (cached) 4 |  testjson/internal/parallelfails (20ms) 5 |  testjson/internal/withfails (20ms) 6 | -------------------------------------------------------------------------------- /testjson/testdata/format/pkgname-shuffle.out: -------------------------------------------------------------------------------- 1 | ✖ testjson/internal/badmain (1ms) 2 | ✓ testjson/internal/good (20ms) 3 | ✖ testjson/internal/parallelfails (21ms) (-test.shuffle 123456) 4 | ✖ testjson/internal/withfails (20ms) (-test.shuffle 123456) 5 | -------------------------------------------------------------------------------- /testjson/testdata/format/pkgname-text.out: -------------------------------------------------------------------------------- 1 | FAIL testjson/internal/badmain (1ms) 2 | SKIP testjson/internal/empty (cached) 3 | PASS testjson/internal/good (cached) 4 | FAIL testjson/internal/parallelfails (20ms) 5 | FAIL testjson/internal/withfails (20ms) 6 | -------------------------------------------------------------------------------- /testjson/testdata/format/pkgname.out: -------------------------------------------------------------------------------- 1 | ✖ testjson/internal/badmain (1ms) 2 | ∅ testjson/internal/empty (cached) 3 | ✓ testjson/internal/good (cached) 4 | ✖ testjson/internal/parallelfails (20ms) 5 | ✖ testjson/internal/withfails (20ms) 6 | -------------------------------------------------------------------------------- /testjson/testdata/format/standard-quiet-coverage-go1.20.out: -------------------------------------------------------------------------------- 1 | sometimes main can exit 2 2 | program not built with -cover 3 | FAIL gotest.tools/gotestsum/testjson/internal/badmain 0.001s 4 | ok gotest.tools/gotestsum/testjson/internal/empty (cached) coverage: [no statements] [no tests to run] 5 | ok gotest.tools/gotestsum/testjson/internal/good 0.020s coverage: 66.7% of statements 6 | FAIL 7 | coverage: [no statements] 8 | FAIL gotest.tools/gotestsum/testjson/internal/parallelfails 0.021s 9 | FAIL 10 | coverage: [no statements] 11 | FAIL gotest.tools/gotestsum/testjson/internal/withfails 0.020s 12 | -------------------------------------------------------------------------------- /testjson/testdata/format/standard-quiet-coverage.out: -------------------------------------------------------------------------------- 1 | sometimes main can exit 2 2 | FAIL gotest.tools/gotestsum/testjson/internal/badmain 0.001s 3 | ok gotest.tools/gotestsum/testjson/internal/good 0.011s coverage: 0.0% of statements 4 | FAIL 5 | FAIL gotest.tools/gotestsum/testjson/internal/stub 0.011s 6 | -------------------------------------------------------------------------------- /testjson/testdata/format/standard-quiet-shuffle.out: -------------------------------------------------------------------------------- 1 | sometimes main can exit 2 2 | FAIL gotest.tools/gotestsum/testjson/internal/badmain 0.001s 3 | -test.shuffle 123456 4 | ok gotest.tools/gotestsum/testjson/internal/good 0.020s 5 | -test.shuffle 123456 6 | FAIL 7 | FAIL gotest.tools/gotestsum/testjson/internal/parallelfails 0.020s 8 | -test.shuffle 123456 9 | FAIL 10 | FAIL gotest.tools/gotestsum/testjson/internal/withfails 0.020s 11 | -------------------------------------------------------------------------------- /testjson/testdata/format/standard-quiet.out: -------------------------------------------------------------------------------- 1 | sometimes main can exit 2 2 | FAIL gotest.tools/gotestsum/testjson/internal/badmain 0.001s 3 | ok gotest.tools/gotestsum/testjson/internal/empty (cached) [no tests to run] 4 | ok gotest.tools/gotestsum/testjson/internal/good (cached) 5 | FAIL 6 | FAIL gotest.tools/gotestsum/testjson/internal/parallelfails 0.020s 7 | FAIL 8 | FAIL gotest.tools/gotestsum/testjson/internal/withfails 0.020s 9 | -------------------------------------------------------------------------------- /testjson/testdata/format/standard-verbose-coverage.out: -------------------------------------------------------------------------------- 1 | sometimes main can exit 2 2 | FAIL gotest.tools/gotestsum/testjson/internal/badmain 0.001s 3 | === RUN TestPassed 4 | --- PASS: TestPassed (0.00s) 5 | === RUN TestPassedWithLog 6 | --- PASS: TestPassedWithLog (0.00s) 7 | good_test.go:15: this is a log 8 | === RUN TestPassedWithStdout 9 | this is a Print 10 | --- PASS: TestPassedWithStdout (0.00s) 11 | === RUN TestSkipped 12 | --- SKIP: TestSkipped (0.00s) 13 | good_test.go:23: 14 | === RUN TestSkippedWitLog 15 | --- SKIP: TestSkippedWitLog (0.00s) 16 | good_test.go:27: the skip message 17 | === RUN TestWithStderr 18 | this is stderr 19 | --- PASS: TestWithStderr (0.00s) 20 | === RUN TestParallelTheFirst 21 | === PAUSE TestParallelTheFirst 22 | === RUN TestParallelTheSecond 23 | === PAUSE TestParallelTheSecond 24 | === RUN TestParallelTheThird 25 | === PAUSE TestParallelTheThird 26 | === RUN TestNestedSuccess 27 | === RUN TestNestedSuccess/a 28 | === RUN TestNestedSuccess/a/sub 29 | === RUN TestNestedSuccess/b 30 | === RUN TestNestedSuccess/b/sub 31 | === RUN TestNestedSuccess/c 32 | === RUN TestNestedSuccess/c/sub 33 | === RUN TestNestedSuccess/d 34 | === RUN TestNestedSuccess/d/sub 35 | --- PASS: TestNestedSuccess (0.00s) 36 | --- PASS: TestNestedSuccess/a (0.00s) 37 | --- PASS: TestNestedSuccess/a/sub (0.00s) 38 | --- PASS: TestNestedSuccess/b (0.00s) 39 | --- PASS: TestNestedSuccess/b/sub (0.00s) 40 | --- PASS: TestNestedSuccess/c (0.00s) 41 | --- PASS: TestNestedSuccess/c/sub (0.00s) 42 | --- PASS: TestNestedSuccess/d (0.00s) 43 | --- PASS: TestNestedSuccess/d/sub (0.00s) 44 | === CONT TestParallelTheFirst 45 | === CONT TestParallelTheThird 46 | === CONT TestParallelTheSecond 47 | --- PASS: TestParallelTheThird (0.00s) 48 | --- PASS: TestParallelTheSecond (0.01s) 49 | --- PASS: TestParallelTheFirst (0.01s) 50 | PASS 51 | coverage: 0.0% of statements 52 | ok gotest.tools/gotestsum/testjson/internal/good 0.011s coverage: 0.0% of statements 53 | === RUN TestPassed 54 | --- PASS: TestPassed (0.00s) 55 | === RUN TestPassedWithLog 56 | --- PASS: TestPassedWithLog (0.00s) 57 | stub_test.go:18: this is a log 58 | === RUN TestPassedWithStdout 59 | this is a Print 60 | --- PASS: TestPassedWithStdout (0.00s) 61 | === RUN TestSkipped 62 | --- SKIP: TestSkipped (0.00s) 63 | stub_test.go:26: 64 | === RUN TestSkippedWitLog 65 | --- SKIP: TestSkippedWitLog (0.00s) 66 | stub_test.go:30: the skip message 67 | === RUN TestFailed 68 | --- FAIL: TestFailed (0.00s) 69 | stub_test.go:34: this failed 70 | === RUN TestWithStderr 71 | this is stderr 72 | --- PASS: TestWithStderr (0.00s) 73 | === RUN TestFailedWithStderr 74 | this is stderr 75 | --- FAIL: TestFailedWithStderr (0.00s) 76 | stub_test.go:43: also failed 77 | === RUN TestParallelTheFirst 78 | === PAUSE TestParallelTheFirst 79 | === RUN TestParallelTheSecond 80 | === PAUSE TestParallelTheSecond 81 | === RUN TestParallelTheThird 82 | === PAUSE TestParallelTheThird 83 | === RUN TestNestedWithFailure 84 | === RUN TestNestedWithFailure/a 85 | === RUN TestNestedWithFailure/a/sub 86 | === RUN TestNestedWithFailure/b 87 | === RUN TestNestedWithFailure/b/sub 88 | === RUN TestNestedWithFailure/c 89 | === RUN TestNestedWithFailure/d 90 | === RUN TestNestedWithFailure/d/sub 91 | --- FAIL: TestNestedWithFailure (0.00s) 92 | --- PASS: TestNestedWithFailure/a (0.00s) 93 | --- PASS: TestNestedWithFailure/a/sub (0.00s) 94 | --- PASS: TestNestedWithFailure/b (0.00s) 95 | --- PASS: TestNestedWithFailure/b/sub (0.00s) 96 | --- FAIL: TestNestedWithFailure/c (0.00s) 97 | stub_test.go:65: failed 98 | --- PASS: TestNestedWithFailure/d (0.00s) 99 | --- PASS: TestNestedWithFailure/d/sub (0.00s) 100 | === RUN TestNestedSuccess 101 | === RUN TestNestedSuccess/a 102 | === RUN TestNestedSuccess/a/sub 103 | === RUN TestNestedSuccess/b 104 | === RUN TestNestedSuccess/b/sub 105 | === RUN TestNestedSuccess/c 106 | === RUN TestNestedSuccess/c/sub 107 | === RUN TestNestedSuccess/d 108 | === RUN TestNestedSuccess/d/sub 109 | --- PASS: TestNestedSuccess (0.00s) 110 | --- PASS: TestNestedSuccess/a (0.00s) 111 | --- PASS: TestNestedSuccess/a/sub (0.00s) 112 | --- PASS: TestNestedSuccess/b (0.00s) 113 | --- PASS: TestNestedSuccess/b/sub (0.00s) 114 | --- PASS: TestNestedSuccess/c (0.00s) 115 | --- PASS: TestNestedSuccess/c/sub (0.00s) 116 | --- PASS: TestNestedSuccess/d (0.00s) 117 | --- PASS: TestNestedSuccess/d/sub (0.00s) 118 | === CONT TestParallelTheFirst 119 | === CONT TestParallelTheThird 120 | === CONT TestParallelTheSecond 121 | --- PASS: TestParallelTheThird (0.00s) 122 | --- PASS: TestParallelTheSecond (0.01s) 123 | --- PASS: TestParallelTheFirst (0.01s) 124 | FAIL 125 | coverage: 0.0% of statements 126 | FAIL gotest.tools/gotestsum/testjson/internal/stub 0.011s 127 | -------------------------------------------------------------------------------- /testjson/testdata/format/testdox-coverage.out: -------------------------------------------------------------------------------- 1 | gotest.tools/gotestsum/testjson/internal/badmain: 2 | 3 | gotest.tools/gotestsum/testjson/internal/good: 4 | ✓ Nested success (0.00s) 5 | ✓ Nested success a (0.00s) 6 | ✓ Nested success a sub (0.00s) 7 | ✓ Nested success b (0.00s) 8 | ✓ Nested success b sub (0.00s) 9 | ✓ Nested success c (0.00s) 10 | ✓ Nested success c sub (0.00s) 11 | ✓ Nested success d (0.00s) 12 | ✓ Nested success d sub (0.00s) 13 | ✓ Parallel the first (0.01s) 14 | ✓ Parallel the second (0.01s) 15 | ✓ Parallel the third (0.00s) 16 | ✓ Passed (0.00s) 17 | ✓ Passed with log (0.00s) 18 | ✓ Passed with stdout (0.00s) 19 | ∅ Skipped (0.00s) 20 | ∅ Skipped wit log (0.00s) 21 | ✓ With stderr (0.00s) 22 | 23 | gotest.tools/gotestsum/testjson/internal/stub: 24 | ✖ Failed (0.00s) 25 | ✖ Failed with stderr (0.00s) 26 | ✓ Nested success (0.00s) 27 | ✓ Nested success a (0.00s) 28 | ✓ Nested success a sub (0.00s) 29 | ✓ Nested success b (0.00s) 30 | ✓ Nested success b sub (0.00s) 31 | ✓ Nested success c (0.00s) 32 | ✓ Nested success c sub (0.00s) 33 | ✓ Nested success d (0.00s) 34 | ✓ Nested success d sub (0.00s) 35 | ✖ Nested with failure (0.00s) 36 | ✓ Nested with failure a (0.00s) 37 | ✓ Nested with failure a sub (0.00s) 38 | ✓ Nested with failure b (0.00s) 39 | ✓ Nested with failure b sub (0.00s) 40 | ✖ Nested with failure c (0.00s) 41 | ✓ Nested with failure d (0.00s) 42 | ✓ Nested with failure d sub (0.00s) 43 | ✓ Parallel the first (0.01s) 44 | ✓ Parallel the second (0.01s) 45 | ✓ Parallel the third (0.00s) 46 | ✓ Passed (0.00s) 47 | ✓ Passed with log (0.00s) 48 | ✓ Passed with stdout (0.00s) 49 | ∅ Skipped (0.00s) 50 | ∅ Skipped wit log (0.00s) 51 | ✓ With stderr (0.00s) 52 | 53 | -------------------------------------------------------------------------------- /testjson/testdata/format/testdox-shuffle.out: -------------------------------------------------------------------------------- 1 | gotest.tools/gotestsum/testjson/internal/badmain: 2 | 3 | gotest.tools/gotestsum/testjson/internal/good: 4 | ✓ Nested success (0.00s) 5 | ✓ Nested success a (0.00s) 6 | ✓ Nested success a sub (0.00s) 7 | ✓ Nested success b (0.00s) 8 | ✓ Nested success b sub (0.00s) 9 | ✓ Nested success c (0.00s) 10 | ✓ Nested success c sub (0.00s) 11 | ✓ Nested success d (0.00s) 12 | ✓ Nested success d sub (0.00s) 13 | ✓ Parallel the first (0.01s) 14 | ✓ Parallel the second (0.01s) 15 | ✓ Parallel the third (0.00s) 16 | ✓ Passed (0.00s) 17 | ✓ Passed with log (0.00s) 18 | ✓ Passed with stdout (0.00s) 19 | ∅ Skipped (0.00s) 20 | ∅ Skipped wit log (0.00s) 21 | ✓ With stderr (0.00s) 22 | 23 | gotest.tools/gotestsum/testjson/internal/parallelfails: 24 | ✖ Nested parallel failures (0.00s) 25 | ✖ Nested parallel failures a (0.00s) 26 | ✖ Nested parallel failures b (0.00s) 27 | ✖ Nested parallel failures c (0.00s) 28 | ✖ Nested parallel failures d (0.00s) 29 | ✖ Parallel the first (0.01s) 30 | ✖ Parallel the second (0.01s) 31 | ✖ Parallel the third (0.00s) 32 | ✓ Passed (0.00s) 33 | ✓ Passed with log (0.00s) 34 | ✓ Passed with stdout (0.00s) 35 | ✓ With stderr (0.00s) 36 | 37 | gotest.tools/gotestsum/testjson/internal/withfails: 38 | ✖ Failed (0.00s) 39 | ✖ Failed with stderr (0.00s) 40 | ✓ Nested success (0.00s) 41 | ✓ Nested success a (0.00s) 42 | ✓ Nested success a sub (0.00s) 43 | ✓ Nested success b (0.00s) 44 | ✓ Nested success b sub (0.00s) 45 | ✓ Nested success c (0.00s) 46 | ✓ Nested success c sub (0.00s) 47 | ✓ Nested success d (0.00s) 48 | ✓ Nested success d sub (0.00s) 49 | ✖ Nested with failure (0.00s) 50 | ✓ Nested with failure a (0.00s) 51 | ✓ Nested with failure a sub (0.00s) 52 | ✓ Nested with failure b (0.00s) 53 | ✓ Nested with failure b sub (0.00s) 54 | ✖ Nested with failure c (0.00s) 55 | ✓ Nested with failure d (0.00s) 56 | ✓ Nested with failure d sub (0.00s) 57 | ✓ Parallel the first (0.01s) 58 | ✓ Parallel the second (0.01s) 59 | ✓ Parallel the third (0.00s) 60 | ✓ Passed (0.00s) 61 | ✓ Passed with log (0.00s) 62 | ✓ Passed with stdout (0.00s) 63 | ∅ Skipped (0.00s) 64 | ∅ Skipped wit log (0.00s) 65 | ∅ Timeout (0.00s) 66 | ✓ With stderr (0.00s) 67 | 68 | -------------------------------------------------------------------------------- /testjson/testdata/format/testdox.out: -------------------------------------------------------------------------------- 1 | gotest.tools/gotestsum/testjson/internal/badmain: 2 | 3 | gotest.tools/gotestsum/testjson/internal/empty: 4 | 5 | gotest.tools/gotestsum/testjson/internal/good: 6 | ✓ Nested success (0.00s) 7 | ✓ Nested success a (0.00s) 8 | ✓ Nested success a sub (0.00s) 9 | ✓ Nested success b (0.00s) 10 | ✓ Nested success b sub (0.00s) 11 | ✓ Nested success c (0.00s) 12 | ✓ Nested success c sub (0.00s) 13 | ✓ Nested success d (0.00s) 14 | ✓ Nested success d sub (0.00s) 15 | ✓ Parallel the first (0.01s) 16 | ✓ Parallel the second (0.01s) 17 | ✓ Parallel the third (0.00s) 18 | ✓ Passed (0.00s) 19 | ✓ Passed with log (0.00s) 20 | ✓ Passed with stdout (0.00s) 21 | ∅ Skipped (0.00s) 22 | ∅ Skipped wit log (0.00s) 23 | ✓ With stderr (0.00s) 24 | 25 | gotest.tools/gotestsum/testjson/internal/parallelfails: 26 | ✖ Nested parallel failures (0.00s) 27 | ✖ Nested parallel failures a (0.00s) 28 | ✖ Nested parallel failures b (0.00s) 29 | ✖ Nested parallel failures c (0.00s) 30 | ✖ Nested parallel failures d (0.00s) 31 | ✖ Parallel the first (0.01s) 32 | ✖ Parallel the second (0.01s) 33 | ✖ Parallel the third (0.00s) 34 | ✓ Passed (0.00s) 35 | ✓ Passed with log (0.00s) 36 | ✓ Passed with stdout (0.00s) 37 | ✓ With stderr (0.00s) 38 | 39 | gotest.tools/gotestsum/testjson/internal/withfails: 40 | ✖ Failed (0.00s) 41 | ✖ Failed with stderr (0.00s) 42 | ✓ Nested success (0.00s) 43 | ✓ Nested success a (0.00s) 44 | ✓ Nested success a sub (0.00s) 45 | ✓ Nested success b (0.00s) 46 | ✓ Nested success b sub (0.00s) 47 | ✓ Nested success c (0.00s) 48 | ✓ Nested success c sub (0.00s) 49 | ✓ Nested success d (0.00s) 50 | ✓ Nested success d sub (0.00s) 51 | ✖ Nested with failure (0.00s) 52 | ✓ Nested with failure a (0.00s) 53 | ✓ Nested with failure a sub (0.00s) 54 | ✓ Nested with failure b (0.00s) 55 | ✓ Nested with failure b sub (0.00s) 56 | ✖ Nested with failure c (0.00s) 57 | ✓ Nested with failure d (0.00s) 58 | ✓ Nested with failure d sub (0.00s) 59 | ✓ Parallel the first (0.01s) 60 | ✓ Parallel the second (0.01s) 61 | ✓ Parallel the third (0.00s) 62 | ✓ Passed (0.00s) 63 | ✓ Passed with log (0.00s) 64 | ✓ Passed with stdout (0.00s) 65 | ∅ Skipped (0.00s) 66 | ∅ Skipped wit log (0.00s) 67 | ∅ Timeout (0.00s) 68 | ✓ With stderr (0.00s) 69 | 70 | -------------------------------------------------------------------------------- /testjson/testdata/format/testname-coverage.out: -------------------------------------------------------------------------------- 1 | sometimes main can exit 2 2 | FAIL gotestsum/testjson/internal/badmain 3 | PASS gotestsum/testjson/internal/good.TestPassed (0.00s) 4 | PASS gotestsum/testjson/internal/good.TestPassedWithLog (0.00s) 5 | PASS gotestsum/testjson/internal/good.TestPassedWithStdout (0.00s) 6 | SKIP gotestsum/testjson/internal/good.TestSkipped (0.00s) 7 | SKIP gotestsum/testjson/internal/good.TestSkippedWitLog (0.00s) 8 | PASS gotestsum/testjson/internal/good.TestWithStderr (0.00s) 9 | PASS gotestsum/testjson/internal/good.TestNestedSuccess/a/sub (0.00s) 10 | PASS gotestsum/testjson/internal/good.TestNestedSuccess/a (0.00s) 11 | PASS gotestsum/testjson/internal/good.TestNestedSuccess/b/sub (0.00s) 12 | PASS gotestsum/testjson/internal/good.TestNestedSuccess/b (0.00s) 13 | PASS gotestsum/testjson/internal/good.TestNestedSuccess/c/sub (0.00s) 14 | PASS gotestsum/testjson/internal/good.TestNestedSuccess/c (0.00s) 15 | PASS gotestsum/testjson/internal/good.TestNestedSuccess/d/sub (0.00s) 16 | PASS gotestsum/testjson/internal/good.TestNestedSuccess/d (0.00s) 17 | PASS gotestsum/testjson/internal/good.TestNestedSuccess (0.00s) 18 | PASS gotestsum/testjson/internal/good.TestParallelTheThird (0.00s) 19 | PASS gotestsum/testjson/internal/good.TestParallelTheSecond (0.01s) 20 | PASS gotestsum/testjson/internal/good.TestParallelTheFirst (0.01s) 21 | coverage: 0.0% of statements 22 | PASS gotestsum/testjson/internal/good (coverage: 0.0% of statements) 23 | PASS gotestsum/testjson/internal/stub.TestPassed (0.00s) 24 | PASS gotestsum/testjson/internal/stub.TestPassedWithLog (0.00s) 25 | PASS gotestsum/testjson/internal/stub.TestPassedWithStdout (0.00s) 26 | SKIP gotestsum/testjson/internal/stub.TestSkipped (0.00s) 27 | SKIP gotestsum/testjson/internal/stub.TestSkippedWitLog (0.00s) 28 | === RUN TestFailed 29 | --- FAIL: TestFailed (0.00s) 30 | stub_test.go:34: this failed 31 | FAIL gotestsum/testjson/internal/stub.TestFailed (0.00s) 32 | PASS gotestsum/testjson/internal/stub.TestWithStderr (0.00s) 33 | === RUN TestFailedWithStderr 34 | this is stderr 35 | --- FAIL: TestFailedWithStderr (0.00s) 36 | stub_test.go:43: also failed 37 | FAIL gotestsum/testjson/internal/stub.TestFailedWithStderr (0.00s) 38 | PASS gotestsum/testjson/internal/stub.TestNestedWithFailure/a/sub (0.00s) 39 | PASS gotestsum/testjson/internal/stub.TestNestedWithFailure/a (0.00s) 40 | PASS gotestsum/testjson/internal/stub.TestNestedWithFailure/b/sub (0.00s) 41 | PASS gotestsum/testjson/internal/stub.TestNestedWithFailure/b (0.00s) 42 | === RUN TestNestedWithFailure/c 43 | --- FAIL: TestNestedWithFailure/c (0.00s) 44 | stub_test.go:65: failed 45 | FAIL gotestsum/testjson/internal/stub.TestNestedWithFailure/c (0.00s) 46 | PASS gotestsum/testjson/internal/stub.TestNestedWithFailure/d/sub (0.00s) 47 | PASS gotestsum/testjson/internal/stub.TestNestedWithFailure/d (0.00s) 48 | === RUN TestNestedWithFailure 49 | --- FAIL: TestNestedWithFailure (0.00s) 50 | FAIL gotestsum/testjson/internal/stub.TestNestedWithFailure (0.00s) 51 | PASS gotestsum/testjson/internal/stub.TestNestedSuccess/a/sub (0.00s) 52 | PASS gotestsum/testjson/internal/stub.TestNestedSuccess/a (0.00s) 53 | PASS gotestsum/testjson/internal/stub.TestNestedSuccess/b/sub (0.00s) 54 | PASS gotestsum/testjson/internal/stub.TestNestedSuccess/b (0.00s) 55 | PASS gotestsum/testjson/internal/stub.TestNestedSuccess/c/sub (0.00s) 56 | PASS gotestsum/testjson/internal/stub.TestNestedSuccess/c (0.00s) 57 | PASS gotestsum/testjson/internal/stub.TestNestedSuccess/d/sub (0.00s) 58 | PASS gotestsum/testjson/internal/stub.TestNestedSuccess/d (0.00s) 59 | PASS gotestsum/testjson/internal/stub.TestNestedSuccess (0.00s) 60 | PASS gotestsum/testjson/internal/stub.TestParallelTheThird (0.00s) 61 | PASS gotestsum/testjson/internal/stub.TestParallelTheSecond (0.01s) 62 | PASS gotestsum/testjson/internal/stub.TestParallelTheFirst (0.01s) 63 | coverage: 0.0% of statements 64 | FAIL gotestsum/testjson/internal/stub (coverage: 0.0% of statements) 65 | -------------------------------------------------------------------------------- /testjson/testdata/format/testname-shuffle.out: -------------------------------------------------------------------------------- 1 | sometimes main can exit 2 2 | FAIL testjson/internal/badmain 3 | PASS testjson/internal/good.TestPassedWithLog (0.00s) 4 | SKIP testjson/internal/good.TestSkippedWitLog (0.00s) 5 | PASS testjson/internal/good.TestPassedWithStdout (0.00s) 6 | PASS testjson/internal/good.TestPassed (0.00s) 7 | PASS testjson/internal/good.TestNestedSuccess/a/sub (0.00s) 8 | PASS testjson/internal/good.TestNestedSuccess/a (0.00s) 9 | PASS testjson/internal/good.TestNestedSuccess/b/sub (0.00s) 10 | PASS testjson/internal/good.TestNestedSuccess/b (0.00s) 11 | PASS testjson/internal/good.TestNestedSuccess/c/sub (0.00s) 12 | PASS testjson/internal/good.TestNestedSuccess/c (0.00s) 13 | PASS testjson/internal/good.TestNestedSuccess/d/sub (0.00s) 14 | PASS testjson/internal/good.TestNestedSuccess/d (0.00s) 15 | PASS testjson/internal/good.TestNestedSuccess (0.00s) 16 | PASS testjson/internal/good.TestWithStderr (0.00s) 17 | SKIP testjson/internal/good.TestSkipped (0.00s) 18 | PASS testjson/internal/good.TestParallelTheSecond (0.01s) 19 | PASS testjson/internal/good.TestParallelTheFirst (0.01s) 20 | PASS testjson/internal/good.TestParallelTheThird (0.00s) 21 | PASS testjson/internal/good 22 | PASS testjson/internal/parallelfails.TestPassedWithLog (0.00s) 23 | === RUN TestNestedParallelFailures/a 24 | === PAUSE TestNestedParallelFailures/a 25 | === CONT TestNestedParallelFailures/a 26 | fails_test.go:50: failed sub a 27 | --- FAIL: TestNestedParallelFailures/a (0.00s) 28 | FAIL testjson/internal/parallelfails.TestNestedParallelFailures/a (0.00s) 29 | === RUN TestNestedParallelFailures/d 30 | === PAUSE TestNestedParallelFailures/d 31 | === CONT TestNestedParallelFailures/d 32 | fails_test.go:50: failed sub d 33 | --- FAIL: TestNestedParallelFailures/d (0.00s) 34 | FAIL testjson/internal/parallelfails.TestNestedParallelFailures/d (0.00s) 35 | === RUN TestNestedParallelFailures/c 36 | === PAUSE TestNestedParallelFailures/c 37 | === CONT TestNestedParallelFailures/c 38 | fails_test.go:50: failed sub c 39 | --- FAIL: TestNestedParallelFailures/c (0.00s) 40 | FAIL testjson/internal/parallelfails.TestNestedParallelFailures/c (0.00s) 41 | === RUN TestNestedParallelFailures/b 42 | === PAUSE TestNestedParallelFailures/b 43 | === CONT TestNestedParallelFailures/b 44 | fails_test.go:50: failed sub b 45 | --- FAIL: TestNestedParallelFailures/b (0.00s) 46 | FAIL testjson/internal/parallelfails.TestNestedParallelFailures/b (0.00s) 47 | === RUN TestNestedParallelFailures 48 | --- FAIL: TestNestedParallelFailures (0.00s) 49 | FAIL testjson/internal/parallelfails.TestNestedParallelFailures (0.00s) 50 | PASS testjson/internal/parallelfails.TestPassed (0.00s) 51 | PASS testjson/internal/parallelfails.TestPassedWithStdout (0.00s) 52 | PASS testjson/internal/parallelfails.TestWithStderr (0.00s) 53 | === RUN TestParallelTheSecond 54 | === PAUSE TestParallelTheSecond 55 | === CONT TestParallelTheSecond 56 | fails_test.go:35: failed the second 57 | --- FAIL: TestParallelTheSecond (0.01s) 58 | FAIL testjson/internal/parallelfails.TestParallelTheSecond (0.01s) 59 | === RUN TestParallelTheFirst 60 | === PAUSE TestParallelTheFirst 61 | === CONT TestParallelTheFirst 62 | fails_test.go:29: failed the first 63 | --- FAIL: TestParallelTheFirst (0.01s) 64 | FAIL testjson/internal/parallelfails.TestParallelTheFirst (0.01s) 65 | === RUN TestParallelTheThird 66 | === PAUSE TestParallelTheThird 67 | === CONT TestParallelTheThird 68 | fails_test.go:41: failed the third 69 | --- FAIL: TestParallelTheThird (0.00s) 70 | FAIL testjson/internal/parallelfails.TestParallelTheThird (0.00s) 71 | FAIL testjson/internal/parallelfails (-test.shuffle 123456) 72 | PASS testjson/internal/withfails.TestPassedWithStdout (0.00s) 73 | SKIP testjson/internal/withfails.TestSkipped (0.00s) 74 | PASS testjson/internal/withfails.TestNestedWithFailure/a/sub (0.00s) 75 | PASS testjson/internal/withfails.TestNestedWithFailure/a (0.00s) 76 | PASS testjson/internal/withfails.TestNestedWithFailure/b/sub (0.00s) 77 | PASS testjson/internal/withfails.TestNestedWithFailure/b (0.00s) 78 | === RUN TestNestedWithFailure/c 79 | fails_test.go:65: failed 80 | --- FAIL: TestNestedWithFailure/c (0.00s) 81 | FAIL testjson/internal/withfails.TestNestedWithFailure/c (0.00s) 82 | PASS testjson/internal/withfails.TestNestedWithFailure/d/sub (0.00s) 83 | PASS testjson/internal/withfails.TestNestedWithFailure/d (0.00s) 84 | === RUN TestNestedWithFailure 85 | --- FAIL: TestNestedWithFailure (0.00s) 86 | FAIL testjson/internal/withfails.TestNestedWithFailure (0.00s) 87 | PASS testjson/internal/withfails.TestWithStderr (0.00s) 88 | PASS testjson/internal/withfails.TestPassed (0.00s) 89 | SKIP testjson/internal/withfails.TestSkippedWitLog (0.00s) 90 | PASS testjson/internal/withfails.TestNestedSuccess/a/sub (0.00s) 91 | PASS testjson/internal/withfails.TestNestedSuccess/a (0.00s) 92 | PASS testjson/internal/withfails.TestNestedSuccess/b/sub (0.00s) 93 | PASS testjson/internal/withfails.TestNestedSuccess/b (0.00s) 94 | PASS testjson/internal/withfails.TestNestedSuccess/c/sub (0.00s) 95 | PASS testjson/internal/withfails.TestNestedSuccess/c (0.00s) 96 | PASS testjson/internal/withfails.TestNestedSuccess/d/sub (0.00s) 97 | PASS testjson/internal/withfails.TestNestedSuccess/d (0.00s) 98 | PASS testjson/internal/withfails.TestNestedSuccess (0.00s) 99 | PASS testjson/internal/withfails.TestPassedWithLog (0.00s) 100 | SKIP testjson/internal/withfails.TestTimeout (0.00s) 101 | === RUN TestFailedWithStderr 102 | this is stderr 103 | fails_test.go:43: also failed 104 | --- FAIL: TestFailedWithStderr (0.00s) 105 | FAIL testjson/internal/withfails.TestFailedWithStderr (0.00s) 106 | === RUN TestFailed 107 | fails_test.go:34: this failed 108 | --- FAIL: TestFailed (0.00s) 109 | FAIL testjson/internal/withfails.TestFailed (0.00s) 110 | PASS testjson/internal/withfails.TestParallelTheFirst (0.01s) 111 | PASS testjson/internal/withfails.TestParallelTheThird (0.00s) 112 | PASS testjson/internal/withfails.TestParallelTheSecond (0.01s) 113 | FAIL testjson/internal/withfails (-test.shuffle 123456) 114 | -------------------------------------------------------------------------------- /testjson/testdata/format/testname.out: -------------------------------------------------------------------------------- 1 | sometimes main can exit 2 2 | FAIL testjson/internal/badmain 3 | EMPTY testjson/internal/empty (cached) 4 | PASS testjson/internal/good.TestPassed (0.00s) 5 | PASS testjson/internal/good.TestPassedWithLog (0.00s) 6 | PASS testjson/internal/good.TestPassedWithStdout (0.00s) 7 | SKIP testjson/internal/good.TestSkipped (0.00s) 8 | SKIP testjson/internal/good.TestSkippedWitLog (0.00s) 9 | PASS testjson/internal/good.TestWithStderr (0.00s) 10 | PASS testjson/internal/good.TestNestedSuccess/a/sub (0.00s) 11 | PASS testjson/internal/good.TestNestedSuccess/a (0.00s) 12 | PASS testjson/internal/good.TestNestedSuccess/b/sub (0.00s) 13 | PASS testjson/internal/good.TestNestedSuccess/b (0.00s) 14 | PASS testjson/internal/good.TestNestedSuccess/c/sub (0.00s) 15 | PASS testjson/internal/good.TestNestedSuccess/c (0.00s) 16 | PASS testjson/internal/good.TestNestedSuccess/d/sub (0.00s) 17 | PASS testjson/internal/good.TestNestedSuccess/d (0.00s) 18 | PASS testjson/internal/good.TestNestedSuccess (0.00s) 19 | PASS testjson/internal/good.TestParallelTheFirst (0.01s) 20 | PASS testjson/internal/good.TestParallelTheThird (0.00s) 21 | PASS testjson/internal/good.TestParallelTheSecond (0.01s) 22 | PASS testjson/internal/good (cached) 23 | PASS testjson/internal/parallelfails.TestPassed (0.00s) 24 | PASS testjson/internal/parallelfails.TestPassedWithLog (0.00s) 25 | PASS testjson/internal/parallelfails.TestPassedWithStdout (0.00s) 26 | PASS testjson/internal/parallelfails.TestWithStderr (0.00s) 27 | === RUN TestNestedParallelFailures/a 28 | === PAUSE TestNestedParallelFailures/a 29 | === CONT TestNestedParallelFailures/a 30 | fails_test.go:50: failed sub a 31 | --- FAIL: TestNestedParallelFailures/a (0.00s) 32 | FAIL testjson/internal/parallelfails.TestNestedParallelFailures/a (0.00s) 33 | === RUN TestNestedParallelFailures/d 34 | === PAUSE TestNestedParallelFailures/d 35 | === CONT TestNestedParallelFailures/d 36 | fails_test.go:50: failed sub d 37 | --- FAIL: TestNestedParallelFailures/d (0.00s) 38 | FAIL testjson/internal/parallelfails.TestNestedParallelFailures/d (0.00s) 39 | === RUN TestNestedParallelFailures/c 40 | === PAUSE TestNestedParallelFailures/c 41 | === CONT TestNestedParallelFailures/c 42 | fails_test.go:50: failed sub c 43 | --- FAIL: TestNestedParallelFailures/c (0.00s) 44 | FAIL testjson/internal/parallelfails.TestNestedParallelFailures/c (0.00s) 45 | === RUN TestNestedParallelFailures/b 46 | === PAUSE TestNestedParallelFailures/b 47 | === CONT TestNestedParallelFailures/b 48 | fails_test.go:50: failed sub b 49 | --- FAIL: TestNestedParallelFailures/b (0.00s) 50 | FAIL testjson/internal/parallelfails.TestNestedParallelFailures/b (0.00s) 51 | === RUN TestNestedParallelFailures 52 | --- FAIL: TestNestedParallelFailures (0.00s) 53 | FAIL testjson/internal/parallelfails.TestNestedParallelFailures (0.00s) 54 | === RUN TestParallelTheFirst 55 | === PAUSE TestParallelTheFirst 56 | === CONT TestParallelTheFirst 57 | fails_test.go:29: failed the first 58 | --- FAIL: TestParallelTheFirst (0.01s) 59 | FAIL testjson/internal/parallelfails.TestParallelTheFirst (0.01s) 60 | === RUN TestParallelTheThird 61 | === PAUSE TestParallelTheThird 62 | === CONT TestParallelTheThird 63 | fails_test.go:41: failed the third 64 | --- FAIL: TestParallelTheThird (0.00s) 65 | FAIL testjson/internal/parallelfails.TestParallelTheThird (0.00s) 66 | === RUN TestParallelTheSecond 67 | === PAUSE TestParallelTheSecond 68 | === CONT TestParallelTheSecond 69 | fails_test.go:35: failed the second 70 | --- FAIL: TestParallelTheSecond (0.01s) 71 | FAIL testjson/internal/parallelfails.TestParallelTheSecond (0.01s) 72 | FAIL testjson/internal/parallelfails 73 | PASS testjson/internal/withfails.TestPassed (0.00s) 74 | PASS testjson/internal/withfails.TestPassedWithLog (0.00s) 75 | PASS testjson/internal/withfails.TestPassedWithStdout (0.00s) 76 | SKIP testjson/internal/withfails.TestSkipped (0.00s) 77 | SKIP testjson/internal/withfails.TestSkippedWitLog (0.00s) 78 | === RUN TestFailed 79 | fails_test.go:34: this failed 80 | --- FAIL: TestFailed (0.00s) 81 | FAIL testjson/internal/withfails.TestFailed (0.00s) 82 | PASS testjson/internal/withfails.TestWithStderr (0.00s) 83 | === RUN TestFailedWithStderr 84 | this is stderr 85 | fails_test.go:43: also failed 86 | --- FAIL: TestFailedWithStderr (0.00s) 87 | FAIL testjson/internal/withfails.TestFailedWithStderr (0.00s) 88 | PASS testjson/internal/withfails.TestNestedWithFailure/a/sub (0.00s) 89 | PASS testjson/internal/withfails.TestNestedWithFailure/a (0.00s) 90 | PASS testjson/internal/withfails.TestNestedWithFailure/b/sub (0.00s) 91 | PASS testjson/internal/withfails.TestNestedWithFailure/b (0.00s) 92 | === RUN TestNestedWithFailure/c 93 | fails_test.go:65: failed 94 | --- FAIL: TestNestedWithFailure/c (0.00s) 95 | FAIL testjson/internal/withfails.TestNestedWithFailure/c (0.00s) 96 | PASS testjson/internal/withfails.TestNestedWithFailure/d/sub (0.00s) 97 | PASS testjson/internal/withfails.TestNestedWithFailure/d (0.00s) 98 | === RUN TestNestedWithFailure 99 | --- FAIL: TestNestedWithFailure (0.00s) 100 | FAIL testjson/internal/withfails.TestNestedWithFailure (0.00s) 101 | PASS testjson/internal/withfails.TestNestedSuccess/a/sub (0.00s) 102 | PASS testjson/internal/withfails.TestNestedSuccess/a (0.00s) 103 | PASS testjson/internal/withfails.TestNestedSuccess/b/sub (0.00s) 104 | PASS testjson/internal/withfails.TestNestedSuccess/b (0.00s) 105 | PASS testjson/internal/withfails.TestNestedSuccess/c/sub (0.00s) 106 | PASS testjson/internal/withfails.TestNestedSuccess/c (0.00s) 107 | PASS testjson/internal/withfails.TestNestedSuccess/d/sub (0.00s) 108 | PASS testjson/internal/withfails.TestNestedSuccess/d (0.00s) 109 | PASS testjson/internal/withfails.TestNestedSuccess (0.00s) 110 | SKIP testjson/internal/withfails.TestTimeout (0.00s) 111 | PASS testjson/internal/withfails.TestParallelTheFirst (0.01s) 112 | PASS testjson/internal/withfails.TestParallelTheThird (0.00s) 113 | PASS testjson/internal/withfails.TestParallelTheSecond (0.01s) 114 | FAIL testjson/internal/withfails 115 | -------------------------------------------------------------------------------- /testjson/testdata/go-test-json-missing-test-events.out: -------------------------------------------------------------------------------- 1 | {"Time":"2021-04-17T14:33:35.167978423Z","Action":"run","Package":"gotest.tools/testing","Test":"TestPassed"} 2 | {"Time":"2021-04-17T14:33:35.167999152Z","Action":"output","Package":"gotest.tools/testing","Test":"TestPassed","Output":"=== RUN TestPassed\n"} 3 | {"Time":"2021-04-17T14:33:35.168007043Z","Action":"output","Package":"gotest.tools/testing","Test":"TestPassed","Output":"--- PASS: TestPassed (0.00s)\n"} 4 | {"Time":"2021-04-17T14:33:35.16801113Z","Action":"pass","Package":"gotest.tools/testing","Test":"TestPassed","Elapsed":0} 5 | {"Time":"2021-04-17T14:33:35.167978423Z","Action":"run","Package":"gotest.tools/testing","Test":"TestMissingEvent"} 6 | {"Time":"2021-04-17T14:33:35.167999152Z","Action":"output","Package":"gotest.tools/testing","Test":"TestMissingEvent","Output":"=== RUN TestMissingEvent\n"} 7 | {"Time":"2021-04-17T14:33:35.168007043Z","Action":"output","Package":"gotest.tools/testing","Test":"TestMissingEvent","Output":"--- PASS: TestMissingEvent (0.00s)\n"} 8 | {"Time":"2021-04-17T14:33:35.168147969Z","Action":"run","Package":"gotest.tools/testing","Test":"TestNestedSuccess"} 9 | {"Time":"2021-04-17T14:33:35.168150995Z","Action":"output","Package":"gotest.tools/testing","Test":"TestNestedSuccess","Output":"=== RUN TestNestedSuccess\n"} 10 | {"Time":"2021-04-17T14:33:35.168155447Z","Action":"run","Package":"gotest.tools/testing","Test":"TestNestedSuccess/a"} 11 | {"Time":"2021-04-17T14:33:35.168158403Z","Action":"output","Package":"gotest.tools/testing","Test":"TestNestedSuccess/a","Output":"=== RUN TestNestedSuccess/a\n"} 12 | {"Time":"2021-04-17T14:33:35.168161668Z","Action":"run","Package":"gotest.tools/testing","Test":"TestNestedSuccess/a/sub"} 13 | {"Time":"2021-04-17T14:33:35.168164766Z","Action":"output","Package":"gotest.tools/testing","Test":"TestNestedSuccess/a/sub","Output":"=== RUN TestNestedSuccess/a/sub\n"} 14 | {"Time":"2021-04-17T14:33:35.168168123Z","Action":"run","Package":"gotest.tools/testing","Test":"TestNestedSuccess/b"} 15 | {"Time":"2021-04-17T14:33:35.168170964Z","Action":"output","Package":"gotest.tools/testing","Test":"TestNestedSuccess/b","Output":"=== RUN TestNestedSuccess/b\n"} 16 | {"Time":"2021-04-17T14:33:35.168174253Z","Action":"run","Package":"gotest.tools/testing","Test":"TestNestedSuccess/b/sub"} 17 | {"Time":"2021-04-17T14:33:35.168177104Z","Action":"output","Package":"gotest.tools/testing","Test":"TestNestedSuccess/b/sub","Output":"=== RUN TestNestedSuccess/b/sub\n"} 18 | {"Time":"2021-04-17T14:33:35.16820637Z","Action":"output","Package":"gotest.tools/testing","Test":"TestNestedSuccess","Output":"--- PASS: TestNestedSuccess (0.00s)\n"} 19 | {"Time":"2021-04-17T14:33:35.168210256Z","Action":"output","Package":"gotest.tools/testing","Test":"TestNestedSuccess/a","Output":" --- PASS: TestNestedSuccess/a (0.00s)\n"} 20 | {"Time":"2021-04-17T14:33:35.168213987Z","Action":"output","Package":"gotest.tools/testing","Test":"TestNestedSuccess/a/sub","Output":" --- PASS: TestNestedSuccess/a/sub (0.00s)\n"} 21 | {"Time":"2021-04-17T14:33:35.168217438Z","Action":"pass","Package":"gotest.tools/testing","Test":"TestNestedSuccess/a/sub","Elapsed":0} 22 | {"Time":"2021-04-17T14:33:35.168222153Z","Action":"pass","Package":"gotest.tools/testing","Test":"TestNestedSuccess/a","Elapsed":0} 23 | {"Time":"2021-04-17T14:33:35.16826478Z","Action":"pass","Package":"gotest.tools/testing","Test":"TestNestedSuccess","Elapsed":0} 24 | {"Time":"2021-04-17T14:33:35.168147969Z","Action":"run","Package":"gotest.tools/testing","Test":"TestFailed"} 25 | {"Time":"2021-04-17T14:33:35.168150995Z","Action":"output","Package":"gotest.tools/testing","Test":"TestFailed","Output":"=== RUN TestFailed\n"} 26 | {"Time":"2021-04-17T14:33:35.168155447Z","Action":"run","Package":"gotest.tools/testing","Test":"TestFailed/a"} 27 | {"Time":"2021-04-17T14:33:35.168158403Z","Action":"output","Package":"gotest.tools/testing","Test":"TestFailed/a","Output":"=== RUN TestFailed/a\n"} 28 | {"Time":"2021-04-17T14:33:35.168161668Z","Action":"run","Package":"gotest.tools/testing","Test":"TestFailed/a/sub"} 29 | {"Time":"2021-04-17T14:33:35.168164766Z","Action":"output","Package":"gotest.tools/testing","Test":"TestFailed/a/sub","Output":"=== RUN TestFailed/a/sub\n"} 30 | {"Time":"2021-04-17T14:33:35.16826478Z","Action":"fail","Package":"gotest.tools/testing","Test":"TestFailed","Elapsed":0} 31 | {"Time":"2021-04-17T14:33:35.168147969Z","Action":"run","Package":"gotest.tools/testing","Test":"TestMissing"} 32 | {"Time":"2021-04-17T14:33:35.168150995Z","Action":"output","Package":"gotest.tools/testing","Test":"TestMissing","Output":"=== RUN TestMissing\n"} 33 | {"Time":"2021-04-17T14:33:35.168155447Z","Action":"run","Package":"gotest.tools/testing","Test":"TestMissing/a"} 34 | {"Time":"2021-04-17T14:33:35.168158403Z","Action":"output","Package":"gotest.tools/testing","Test":"TestMissing/a","Output":"=== RUN TestMissing/a\n"} 35 | {"Time":"2021-04-17T14:33:35.168308334Z","Action":"output","Package":"gotest.tools/testing","Output":"PASS\n"} 36 | {"Time":"2021-04-17T14:33:35.168311492Z","Action":"output","Package":"gotest.tools/testing","Output":"ok \tgotest.tools/testing\t(cached)\n"} 37 | {"Time":"2021-04-17T14:33:35.168316085Z","Action":"pass","Package":"gotest.tools/testing","Elapsed":0} -------------------------------------------------------------------------------- /testjson/testdata/go-test-json-with-nonjson-stdout.out: -------------------------------------------------------------------------------- 1 | {"Time":"2021-05-12T13:53:07.462687619-05:00","Action":"run","Package":"gotest.tools/gotestsum/testjson/internal/good","Test":"TestPassed"} 2 | |||This line is not valid test2json output.||| 3 | {"Time":"2021-05-12T13:53:07.46279664-05:00","Action":"output","Package":"gotest.tools/gotestsum/testjson/internal/good","Test":"TestPassed","Output":"=== RUN TestPassed\n"} 4 | {"Time":"2021-05-12T13:53:07.462812837-05:00","Action":"output","Package":"gotest.tools/gotestsum/testjson/internal/good","Test":"TestPassed","Output":"--- PASS: TestPassed (0.00s)\n"} 5 | {"Time":"2021-05-12T13:53:07.462819251-05:00","Action":"pass","Package":"gotest.tools/gotestsum/testjson/internal/good","Test":"TestPassed","Elapsed":0} 6 | {"Time":"2021-05-12T13:53:07.462825108-05:00","Action":"output","Package":"gotest.tools/gotestsum/testjson/internal/good","Output":"PASS\n"} 7 | {"Time":"2021-05-12T13:53:07.462848483-05:00","Action":"output","Package":"gotest.tools/gotestsum/testjson/internal/good","Output":"ok \tgotest.tools/gotestsum/testjson/internal/good\t0.001s\n"} 8 | {"Time":"2021-05-12T13:53:07.46309146-05:00","Action":"pass","Package":"gotest.tools/gotestsum/testjson/internal/good","Elapsed":0.001} 9 | -------------------------------------------------------------------------------- /testjson/testdata/go-test.err: -------------------------------------------------------------------------------- 1 | # gotest.tools/gotestsum/testjson/internal/broken 2 | internal/broken/broken.go:5:21: undefined: somepackage 3 | -------------------------------------------------------------------------------- /testjson/testdata/input/go-test-json-misattributed.out: -------------------------------------------------------------------------------- 1 | {"Time":"2020-04-10T14:52:44.192693974-04:00","Action":"run","Test":"TestOutputWithSubtest"} 2 | {"Action":"output","Test":"TestOutputWithSubtest","Output":"=== RUN TestOutputWithSubtest\n"} 3 | {"Action":"run","Test":"TestOutputWithSubtest/sub_test"} 4 | {"Action":"output","Test":"TestOutputWithSubtest/sub_test","Output":"=== RUN TestOutputWithSubtest/sub_test\n"} 5 | {"Action":"run","Test":"TestOutputWithSubtest/sub_test/sub2"} 6 | {"Action":"output","Test":"TestOutputWithSubtest/sub_test/sub2","Output":"=== RUN TestOutputWithSubtest/sub_test/sub2\n"} 7 | {"Action":"run","Test":"TestOutputWithSubtest/sub_test2"} 8 | {"Action":"output","Test":"TestOutputWithSubtest/sub_test2","Output":"=== RUN TestOutputWithSubtest/sub_test2\n"} 9 | {"Action":"run","Test":"TestOutputWithSubtest/sub_test2/sub2"} 10 | {"Action":"output","Test":"TestOutputWithSubtest/sub_test2/sub2","Output":"=== RUN TestOutputWithSubtest/sub_test2/sub2\n"} 11 | {"Action":"output","Test":"TestOutputWithSubtest","Output":"--- FAIL: TestOutputWithSubtest (0.00s)\n"} 12 | {"Action":"output","Test":"TestOutputWithSubtest/sub_test","Output":" --- PASS: TestOutputWithSubtest/sub_test (0.00s)\n"} 13 | {"Action":"output","Test":"TestOutputWithSubtest/sub_test","Output":" foo_test.go:9: output from sub test\n"} 14 | {"Action":"output","Test":"TestOutputWithSubtest/sub_test","Output":" foo_test.go:11: more output from sub test\n"} 15 | {"Action":"output","Test":"TestOutputWithSubtest/sub_test","Output":" foo_test.go:16: more output from sub test\n"} 16 | {"Action":"output","Test":"TestOutputWithSubtest/sub_test/sub2","Output":" --- PASS: TestOutputWithSubtest/sub_test/sub2 (0.00s)\n"} 17 | {"Action":"output","Test":"TestOutputWithSubtest/sub_test/sub2","Output":" foo_test.go:14: output from sub2 test\n"} 18 | {"Action":"output","Test":"TestOutputWithSubtest/sub_test/sub2","Output":" foo_test.go:22: output from root test\n"} 19 | {"Action":"output","Test":"TestOutputWithSubtest/sub_test/sub2","Output":" foo_test.go:27: output from root test\n"} 20 | {"Action":"pass","Test":"TestOutputWithSubtest/sub_test/sub2"} 21 | {"Action":"pass","Test":"TestOutputWithSubtest/sub_test"} 22 | {"Action":"output","Test":"TestOutputWithSubtest/sub_test2","Output":" --- PASS: TestOutputWithSubtest/sub_test2 (0.00s)\n"} 23 | {"Action":"output","Test":"TestOutputWithSubtest/sub_test2","Output":" foo_test.go:21: output from sub test2\n"} 24 | {"Action":"output","Test":"TestOutputWithSubtest/sub_test2","Output":" foo_test.go:23: more output from sub test2\n"} 25 | {"Action":"output","Test":"TestOutputWithSubtest/sub_test2","Output":" foo_test.go:28: more output from sub test2\n"} 26 | {"Action":"output","Test":"TestOutputWithSubtest/sub_test2/sub2","Output":" --- PASS: TestOutputWithSubtest/sub_test2/sub2 (0.00s)\n"} 27 | {"Action":"output","Test":"TestOutputWithSubtest/sub_test2/sub2","Output":" foo_test.go:26: output from sub2 test\n"} 28 | {"Action":"output","Test":"TestOutputWithSubtest/sub_test2/sub2","Output":" foo_test.go:32: output after sub test\n"} 29 | {"Action":"pass","Test":"TestOutputWithSubtest/sub_test2/sub2"} 30 | {"Action":"pass","Test":"TestOutputWithSubtest/sub_test2"} 31 | {"Action":"fail","Test":"TestOutputWithSubtest"} 32 | {"Action":"output","Output":"FAIL\n"} 33 | {"Action":"output","Output":"FAIL\tgotest.tools/gotestsum/foo\t0.001s\n"} 34 | {"Action":"output","Output":"FAIL\n"} 35 | {"Action":"fail"} 36 | -------------------------------------------------------------------------------- /testjson/testdata/input/go-test-json-missing-test-fail.out: -------------------------------------------------------------------------------- 1 | {"Time":"2020-04-10T14:52:44.192693974-04:00","Action":"run","Package":"gotest.tools/v3/poll","Test":"TestWaitOn_WithCompare"} 2 | {"Time":"2020-04-10T14:52:44.192822137-04:00","Action":"output","Package":"gotest.tools/v3/poll","Test":"TestWaitOn_WithCompare","Output":"=== RUN TestWaitOn_WithCompare\n"} 3 | {"Time":"2020-04-10T14:52:44.1950981-04:00","Action":"output","Package":"gotest.tools/v3/poll","Test":"TestWaitOn_WithCompare","Output":"panic: runtime error: index out of range [1] with length 1\n"} 4 | {"Time":"2020-04-10T14:52:44.195110282-04:00","Action":"output","Package":"gotest.tools/v3/poll","Test":"TestWaitOn_WithCompare","Output":"\n"} 5 | {"Time":"2020-04-10T14:52:44.195116665-04:00","Action":"output","Package":"gotest.tools/v3/poll","Test":"TestWaitOn_WithCompare","Output":"goroutine 7 [running]:\n"} 6 | {"Time":"2020-04-10T14:52:44.195120587-04:00","Action":"output","Package":"gotest.tools/v3/poll","Test":"TestWaitOn_WithCompare","Output":"gotest.tools/v3/internal/assert.ArgsFromComparisonCall(0xc0000552a0, 0x1, 0x1, 0x1, 0x0, 0x0)\n"} 7 | {"Time":"2020-04-10T14:52:44.195301254-04:00","Action":"output","Package":"gotest.tools/v3/poll","Test":"TestWaitOn_WithCompare","Output":"\t/home/daniel/pers/code/gotest.tools/internal/assert/result.go:102 +0x9f\n"} 8 | {"Time":"2020-04-10T14:52:44.195332206-04:00","Action":"output","Package":"gotest.tools/v3/poll","Test":"TestWaitOn_WithCompare","Output":"gotest.tools/v3/internal/assert.runComparison(0x6bcb80, 0xc00000e180, 0x67dee8, 0xc00007a9f0, 0x0, 0x0, 0x0, 0x7f7f4fb6d108)\n"} 9 | {"Time":"2020-04-10T14:52:44.19533807-04:00","Action":"output","Package":"gotest.tools/v3/poll","Test":"TestWaitOn_WithCompare","Output":"\t/home/daniel/pers/code/gotest.tools/internal/assert/result.go:34 +0x2b1\n"} 10 | {"Time":"2020-04-10T14:52:44.195342341-04:00","Action":"output","Package":"gotest.tools/v3/poll","Test":"TestWaitOn_WithCompare","Output":"gotest.tools/v3/internal/assert.Eval(0x6bcb80, 0xc00000e180, 0x67dee8, 0x627660, 0xc00007a9f0, 0x0, 0x0, 0x0, 0x642c60)\n"} 11 | {"Time":"2020-04-10T14:52:44.195346449-04:00","Action":"output","Package":"gotest.tools/v3/poll","Test":"TestWaitOn_WithCompare","Output":"\t/home/daniel/pers/code/gotest.tools/internal/assert/assert.go:56 +0x2e4\n"} 12 | {"Time":"2020-04-10T14:52:44.195350377-04:00","Action":"output","Package":"gotest.tools/v3/poll","Test":"TestWaitOn_WithCompare","Output":"gotest.tools/v3/poll.Compare(0xc00007a9f0, 0x6b74a0, 0x618a60)\n"} 13 | {"Time":"2020-04-10T14:52:44.195356946-04:00","Action":"output","Package":"gotest.tools/v3/poll","Test":"TestWaitOn_WithCompare","Output":"\t/home/daniel/pers/code/gotest.tools/poll/poll.go:151 +0x81\n"} 14 | {"Time":"2020-04-10T14:52:44.195360761-04:00","Action":"output","Package":"gotest.tools/v3/poll","Test":"TestWaitOn_WithCompare","Output":"gotest.tools/v3/poll.TestWaitOn_WithCompare.func1(0x6be4c0, 0xc00016c240, 0xc00016c240, 0x6be4c0)\n"} 15 | {"Time":"2020-04-10T14:52:44.195367482-04:00","Action":"output","Package":"gotest.tools/v3/poll","Test":"TestWaitOn_WithCompare","Output":"\t/home/daniel/pers/code/gotest.tools/poll/poll_test.go:81 +0x58\n"} 16 | {"Time":"2020-04-10T14:52:44.195371319-04:00","Action":"output","Package":"gotest.tools/v3/poll","Test":"TestWaitOn_WithCompare","Output":"gotest.tools/v3/poll.WaitOn.func1(0xc00001e3c0, 0x67df50, 0x6c1960, 0xc00016c240)\n"} 17 | {"Time":"2020-04-10T14:52:44.195375766-04:00","Action":"output","Package":"gotest.tools/v3/poll","Test":"TestWaitOn_WithCompare","Output":"\t/home/daniel/pers/code/gotest.tools/poll/poll.go:125 +0x62\n"} 18 | {"Time":"2020-04-10T14:52:44.195379421-04:00","Action":"output","Package":"gotest.tools/v3/poll","Test":"TestWaitOn_WithCompare","Output":"created by gotest.tools/v3/poll.WaitOn\n"} 19 | {"Time":"2020-04-10T14:52:44.195384493-04:00","Action":"output","Package":"gotest.tools/v3/poll","Test":"TestWaitOn_WithCompare","Output":"\t/home/daniel/pers/code/gotest.tools/poll/poll.go:124 +0x16f\n"} 20 | {"Time":"2020-04-10T14:52:44.195785223-04:00","Action":"output","Package":"gotest.tools/v3/poll","Output":"FAIL\tgotest.tools/v3/poll\t0.005s\n"} 21 | {"Time":"2020-04-10T14:52:44.195796081-04:00","Action":"fail","Package":"gotest.tools/v3/poll","Elapsed":0.005} 22 | -------------------------------------------------------------------------------- /testjson/testdata/input/go-test-json-with-cover-go1.20.err: -------------------------------------------------------------------------------- 1 | # gotest.tools/gotestsum/testjson/internal/broken 2 | internal/broken/broken.go:5:21: undefined: somepackage 3 | -------------------------------------------------------------------------------- /testjson/testdata/input/go-test-json-with-cover.err: -------------------------------------------------------------------------------- 1 | # gotest.tools/gotestsum/testjson/internal/broken 2 | internal/broken/broken.go:5:21: undefined: somepackage 3 | -------------------------------------------------------------------------------- /testjson/testdata/input/go-test-json-with-shuffle.err: -------------------------------------------------------------------------------- 1 | # gotest.tools/gotestsum/testjson/internal/broken 2 | internal/broken/broken.go:5:21: undefined: somepackage 3 | -------------------------------------------------------------------------------- /testjson/testdata/input/go-test-json.err: -------------------------------------------------------------------------------- 1 | # gotest.tools/gotestsum/testjson/internal/broken 2 | testjson/internal/broken/broken.go:5:21: undefined: somepackage 3 | -------------------------------------------------------------------------------- /testjson/testdata/summary/bug-missing-skip-message: -------------------------------------------------------------------------------- 1 | 2 | === Skipped 3 | === SKIP: testjson TestNewDotFormatter (0.00s) 4 | WARN Failed to detect terminal width for dots format, error: inappropriate ioctl for device 5 | TestNewDotFormatter: dotformat_test.go:161: !ok: no terminal width 6 | 7 | === SKIP: testjson TestGetPkgPathPrefix/with_go_path (0.00s) 8 | TestGetPkgPathPrefix/with_go_path: pkgpathprefix_test.go:22: isGoModuleEnabled() 9 | --- SKIP: TestGetPkgPathPrefix/with_go_path (0.00s) 10 | 11 | DONE 42 tests, 2 skipped in 0.328s 12 | -------------------------------------------------------------------------------- /testjson/testdata/summary/misattributed-output: -------------------------------------------------------------------------------- 1 | 2 | === Failed 3 | === FAIL: TestOutputWithSubtest (0.00s) 4 | --- PASS: TestOutputWithSubtest/sub_test (0.00s) 5 | foo_test.go:9: output from sub test 6 | foo_test.go:11: more output from sub test 7 | foo_test.go:16: more output from sub test 8 | --- PASS: TestOutputWithSubtest/sub_test/sub2 (0.00s) 9 | foo_test.go:14: output from sub2 test 10 | foo_test.go:22: output from root test 11 | foo_test.go:27: output from root test 12 | --- PASS: TestOutputWithSubtest/sub_test2 (0.00s) 13 | foo_test.go:21: output from sub test2 14 | foo_test.go:23: more output from sub test2 15 | foo_test.go:28: more output from sub test2 16 | --- PASS: TestOutputWithSubtest/sub_test2/sub2 (0.00s) 17 | foo_test.go:26: output from sub2 test 18 | foo_test.go:32: output after sub test 19 | 20 | DONE 5 tests, 1 failure in 0.000s 21 | -------------------------------------------------------------------------------- /testjson/testdata/summary/missing-test-fail-event: -------------------------------------------------------------------------------- 1 | 2 | === Failed 3 | === FAIL: gotest.tools/v3/poll TestWaitOn_WithCompare (unknown) 4 | panic: runtime error: index out of range [1] with length 1 5 | 6 | goroutine 7 [running]: 7 | gotest.tools/v3/internal/assert.ArgsFromComparisonCall(0xc0000552a0, 0x1, 0x1, 0x1, 0x0, 0x0) 8 | /home/daniel/pers/code/gotest.tools/internal/assert/result.go:102 +0x9f 9 | gotest.tools/v3/internal/assert.runComparison(0x6bcb80, 0xc00000e180, 0x67dee8, 0xc00007a9f0, 0x0, 0x0, 0x0, 0x7f7f4fb6d108) 10 | /home/daniel/pers/code/gotest.tools/internal/assert/result.go:34 +0x2b1 11 | gotest.tools/v3/internal/assert.Eval(0x6bcb80, 0xc00000e180, 0x67dee8, 0x627660, 0xc00007a9f0, 0x0, 0x0, 0x0, 0x642c60) 12 | /home/daniel/pers/code/gotest.tools/internal/assert/assert.go:56 +0x2e4 13 | gotest.tools/v3/poll.Compare(0xc00007a9f0, 0x6b74a0, 0x618a60) 14 | /home/daniel/pers/code/gotest.tools/poll/poll.go:151 +0x81 15 | gotest.tools/v3/poll.TestWaitOn_WithCompare.func1(0x6be4c0, 0xc00016c240, 0xc00016c240, 0x6be4c0) 16 | /home/daniel/pers/code/gotest.tools/poll/poll_test.go:81 +0x58 17 | gotest.tools/v3/poll.WaitOn.func1(0xc00001e3c0, 0x67df50, 0x6c1960, 0xc00016c240) 18 | /home/daniel/pers/code/gotest.tools/poll/poll.go:125 +0x62 19 | created by gotest.tools/v3/poll.WaitOn 20 | /home/daniel/pers/code/gotest.tools/poll/poll.go:124 +0x16f 21 | 22 | DONE 1 tests, 1 failure in 0.003s 23 | -------------------------------------------------------------------------------- /testjson/testdata/summary/parallel-failures: -------------------------------------------------------------------------------- 1 | 2 | === Failed 3 | === FAIL: testjson/internal/parallelfails TestNestedParallelFailures/a (0.00s) 4 | TestNestedParallelFailures/a: fails_test.go:50: failed sub a 5 | --- FAIL: TestNestedParallelFailures/a (0.00s) 6 | 7 | === FAIL: testjson/internal/parallelfails TestNestedParallelFailures/d (0.00s) 8 | TestNestedParallelFailures/d: fails_test.go:50: failed sub d 9 | --- FAIL: TestNestedParallelFailures/d (0.00s) 10 | 11 | === FAIL: testjson/internal/parallelfails TestNestedParallelFailures/c (0.00s) 12 | TestNestedParallelFailures/c: fails_test.go:50: failed sub c 13 | --- FAIL: TestNestedParallelFailures/c (0.00s) 14 | 15 | === FAIL: testjson/internal/parallelfails TestNestedParallelFailures/b (0.00s) 16 | TestNestedParallelFailures/b: fails_test.go:50: failed sub b 17 | --- FAIL: TestNestedParallelFailures/b (0.00s) 18 | 19 | === FAIL: testjson/internal/parallelfails TestNestedParallelFailures (0.00s) 20 | 21 | === FAIL: testjson/internal/parallelfails TestParallelTheFirst (0.01s) 22 | TestParallelTheFirst: fails_test.go:29: failed the first 23 | 24 | === FAIL: testjson/internal/parallelfails TestParallelTheThird (0.00s) 25 | TestParallelTheThird: fails_test.go:41: failed the third 26 | 27 | === FAIL: testjson/internal/parallelfails TestParallelTheSecond (0.01s) 28 | TestParallelTheSecond: fails_test.go:35: failed the second 29 | 30 | DONE 12 tests, 8 failures in 0.025s 31 | -------------------------------------------------------------------------------- /testjson/testdata/summary/root-test-has-subtest-failures: -------------------------------------------------------------------------------- 1 | 2 | === Skipped 3 | === SKIP: testjson/internal/good TestSkipped (0.00s) 4 | good_test.go:23: 5 | 6 | === SKIP: testjson/internal/good TestSkippedWitLog (0.00s) 7 | good_test.go:27: the skip message 8 | 9 | === SKIP: testjson/internal/withfails TestSkipped (0.00s) 10 | fails_test.go:26: 11 | 12 | === SKIP: testjson/internal/withfails TestSkippedWitLog (0.00s) 13 | fails_test.go:30: the skip message 14 | 15 | === SKIP: testjson/internal/withfails TestTimeout (0.00s) 16 | timeout_test.go:13: skipping slow test 17 | 18 | === Failed 19 | === FAIL: testjson/internal/badmain (0.00s) 20 | sometimes main can exit 2 21 | FAIL gotest.tools/gotestsum/testjson/internal/badmain 0.001s 22 | 23 | === FAIL: testjson/internal/parallelfails TestNestedParallelFailures/a (0.00s) 24 | fails_test.go:50: failed sub a 25 | --- FAIL: TestNestedParallelFailures/a (0.00s) 26 | 27 | === FAIL: testjson/internal/parallelfails TestNestedParallelFailures/d (0.00s) 28 | fails_test.go:50: failed sub d 29 | --- FAIL: TestNestedParallelFailures/d (0.00s) 30 | 31 | === FAIL: testjson/internal/parallelfails TestNestedParallelFailures/c (0.00s) 32 | fails_test.go:50: failed sub c 33 | --- FAIL: TestNestedParallelFailures/c (0.00s) 34 | 35 | === FAIL: testjson/internal/parallelfails TestNestedParallelFailures/b (0.00s) 36 | fails_test.go:50: failed sub b 37 | --- FAIL: TestNestedParallelFailures/b (0.00s) 38 | 39 | === FAIL: testjson/internal/parallelfails TestNestedParallelFailures (0.00s) 40 | 41 | === FAIL: testjson/internal/parallelfails TestParallelTheFirst (0.01s) 42 | fails_test.go:29: failed the first 43 | 44 | === FAIL: testjson/internal/parallelfails TestParallelTheThird (0.00s) 45 | fails_test.go:41: failed the third 46 | 47 | === FAIL: testjson/internal/parallelfails TestParallelTheSecond (0.01s) 48 | fails_test.go:35: failed the second 49 | 50 | === FAIL: testjson/internal/withfails TestFailed (0.00s) 51 | fails_test.go:34: this failed 52 | 53 | === FAIL: testjson/internal/withfails TestFailedWithStderr (0.00s) 54 | this is stderr 55 | fails_test.go:43: also failed 56 | 57 | === FAIL: testjson/internal/withfails TestNestedWithFailure/c (0.00s) 58 | fails_test.go:65: failed 59 | --- FAIL: TestNestedWithFailure/c (0.00s) 60 | 61 | === FAIL: testjson/internal/withfails TestNestedWithFailure (0.00s) 62 | 63 | DONE 59 tests, 5 skipped, 13 failures in 0.157s 64 | -------------------------------------------------------------------------------- /testjson/testdata/summary/with-run-id: -------------------------------------------------------------------------------- 1 | 2 | === Skipped 3 | === SKIP: testjson/internal/good TestSkipped (re-run 7) (0.00s) 4 | good_test.go:23: 5 | 6 | === SKIP: testjson/internal/good TestSkippedWitLog (re-run 7) (0.00s) 7 | good_test.go:27: the skip message 8 | 9 | === SKIP: testjson/internal/withfails TestSkipped (re-run 7) (0.00s) 10 | fails_test.go:26: 11 | 12 | === SKIP: testjson/internal/withfails TestSkippedWitLog (re-run 7) (0.00s) 13 | fails_test.go:30: the skip message 14 | 15 | === SKIP: testjson/internal/withfails TestTimeout (re-run 7) (0.00s) 16 | timeout_test.go:13: skipping slow test 17 | 18 | === Failed 19 | === FAIL: testjson/internal/badmain (0.00s) 20 | sometimes main can exit 2 21 | FAIL gotest.tools/gotestsum/testjson/internal/badmain 0.001s 22 | 23 | === FAIL: testjson/internal/parallelfails TestNestedParallelFailures/a (re-run 7) (0.00s) 24 | fails_test.go:50: failed sub a 25 | --- FAIL: TestNestedParallelFailures/a (0.00s) 26 | 27 | === FAIL: testjson/internal/parallelfails TestNestedParallelFailures/d (re-run 7) (0.00s) 28 | fails_test.go:50: failed sub d 29 | --- FAIL: TestNestedParallelFailures/d (0.00s) 30 | 31 | === FAIL: testjson/internal/parallelfails TestNestedParallelFailures/c (re-run 7) (0.00s) 32 | fails_test.go:50: failed sub c 33 | --- FAIL: TestNestedParallelFailures/c (0.00s) 34 | 35 | === FAIL: testjson/internal/parallelfails TestNestedParallelFailures/b (re-run 7) (0.00s) 36 | fails_test.go:50: failed sub b 37 | --- FAIL: TestNestedParallelFailures/b (0.00s) 38 | 39 | === FAIL: testjson/internal/parallelfails TestNestedParallelFailures (re-run 7) (0.00s) 40 | 41 | === FAIL: testjson/internal/parallelfails TestParallelTheFirst (re-run 7) (0.01s) 42 | fails_test.go:29: failed the first 43 | 44 | === FAIL: testjson/internal/parallelfails TestParallelTheThird (re-run 7) (0.00s) 45 | fails_test.go:41: failed the third 46 | 47 | === FAIL: testjson/internal/parallelfails TestParallelTheSecond (re-run 7) (0.01s) 48 | fails_test.go:35: failed the second 49 | 50 | === FAIL: testjson/internal/withfails TestFailed (re-run 7) (0.00s) 51 | fails_test.go:34: this failed 52 | 53 | === FAIL: testjson/internal/withfails TestFailedWithStderr (re-run 7) (0.00s) 54 | this is stderr 55 | fails_test.go:43: also failed 56 | 57 | === FAIL: testjson/internal/withfails TestNestedWithFailure/c (re-run 7) (0.00s) 58 | fails_test.go:65: failed 59 | --- FAIL: TestNestedWithFailure/c (0.00s) 60 | 61 | === FAIL: testjson/internal/withfails TestNestedWithFailure (re-run 7) (0.00s) 62 | 63 | DONE 8 runs, 59 tests, 5 skipped, 13 failures in 0.157s 64 | --------------------------------------------------------------------------------