├── .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 |
--------------------------------------------------------------------------------