├── .github └── workflows │ ├── ci.yaml │ ├── lint.yaml │ └── release.yaml ├── .golangci.yaml ├── .goreleaser.yaml ├── CHANGELOG.md ├── LICENSE ├── Makefile ├── README.md ├── go.mod ├── go.sum ├── internal ├── app │ ├── app.go │ ├── console_writer.go │ ├── table.go │ ├── table_failed.go │ ├── table_summary.go │ └── table_tests.go └── utils │ ├── utils.go │ └── utils_test.go ├── main.go ├── parse ├── event.go ├── event_test.go ├── package.go ├── package_slice.go ├── process.go ├── process_options.go └── test.go ├── scripts └── release-notes.sh └── tests ├── cached_test.go ├── cover_test.go ├── follow_test.go ├── outcome_test.go ├── package_start_test.go ├── panic_test.go ├── prescan_test.go ├── race_test.go ├── sort_test.go ├── summary_counts_test.go └── testdata ├── cached ├── test_01.jsonl └── test_02.jsonl ├── cover ├── test_01.jsonl ├── test_02.jsonl └── test_03.jsonl ├── elapsed_test.jsonl ├── failed ├── test_01.jsonl ├── test_02.jsonl ├── test_03.jsonl └── test_04.jsonl ├── follow-verbose ├── test_01.golden ├── test_01.jsonl ├── test_02.golden ├── test_02.jsonl ├── test_03.golden ├── test_03.jsonl ├── test_04.golden ├── test_04.jsonl ├── test_05.golden ├── test_05.jsonl ├── test_06.golden └── test_06.jsonl ├── follow ├── test_01.golden └── test_01.jsonl ├── go120_start_action.jsonl ├── metrics_test.jsonl ├── outcome ├── test_01.jsonl ├── test_02.jsonl ├── test_03.jsonl ├── test_04.jsonl ├── test_05.jsonl ├── test_06.jsonl ├── test_07.jsonl └── test_08.jsonl ├── panic ├── test_01.jsonl ├── test_02.jsonl ├── test_03.jsonl ├── test_04.jsonl ├── test_05.jsonl └── test_06.jsonl ├── prescan ├── test_01.txt ├── test_02.txt ├── test_03.txt └── test_04.txt └── race ├── test_01.jsonl ├── test_02.jsonl ├── test_03.jsonl ├── test_04.jsonl ├── test_05.jsonl ├── test_06.jsonl ├── test_07.jsonl └── test_08.jsonl /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | types: [opened, synchronize, reopened] 9 | 10 | jobs: 11 | build: 12 | name: Build and test 13 | runs-on: ubuntu-latest 14 | strategy: 15 | matrix: 16 | # go-version: ['oldstable', 'stable', '1.23.0-rc.2'] 17 | go-version: ['oldstable', 'stable'] 18 | env: 19 | VERBOSE: 1 20 | 21 | steps: 22 | - name: Checkout code 23 | uses: actions/checkout@v4 24 | - name: Set up Go 25 | uses: actions/setup-go@v5 26 | with: 27 | go-version: ${{ matrix.go-version }} 28 | - name: Build 29 | run: go build -v . 30 | - name: Run tests with GITHUB_STEP_SUMMARY 31 | shell: bash 32 | # Note the use of || true. This so the job doesn't fail at that line. We want to preserve -follow 33 | # as part of the test output, but not output it to the summary page, which is done in the proceeding 34 | # command when we parse the output.jsonl file. 35 | run: | 36 | go test -v -count=1 -race ./... -json -coverpkg github.com/mfridman/tparse/parse \ 37 | | tee output.jsonl | ./tparse -notests -follow -all || true 38 | ./tparse -format markdown -file output.jsonl -all -slow 20 > $GITHUB_STEP_SUMMARY 39 | - name: Run tparse w/ std lib 40 | run: go test -count=1 fmt strings bytes bufio crypto log mime sort slices -json -cover | ./tparse -follow -all 41 | - name: Install GoReleaser 42 | if: github.event_name == 'push' && github.ref == 'refs/heads/main' && matrix.go-version == 'stable' 43 | uses: goreleaser/goreleaser-action@v6 44 | with: 45 | install-only: true 46 | distribution: goreleaser 47 | version: "~> v2" 48 | - name: Gorelease dry-run 49 | if: github.event_name == 'push' && github.ref == 'refs/heads/main' && matrix.go-version == 'stable' 50 | run: | 51 | goreleaser release --skip=publish --snapshot --fail-fast --clean 52 | -------------------------------------------------------------------------------- /.github/workflows/lint.yaml: -------------------------------------------------------------------------------- 1 | name: golangci 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | 9 | concurrency: 10 | group: ${{ github.workflow }}-${{ github.ref }} 11 | cancel-in-progress: true 12 | 13 | jobs: 14 | golangci: 15 | name: lint 16 | runs-on: ubuntu-latest 17 | steps: 18 | - name: Checkout code 19 | uses: actions/checkout@v4 20 | - uses: actions/setup-go@v5 21 | with: 22 | go-version: 'stable' 23 | - name: golangci-lint 24 | uses: golangci/golangci-lint-action@v5 25 | with: 26 | version: latest 27 | github-token: ${{ secrets.GITHUB_TOKEN }} 28 | args: --timeout=2m --verbose 29 | annotations: false 30 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | name: goreleaser 2 | on: 3 | push: 4 | tags: 5 | - '*' 6 | permissions: 7 | contents: write 8 | jobs: 9 | goreleaser: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v4 13 | with: 14 | fetch-depth: 0 15 | - run: git fetch --force --tags 16 | - uses: actions/setup-go@v5 17 | with: 18 | go-version: 'stable' 19 | - name: Generate release notes 20 | continue-on-error: true 21 | run: ./scripts/release-notes.sh ${{github.ref_name}} > ${{runner.temp}}/release_notes.txt 22 | - name: Run GoReleaser 23 | uses: goreleaser/goreleaser-action@v6 24 | with: 25 | distribution: goreleaser 26 | version: "~> v2" 27 | args: release --clean --release-notes=${{runner.temp}}/release_notes.txt 28 | env: 29 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 30 | -------------------------------------------------------------------------------- /.golangci.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | linters: 3 | enable: 4 | # check when errors are compared without errors.Is 5 | - errorlint 6 | 7 | # check imports order and makes it always deterministic. 8 | - gci 9 | 10 | # Very Basic spell error checker 11 | - misspell 12 | 13 | # Fast, configurable, extensible, flexible, and beautiful linter for Go. 14 | # Drop-in replacement of golint. 15 | - revive 16 | 17 | # make sure to use t.Helper() when needed 18 | - thelper 19 | 20 | # ensure that lint exceptions have explanations. Consider the case below: 21 | - nolintlint 22 | 23 | # detect duplicated words in code 24 | - dupword 25 | 26 | # mirror suggests rewrites to avoid unnecessary []byte/string conversion 27 | - mirror 28 | 29 | # testify checks good usage of github.com/stretchr/testify. 30 | - testifylint 31 | 32 | linters-settings: 33 | dupword: 34 | # Keywords used to ignore detection. 35 | # Default: [] 36 | ignore: 37 | - "FAIL" # "FAIL FAIL" is tolerated 38 | 39 | nolintlint: 40 | # Disable to ensure that all nolint directives actually have an effect. 41 | # Default: false 42 | allow-unused: true # too many false positive reported 43 | # Exclude following linters from requiring an explanation. 44 | # Default: [] 45 | allow-no-explanation: [] 46 | # Enable to require an explanation of nonzero length 47 | # after each nolint directive. 48 | # Default: false 49 | require-explanation: true 50 | # Enable to require nolint directives to mention the specific 51 | # linter being suppressed. 52 | # Default: false 53 | require-specific: true 54 | 55 | revive: 56 | rules: 57 | - name: bare-return 58 | - name: blank-imports 59 | - name: comment-spacings 60 | - name: context-as-argument 61 | arguments: 62 | - allowTypesBefore: "*testing.T" 63 | - name: context-keys-type 64 | - name: defer 65 | arguments: 66 | - ["call-chain", "loop"] 67 | - name: dot-imports 68 | - name: early-return 69 | - name: empty-block 70 | - name: error-return 71 | - name: error-strings 72 | - name: error-naming 73 | - name: errorf 74 | - name: exported 75 | arguments: 76 | # enables checking public methods of private types 77 | - "checkPrivateReceivers" 78 | # make error messages clearer 79 | - "sayRepetitiveInsteadOfStutters" 80 | - name: if-return 81 | - name: import-shadowing 82 | - name: increment-decrement 83 | - name: indent-error-flow 84 | - name: exported 85 | - name: var-naming 86 | - name: var-declaration 87 | - name: package-comments 88 | - name: range 89 | - name: receiver-naming 90 | - name: redefines-builtin-id 91 | - name: superfluous-else 92 | - name: time-naming 93 | - name: time-equal 94 | - name: unexported-return 95 | - name: use-any 96 | - name: unreachable-code 97 | - name: unhandled-error 98 | arguments: 99 | - "fmt.Print.*" 100 | - "fmt.Fprint.*" 101 | - "bytes.Buffer.Write.*" 102 | - "strings.Builder.Write.*" 103 | - name: unused-parameter 104 | - name: unused-receiver 105 | - name: useless-break 106 | 107 | # define the import orders 108 | gci: 109 | sections: 110 | # Standard section: captures all standard packages. 111 | - standard 112 | # Default section: catchall that is not standard or custom 113 | - default 114 | # Custom section: groups all imports with the specified Prefix. 115 | - prefix(github.com/mfridman) 116 | 117 | -------------------------------------------------------------------------------- /.goreleaser.yaml: -------------------------------------------------------------------------------- 1 | # yaml-language-server: $schema=https://goreleaser.com/static/schema.json 2 | # 3 | # See https://goreleaser.com/customization/ for more information. 4 | version: 2 5 | project_name: tparse 6 | 7 | before: 8 | hooks: 9 | - go mod tidy 10 | builds: 11 | - env: 12 | - CGO_ENABLED=0 13 | binary: tparse 14 | main: main.go 15 | goos: 16 | - linux 17 | - darwin 18 | # - windows 19 | goarch: 20 | - amd64 21 | - arm64 22 | ldflags: 23 | # The v prefix is stripped by goreleaser, so we need to add it back. 24 | # https://goreleaser.com/customization/templates/#fnref:version-prefix 25 | - "-s -w -X main.version=v{{ .Version }}" 26 | 27 | archives: 28 | - format: binary 29 | name_template: >- 30 | {{ .ProjectName }}_{{- tolower .Os }}_{{- if eq .Arch "amd64" }}x86_64{{- else }}{{ .Arch }}{{ end }} 31 | checksum: 32 | name_template: "checksums.txt" 33 | snapshot: 34 | name_template: "{{ incpatch .Version }}-next" 35 | changelog: 36 | use: github-native 37 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project 6 | adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [Unreleased] 9 | 10 | ## [v0.17.0] 11 | 12 | - Deprecate github.com/mfridman/buildversion, and use std lib `debug.ReadBuildInfo()` instead. In 13 | go1.24 this is handled automatically, from the [release notes](https://go.dev/doc/go1.24): 14 | 15 | > The go build command now sets the main module’s version in the compiled binary based on the 16 | > version control system tag and/or commit. A +dirty suffix will be appended if there are 17 | > uncommitted changes. Use the -buildvcs=false flag to omit version control information from the 18 | > binary. 19 | 20 | - Handle changes in go1.24 related to build output. `tparse` will pipe the build output to stderr 21 | 22 | > Furthermore, `go test -json` now reports build output and failures in JSON, interleaved with 23 | > test result JSON. These are distinguished by new Action types, but if they cause problems in a 24 | > test integration system, you can revert to the text build output with GODEBUG setting 25 | > gotestjsonbuildtext=1. 26 | 27 | ## [v0.16.0] 28 | 29 | - Add a `-follow-output` flag to allow writing go test output directly into a file. This will be 30 | useful (especially in CI jobs) for outputting overly verbose testing output into a file instead of 31 | the standard stream. (#134) 32 | 33 | | flag combination | `go test` output destination | 34 | | ------------------------ | ---------------------------- | 35 | | No flags | Discard output | 36 | | `-follow` | Write to stdout | 37 | | `-follow-output` | Write to file | 38 | | `-follow -follow-output` | Write to file | 39 | 40 | - Use [charmbracelet/lipgloss](https://github.com/charmbracelet/lipgloss) for table rendering. 41 | - This will allow for more control over the output and potentially more features in the future. 42 | (#136) 43 | - Minor changes to the output format are expected, but the overall content should remain the same. 44 | If you have any feedback, please let me know. 45 | 46 | ## [v0.15.0] 47 | 48 | - Add `-trimpath` flag, which removes the path prefix from package names in the output, simplifying 49 | their display. See #128 for examples. 50 | - There's a special case for `-trimpath=auto` which will automatically determine the prefix based 51 | on the longest common prefix of all package paths. 52 | 53 | ## [v0.14.0] 54 | 55 | - Modify `--follow` behavior by minimizing noisy output. (#122) 56 | 57 | > [!TIP] 58 | > 59 | > If you want the existing behavior, I added a `--follow-verbose` flag. But please do let me know if 60 | > this affected you, as I plan to remove this before cutting a `v1.0.0`. Thank you! 61 | 62 | ## [v0.13.3] 63 | 64 | - General housekeeping and dependency updates. 65 | 66 | ## [v0.13.2] 67 | 68 | - Add partial support for `-compare`. A feature that displays the coverage difference against a 69 | previous run. See description for more details 70 | https://github.com/mfridman/tparse/pull/101#issue-1857786730 and the initial issue #92. 71 | - Fix unstable common package prefix logic #104 72 | 73 | ## [v0.13.1] - 2023-08-04 74 | 75 | - Fix failing GoReleaser GitHub action (release notes location). 76 | 77 | Summary from [v0.13.0](https://github.com/mfridman/tparse/releases/tag/v0.13.0) 78 | 79 | - Start a [CHANGELOG.md](https://github.com/mfridman/tparse/blob/main/CHANGELOG.md) for user-facing 80 | change. 81 | - Add [GoReleaser](https://goreleaser.com/) to automate the release process. Pre-built binaries are 82 | available for each release, currently Linux and macOS. If there is demand, can also add Windows. 83 | 84 | ## [v0.13.0] - 2023-08-04 85 | 86 | - Start a [CHANGELOG.md](https://github.com/mfridman/tparse/blob/main/CHANGELOG.md) for user-facing 87 | change. 88 | - Add [GoReleaser](https://goreleaser.com/) to automate the release process. Pre-built binaries are 89 | available for each release, currently Linux and macOS. If there is demand, can also add Windows. 90 | 91 | [Unreleased]: https://github.com/mfridman/tparse/compare/v0.17.0...HEAD 92 | [v0.17.0]: https://github.com/mfridman/tparse/compare/v0.16.0...v0.17.0 93 | [v0.16.0]: https://github.com/mfridman/tparse/compare/v0.15.0...v0.16.0 94 | [v0.15.0]: https://github.com/mfridman/tparse/compare/v0.14.0...v0.15.0 95 | [v0.14.0]: https://github.com/mfridman/tparse/compare/v0.13.3...v0.14.0 96 | [v0.13.3]: https://github.com/mfridman/tparse/compare/v0.13.2...v0.13.3 97 | [v0.13.2]: https://github.com/mfridman/tparse/compare/v0.13.1...v0.13.2 98 | [v0.13.1]: https://github.com/mfridman/tparse/compare/v0.13.0...v0.13.1 99 | [v0.13.0]: https://github.com/mfridman/tparse/releases/tag/v0.13.0 100 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Michael Fridman 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ROOT := github.com/mfridman/tparse 2 | GOPATH ?= $(shell go env GOPATH) 3 | TOOLS_BIN = $(GOPATH)/bin 4 | 5 | .PHONY: vet 6 | vet: 7 | @go vet ./... 8 | 9 | .PHONY: lint 10 | lint: tools 11 | @golangci-lint run ./... --fix 12 | 13 | .PHONY: tools 14 | tools: 15 | @which golangci-lint >/dev/null 2>&1 || \ 16 | (echo "Installing latest golangci-lint" && \ 17 | curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | \ 18 | sh -s -- -b "$(TOOLS_BIN)") 19 | 20 | .PHONY: tools-update 21 | tools-update: 22 | @echo "Updating golangci-lint to latest version" 23 | @rm -f "$(TOOLS_BIN)/golangci-lint" 24 | @curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | \ 25 | sh -s -- -b "$(TOOLS_BIN)" 26 | @echo "golangci-lint updated successfully to latest version" 27 | 28 | .PHONY: tools-version 29 | tools-version: 30 | @echo "Current tool versions:" 31 | @echo "golangci-lint: $$(golangci-lint --version 2>/dev/null || echo 'not installed')" 32 | 33 | .PHONY: release 34 | release: 35 | @goreleaser --rm-dist 36 | 37 | .PHONY: build 38 | build: 39 | @go build -o $$GOBIN/tparse ./ 40 | 41 | .PHONY: clean 42 | clean: 43 | @find . -type f -name '*.FAIL' -delete 44 | 45 | .PHONY: test 46 | test: 47 | @go test -count=1 ./... 48 | 49 | test-tparse: 50 | @go test -race -count=1 ./internal/... -json -cover | go run main.go -trimpath=auto -sort=elapsed 51 | @go test -race -count=1 ./tests/... -json -cover -coverpkg=./parse | go run main.go -trimpath=github.com/mfridman/tparse/ -sort=elapsed 52 | 53 | # dogfooding :) 54 | test-tparse-full: 55 | go test -race -count=1 -v ./... -json | go run main.go -all -smallscreen -notests -sort=elapsed 56 | 57 | coverage: 58 | go test ./tests/... -coverpkg=./parse -covermode=count -coverprofile=count.out 59 | go tool cover -html=count.out 60 | 61 | search-todo: 62 | @echo "Searching for TODOs in Go files..." 63 | @rg '// TODO\(mf\):' --glob '*.go' || echo "No TODOs found." 64 | 65 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tparse [![Actions](https://github.com/mfridman/tparse/workflows/CI/badge.svg)](https://github.com/mfridman/tparse) 2 | 3 | A command line tool for analyzing and summarizing `go test` output. 4 | 5 | > [!TIP] 6 | > 7 | > Don't forget to run `go test` with the `-json` flag. 8 | 9 | Pass | Fail 10 | :-------------------------:|:-------------------------: 11 | | 12 | 13 | By default, `tparse` will always return test failures and panics, if any, followed by a package-level summary table. 14 | 15 | To get additional info on passed tests run `tparse` with `-pass` flag. Tests are grouped by package and sorted by elapsed time in descending order (longest to shortest). 16 | 17 | ### [But why?!](#but-why) for more info. 18 | 19 | ## Installation 20 | 21 | go install github.com/mfridman/tparse@latest 22 | 23 | Or download the latest pre-built binary [here](https://github.com/mfridman/tparse/releases/latest). 24 | 25 | ## Usage 26 | 27 | Once `tparse` is installed there are 2 ways to use it: 28 | 29 | 1. Run `go test` as normal, but add `-json` flag and pipe output to `tparse`. 30 | 31 | ``` 32 | set -o pipefail && go test fmt -json | tparse -all 33 | ``` 34 | 35 | 2. Save the output of `go test` with `-json` flag into a file and call `tparse` with `-file` option. 36 | 37 | ``` 38 | go test fmt -json > fmt.out 39 | tparse -all -file=fmt.out 40 | ``` 41 | 42 | Tip: run `tparse -h` to get usage and options. 43 | 44 | ## But why?! 45 | 46 | `go test` is awesome, but verbose. Sometimes you just want readily available failures, grouped by package, printed with a dash of color. 47 | 48 | `tparse` attempts to do just that; return failed tests and panics, if any, followed by a single package-level summary. No more searching for the literal string: "--- FAIL". 49 | 50 | But, let's take it a bit further. With `-all` (`-pass` and `-skip` combined) you can get additional info, such as skipped tests and elapsed time of each passed test. 51 | 52 | `tparse` comes with a `-follow` flag to print raw output. Yep, go test pipes JSON, it's parsed and the output is printed back out as if you ran go test without `-json` flag. Eliminating the need for `tee /dev/tty` between pipes. 53 | 54 | The default print order is: 55 | - `go test` output (if adding `-follow` flag) 56 | - passed/skipped table (if adding `-all`, `-skip` or `-pass` flag) 57 | - failed tests and panics 58 | - summary 59 | 60 | For narrow displays the `-smallscreen` flag may be useful, dividing a long test name and making it vertical heavy: 61 | 62 | ``` 63 | TestSubtests/an_awesome_but_long/subtest_for_the/win 64 | 65 | TestSubtests 66 | /an_awesome_but_long 67 | /subtest_for_the 68 | /win 69 | ``` 70 | 71 | `tparse` aims to be a simple alternative to one-liner bash functions. 72 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/mfridman/tparse 2 | 3 | go 1.23.0 4 | 5 | toolchain go1.24.2 6 | 7 | require ( 8 | github.com/charmbracelet/lipgloss v1.1.0 9 | github.com/muesli/termenv v0.16.0 10 | github.com/stretchr/testify v1.9.0 11 | ) 12 | 13 | require ( 14 | github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect 15 | github.com/charmbracelet/colorprofile v0.3.1 // indirect 16 | github.com/charmbracelet/x/ansi v0.9.2 // indirect 17 | github.com/charmbracelet/x/cellbuf v0.0.13 // indirect 18 | github.com/charmbracelet/x/term v0.2.1 // indirect 19 | github.com/davecgh/go-spew v1.1.1 // indirect 20 | github.com/lucasb-eyer/go-colorful v1.2.0 // indirect 21 | github.com/mattn/go-isatty v0.0.20 // indirect 22 | github.com/mattn/go-runewidth v0.0.16 // indirect 23 | github.com/pmezard/go-difflib v1.0.0 // indirect 24 | github.com/rivo/uniseg v0.4.7 // indirect 25 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect 26 | golang.org/x/sys v0.33.0 // indirect 27 | gopkg.in/yaml.v3 v3.0.1 // indirect 28 | ) 29 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= 2 | github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= 3 | github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWpi6yML8= 4 | github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA= 5 | github.com/charmbracelet/colorprofile v0.3.1 h1:k8dTHMd7fgw4bnFd7jXTLZrSU/CQrKnL3m+AxCzDz40= 6 | github.com/charmbracelet/colorprofile v0.3.1/go.mod h1:/GkGusxNs8VB/RSOh3fu0TJmQ4ICMMPApIIVn0KszZ0= 7 | github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY= 8 | github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30= 9 | github.com/charmbracelet/x/ansi v0.9.2 h1:92AGsQmNTRMzuzHEYfCdjQeUzTrgE1vfO5/7fEVoXdY= 10 | github.com/charmbracelet/x/ansi v0.9.2/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE= 11 | github.com/charmbracelet/x/cellbuf v0.0.13 h1:/KBBKHuVRbq1lYx5BzEHBAFBP8VcQzJejZ/IA3iR28k= 12 | github.com/charmbracelet/x/cellbuf v0.0.13/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs= 13 | github.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a h1:G99klV19u0QnhiizODirwVksQB91TJKV/UaTnACcG30= 14 | github.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U= 15 | github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ= 16 | github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg= 17 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 18 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 19 | github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= 20 | github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= 21 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 22 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 23 | github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= 24 | github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= 25 | github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= 26 | github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= 27 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 28 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 29 | github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 30 | github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= 31 | github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= 32 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 33 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 34 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= 35 | github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= 36 | golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 h1:MDc5xs78ZrZr3HMQugiXOAkSZtfTpbJLDr/lwfgO53E= 37 | golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= 38 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 39 | golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= 40 | golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 41 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 42 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 43 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 44 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 45 | -------------------------------------------------------------------------------- /internal/app/app.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "io" 7 | "os" 8 | 9 | "github.com/mfridman/tparse/parse" 10 | ) 11 | 12 | type Options struct { 13 | // Output is used to write the final output, such as the tables, summary, etc. 14 | Output io.Writer 15 | // DisableColor will disable all colors. 16 | DisableColor bool 17 | // Format will set the output format for tables. 18 | Format OutputFormat 19 | // Sorter will set the sort order for the table. 20 | Sorter parse.PackageSorter 21 | // ShowNoTests will display packages containing no test files or empty test files. 22 | ShowNoTests bool 23 | // FileName will read test output from a file. 24 | FileName string 25 | 26 | // Test table options 27 | TestTableOptions TestTableOptions 28 | SummaryTableOptions SummaryTableOptions 29 | 30 | // FollowOutput will follow the raw output as go test is running. 31 | FollowOutput bool // Output to stdout 32 | FollowOutputWriter io.WriteCloser // Output to a file, takes precedence over FollowOutput 33 | FollowOutputVerbose bool 34 | 35 | // Progress will print a single summary line for each package once the package has completed. 36 | // Useful for long running test suites. Maybe used with FollowOutput or on its own. 37 | // 38 | // This will output to stdout. 39 | Progress bool 40 | ProgressOutput io.Writer 41 | 42 | // DisableTableOutput will disable all table output. This is used for testing. 43 | DisableTableOutput bool 44 | 45 | // 46 | // Experimental 47 | // 48 | 49 | // Compare includes a diff of a previous test output file in the summary table. 50 | Compare string 51 | } 52 | 53 | func Run(option Options) (int, error) { 54 | var reader io.ReadCloser 55 | var err error 56 | if option.FileName != "" { 57 | if reader, err = os.Open(option.FileName); err != nil { 58 | return 1, err 59 | } 60 | } else { 61 | if reader, err = newPipeReader(); err != nil { 62 | return 1, errors.New("stdin must be a pipe, or use -file to open a go test output file") 63 | } 64 | } 65 | defer reader.Close() 66 | 67 | if option.FollowOutputWriter != nil { 68 | defer option.FollowOutputWriter.Close() 69 | } 70 | 71 | summary, err := parse.Process( 72 | reader, 73 | parse.WithFollowOutput(option.FollowOutput), 74 | parse.WithFollowVersboseOutput(option.FollowOutputVerbose), 75 | parse.WithWriter(option.FollowOutputWriter), 76 | parse.WithProgress(option.Progress), 77 | parse.WithProgressOutput(option.ProgressOutput), 78 | ) 79 | if err != nil { 80 | return 1, err 81 | } 82 | if len(summary.Packages) == 0 { 83 | return 1, fmt.Errorf("found no go test packages") 84 | } 85 | // Useful for tests that don't need tparse table output. Very useful for testing output from 86 | // [parse.Process] 87 | if !option.DisableTableOutput { 88 | display(option.Output, summary, option) 89 | } 90 | return summary.ExitCode(), nil 91 | } 92 | 93 | func newPipeReader() (io.ReadCloser, error) { 94 | finfo, err := os.Stdin.Stat() 95 | if err != nil { 96 | return nil, err 97 | } 98 | // Check file mode bits to test for named pipe as stdin. 99 | if finfo.Mode()&os.ModeNamedPipe != 0 { 100 | return os.Stdin, nil 101 | } 102 | return nil, errors.New("stdin must be a pipe") 103 | } 104 | 105 | func display(w io.Writer, summary *parse.GoTestSummary, option Options) { 106 | // Best effort to open the compare against file, if it exists. 107 | var warnings []string 108 | defer func() { 109 | for _, w := range warnings { 110 | fmt.Fprintf(os.Stderr, "warning: %s\n", w) 111 | } 112 | }() 113 | var against *parse.GoTestSummary 114 | if option.Compare != "" { 115 | // TODO(mf): cleanup, this is messy. 116 | f, err := os.Open(option.Compare) 117 | if err != nil { 118 | warnings = append(warnings, fmt.Sprintf("failed to open against file: %s", option.Compare)) 119 | } else { 120 | defer f.Close() 121 | against, err = parse.Process(f) 122 | if err != nil { 123 | warnings = append(warnings, fmt.Sprintf("failed to parse against file: %s", option.Compare)) 124 | } 125 | } 126 | } 127 | 128 | cw := newConsoleWriter(w, option.Format, option.DisableColor) 129 | // Sort packages by name ASC. 130 | packages := summary.GetSortedPackages(option.Sorter) 131 | // Only print the tests table if either pass or skip is true. 132 | if option.TestTableOptions.Pass || option.TestTableOptions.Skip { 133 | if option.Format == OutputFormatMarkdown { 134 | cw.testsTableMarkdown(packages, option.TestTableOptions) 135 | } else { 136 | cw.testsTable(packages, option.TestTableOptions) 137 | } 138 | } 139 | // Failures (if any) and summary table are always printed. 140 | cw.printFailed(packages) 141 | cw.summaryTable(packages, option.ShowNoTests, option.SummaryTableOptions, against) 142 | } 143 | -------------------------------------------------------------------------------- /internal/app/console_writer.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "io" 5 | 6 | "github.com/charmbracelet/lipgloss" 7 | "github.com/muesli/termenv" 8 | ) 9 | 10 | type OutputFormat int 11 | 12 | const ( 13 | // OutputFormatBasic is a normal table without a border 14 | OutputFormatPlain OutputFormat = iota + 1 15 | // OutputFormatBasic is a normal table with border 16 | OutputFormatBasic 17 | // OutputFormatBasic is a markdown-rendered table 18 | OutputFormatMarkdown 19 | ) 20 | 21 | type consoleWriter struct { 22 | format OutputFormat 23 | w io.Writer 24 | 25 | red colorOptionFunc 26 | green colorOptionFunc 27 | yellow colorOptionFunc 28 | } 29 | 30 | type colorOptionFunc func(s string) string 31 | 32 | // newColor is a helper function to set the base color. 33 | func newColor(color lipgloss.TerminalColor) colorOptionFunc { 34 | return func(text string) string { 35 | return lipgloss.NewStyle().Foreground(color).Render(text) 36 | } 37 | } 38 | 39 | // newMarkdownColor is a helper function to set the base color for markdown. 40 | func newMarkdownColor(s string) colorOptionFunc { 41 | return func(text string) string { 42 | return s + " " + text 43 | } 44 | } 45 | 46 | func noColor() colorOptionFunc { 47 | return func(text string) string { return text } 48 | } 49 | 50 | func newConsoleWriter(w io.Writer, format OutputFormat, disableColor bool) *consoleWriter { 51 | if format == 0 { 52 | format = OutputFormatBasic 53 | } 54 | cw := &consoleWriter{ 55 | w: w, 56 | format: format, 57 | } 58 | cw.red = noColor() 59 | cw.green = noColor() 60 | cw.yellow = noColor() 61 | 62 | if !disableColor { 63 | // NOTE(mf): GitHub Actions CI env (and probably others) do not have an 64 | // interactive TTY, and tparse through termenv will degrade to the 65 | // "best available option" .. which is no colors. We can work around this by 66 | // setting a color profile explicitly instead of relying on termenv to auto-detect. 67 | // Ref: https://github.com/charmbracelet/lipgloss/issues/74 68 | // Ref: https://github.com/mfridman/tparse/issues/76 69 | lipgloss.SetColorProfile(termenv.TrueColor) 70 | 71 | switch format { 72 | case OutputFormatMarkdown: 73 | cw.green = newMarkdownColor("🟢") 74 | cw.yellow = newMarkdownColor("🟡") 75 | cw.red = newMarkdownColor("🔴") 76 | default: 77 | cw.green = newColor(lipgloss.Color("10")) 78 | cw.yellow = newColor(lipgloss.Color("11")) 79 | cw.red = newColor(lipgloss.Color("9")) 80 | } 81 | } 82 | return cw 83 | } 84 | -------------------------------------------------------------------------------- /internal/app/table.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "github.com/charmbracelet/lipgloss" 5 | "github.com/charmbracelet/lipgloss/table" 6 | ) 7 | 8 | func newTable( 9 | format OutputFormat, 10 | override func(style lipgloss.Style, row, col int) lipgloss.Style, 11 | ) *table.Table { 12 | tbl := table.New() 13 | switch format { 14 | case OutputFormatPlain: 15 | tbl.Border(lipgloss.HiddenBorder()).BorderTop(false).BorderBottom(false) 16 | case OutputFormatMarkdown: 17 | tbl.Border(markdownBorder).BorderBottom(false).BorderTop(false) 18 | case OutputFormatBasic: 19 | tbl.Border(lipgloss.RoundedBorder()) 20 | } 21 | return tbl.StyleFunc(func(row, col int) lipgloss.Style { 22 | // Default style, may be overridden. 23 | style := lipgloss.NewStyle().PaddingLeft(1).PaddingRight(1).Align(lipgloss.Center) 24 | if override != nil { 25 | style = override(style, row, col) 26 | } 27 | return style 28 | }) 29 | } 30 | 31 | var markdownBorder = lipgloss.Border{ 32 | Top: "-", 33 | Bottom: "-", 34 | Left: "|", 35 | Right: "|", 36 | TopLeft: "", // empty for markdown 37 | TopRight: "", // empty for markdown 38 | BottomLeft: "", // empty for markdown 39 | BottomRight: "", // empty for markdown 40 | MiddleLeft: "|", 41 | MiddleRight: "|", 42 | Middle: "|", 43 | MiddleTop: "|", 44 | MiddleBottom: "|", 45 | } 46 | -------------------------------------------------------------------------------- /internal/app/table_failed.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "fmt" 5 | "sort" 6 | "strings" 7 | 8 | "github.com/charmbracelet/lipgloss" 9 | 10 | "github.com/mfridman/tparse/parse" 11 | ) 12 | 13 | // printFailed prints all failed tests, grouping them by package. Packages are sorted. 14 | // Panic is an exception. 15 | func (c *consoleWriter) printFailed(packages []*parse.Package) { 16 | for _, pkg := range packages { 17 | if pkg.HasPanic { 18 | // TODO(mf): document why panics are handled separately. A panic may or may 19 | // not be associated with tests, so we print it at the package level. 20 | output := c.prepareStyledPanic(pkg.Summary.Package, pkg.Summary.Test, pkg.PanicEvents) 21 | fmt.Fprintln(c.w, output) 22 | continue 23 | } 24 | failedTests := pkg.TestsByAction(parse.ActionFail) 25 | if len(failedTests) == 0 { 26 | continue 27 | } 28 | styledPackageHeader := c.styledHeader( 29 | pkg.Summary.Action.String(), 30 | pkg.Summary.Package, 31 | ) 32 | fmt.Fprintln(c.w, styledPackageHeader) 33 | fmt.Fprintln(c.w) 34 | /* 35 | Failed tests are all the individual tests, where the subtests are not separated. 36 | 37 | We need to sort the tests by name to ensure they are grouped together 38 | */ 39 | sort.Slice(failedTests, func(i, j int) bool { 40 | return failedTests[i].Name < failedTests[j].Name 41 | }) 42 | 43 | divider := lipgloss.NewStyle(). 44 | BorderStyle(lipgloss.NormalBorder()). 45 | BorderTop(true). 46 | Faint(c.format != OutputFormatMarkdown). 47 | Width(96) 48 | 49 | /* 50 | Note, some output such as the "--- FAIL: " line is prefixed 51 | with spaces. Unfortunately when dumping this in markdown format 52 | it renders as an code block. 53 | 54 | "To produce a code block in Markdown, simply indent every line of the 55 | block by at least 4 spaces or 1 tab." 56 | Ref. https://daringfireball.net/projects/markdown/syntax 57 | 58 | Example: 59 | --- FAIL: Test (0.05s) 60 | --- FAIL: Test/test_01 (0.01s) 61 | --- FAIL: Test/test_01/sort (0.00s) 62 | 63 | This is why we wrap the entire test output in a code block. 64 | */ 65 | 66 | if c.format == OutputFormatMarkdown { 67 | fmt.Fprintln(c.w, fencedCodeBlock) 68 | } 69 | var key string 70 | for i, t := range failedTests { 71 | // Add top divider to all tests except first one. 72 | base, _, _ := cut(t.Name, "/") 73 | if i > 0 && key != base { 74 | fmt.Fprintln(c.w, divider.String()) 75 | } 76 | key = base 77 | fmt.Fprintln(c.w, c.prepareStyledTest(t)) 78 | } 79 | if c.format == OutputFormatMarkdown { 80 | fmt.Fprint(c.w, fencedCodeBlock+"\n\n") 81 | } 82 | } 83 | } 84 | 85 | const ( 86 | fencedCodeBlock string = "```" 87 | ) 88 | 89 | // copied directly from strings.Cut (go1.18) to support older Go versions. 90 | // In the future, replace this with the upstream function. 91 | func cut(s, sep string) (before, after string, found bool) { 92 | if i := strings.Index(s, sep); i >= 0 { 93 | return s[:i], s[i+len(sep):], true 94 | } 95 | return s, "", false 96 | } 97 | 98 | func (c *consoleWriter) prepareStyledPanic( 99 | packageName string, 100 | testName string, 101 | panicEvents []*parse.Event, 102 | ) string { 103 | if testName != "" { 104 | packageName = packageName + " • " + testName 105 | } 106 | styledPackageHeader := c.styledHeader("PANIC", packageName) 107 | // TODO(mf): can we pass this panic stack to another package and either by default, 108 | // or optionally, build human-readable panic output with: 109 | // https://github.com/maruel/panicparse 110 | var rows strings.Builder 111 | for _, e := range panicEvents { 112 | if e.Output == "" { 113 | continue 114 | } 115 | rows.WriteString(e.Output) 116 | } 117 | return lipgloss.JoinVertical(lipgloss.Left, styledPackageHeader, rows.String()) 118 | } 119 | 120 | func (c *consoleWriter) styledHeader(status, packageName string) string { 121 | status = c.red(strings.ToUpper(status)) 122 | packageName = strings.TrimSpace(packageName) 123 | 124 | if c.format == OutputFormatMarkdown { 125 | msg := fmt.Sprintf("## %s • %s", status, packageName) 126 | return msg 127 | // TODO(mf): an alternative implementation is to add 2 horizontal lines above and below 128 | // the package header output. 129 | // 130 | // var divider string 131 | // for i := 0; i < len(msg); i++ { 132 | // divider += "─" 133 | // } 134 | // return fmt.Sprintf("%s\n%s\n%s", divider, msg, divider) 135 | } 136 | /* 137 | Need to rethink how to best support multiple output formats across 138 | CI, local terminal development and markdown 139 | 140 | See https://github.com/mfridman/tparse/issues/71 141 | */ 142 | headerStyle := lipgloss.NewStyle(). 143 | BorderStyle(lipgloss.ThickBorder()). 144 | BorderForeground(lipgloss.Color("103")) 145 | statusStyle := lipgloss.NewStyle(). 146 | PaddingLeft(3). 147 | PaddingRight(2). 148 | Foreground(lipgloss.Color("9")) 149 | packageNameStyle := lipgloss.NewStyle(). 150 | PaddingRight(3) 151 | headerRow := lipgloss.JoinHorizontal( 152 | lipgloss.Left, 153 | statusStyle.Render(status), 154 | packageNameStyle.Render("package: "+packageName), 155 | ) 156 | return headerStyle.Render(headerRow) 157 | } 158 | 159 | const ( 160 | failLine = "--- FAIL: " 161 | ) 162 | 163 | func (c *consoleWriter) prepareStyledTest(t *parse.Test) string { 164 | t.SortEvents() 165 | 166 | var rows, headerRows strings.Builder 167 | for _, e := range t.Events { 168 | // Only add events that have output information. Skip everything else. 169 | // Note, since we know about all the output, we can bubble "--- Fail" to the top 170 | // of the output so it's trivial to spot the failing test name and elapsed time. 171 | if e.Action != parse.ActionOutput { 172 | continue 173 | } 174 | if strings.Contains(e.Output, failLine) { 175 | header := strings.TrimSuffix(e.Output, "\n") 176 | // go test prefixes too much padding to the "--- FAIL: " output lines. 177 | // Let's cut the padding by half, being careful to preserve the fail 178 | // line and the proceeding output. 179 | before, after, ok := cut(header, failLine) 180 | var pad string 181 | if ok { 182 | var n int 183 | for _, r := range before { 184 | if r == 32 { 185 | n++ 186 | } 187 | } 188 | for i := 0; i < n/2; i++ { 189 | pad += " " 190 | } 191 | } 192 | header = pad + failLine + after 193 | 194 | // Avoid colorizing markdown output so it renders properly, otherwise add a subtle 195 | // red color to the test headers. 196 | if c.format != OutputFormatMarkdown { 197 | header = lipgloss.NewStyle().Foreground(lipgloss.Color("1")).Render(header) 198 | } 199 | headerRows.WriteString(header) 200 | continue 201 | } 202 | 203 | if e.Output != "" { 204 | rows.WriteString(e.Output) 205 | } 206 | } 207 | out := headerRows.String() 208 | if rows.Len() > 0 { 209 | out += "\n\n" + rows.String() 210 | } 211 | return out 212 | } 213 | -------------------------------------------------------------------------------- /internal/app/table_summary.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "fmt" 5 | "path" 6 | "strconv" 7 | "strings" 8 | 9 | "github.com/charmbracelet/lipgloss" 10 | "github.com/charmbracelet/lipgloss/table" 11 | 12 | "github.com/mfridman/tparse/internal/utils" 13 | "github.com/mfridman/tparse/parse" 14 | ) 15 | 16 | type SummaryTableOptions struct { 17 | // For narrow screens, remove common prefix and trim long package names vertically. Example: 18 | // github.com/mfridman/tparse/app 19 | // github.com/mfridman/tparse/internal/seed-up-down-to-zero 20 | // 21 | // tparse/app 22 | // tparse 23 | // /seed-up-down-to-zero 24 | Trim bool 25 | 26 | // TrimPath is the path prefix to trim from the package name. 27 | TrimPath string 28 | } 29 | 30 | func (c *consoleWriter) summaryTable( 31 | packages []*parse.Package, 32 | showNoTests bool, 33 | options SummaryTableOptions, 34 | against *parse.GoTestSummary, 35 | ) { 36 | tbl := newTable(c.format, func(style lipgloss.Style, row, col int) lipgloss.Style { 37 | switch row { 38 | case table.HeaderRow: 39 | default: 40 | if col == 2 { 41 | // Package name 42 | style = style.Align(lipgloss.Left) 43 | } 44 | } 45 | return style 46 | }) 47 | header := summaryRow{ 48 | status: "Status", 49 | elapsed: "Elapsed", 50 | packageName: "Package", 51 | cover: "Cover", 52 | pass: "Pass", 53 | fail: "Fail", 54 | skip: "Skip", 55 | } 56 | tbl.Headers(header.toRow()...) 57 | data := table.NewStringData() 58 | 59 | // Capture as separate slices because notests are optional when passed tests are available. 60 | // The only exception is if passed=0 and notests=1, then we display them regardless. This 61 | // is almost always the user matching on the wrong package. 62 | var passed, notests []summaryRow 63 | 64 | names := make([]string, 0, len(packages)) 65 | for _, pkg := range packages { 66 | names = append(names, pkg.Summary.Package) 67 | } 68 | packagePrefix := utils.FindLongestCommonPrefix(names) 69 | 70 | for _, pkg := range packages { 71 | elapsed := strconv.FormatFloat(pkg.Summary.Elapsed, 'f', 2, 64) + "s" 72 | if pkg.Cached { 73 | elapsed = "(cached)" 74 | } 75 | packageName := pkg.Summary.Package 76 | packageName = shortenPackageName(packageName, packagePrefix, 32, options.Trim, options.TrimPath) 77 | if pkg.HasPanic { 78 | row := summaryRow{ 79 | status: c.red("PANIC"), 80 | elapsed: elapsed, 81 | packageName: packageName, 82 | cover: "--", pass: "--", fail: "--", skip: "--", 83 | } 84 | data.Append(row.toRow()) 85 | continue 86 | } 87 | if pkg.HasFailedBuildOrSetup { 88 | row := summaryRow{ 89 | status: c.red("FAIL"), 90 | elapsed: elapsed, 91 | packageName: packageName + "\n[" + pkg.Summary.Output + "]", 92 | cover: "--", pass: "--", fail: "--", skip: "--", 93 | } 94 | data.Append(row.toRow()) 95 | continue 96 | } 97 | if pkg.NoTestFiles { 98 | row := summaryRow{ 99 | status: c.yellow("NOTEST"), 100 | elapsed: elapsed, 101 | packageName: packageName + "\n[no test files]", 102 | cover: "--", pass: "--", fail: "--", skip: "--", 103 | } 104 | notests = append(notests, row) 105 | continue 106 | } 107 | if pkg.NoTests { 108 | // This should capture cases where packages truly have no tests, but empty files. 109 | if len(pkg.NoTestSlice) == 0 { 110 | row := summaryRow{ 111 | status: c.yellow("NOTEST"), 112 | elapsed: elapsed, 113 | packageName: packageName + "\n[no tests to run]", 114 | cover: "--", pass: "--", fail: "--", skip: "--", 115 | } 116 | notests = append(notests, row) 117 | continue 118 | } 119 | // This should capture cases where packages have a mixture of empty and non-empty test files. 120 | var ss []string 121 | for i, t := range pkg.NoTestSlice { 122 | i++ 123 | ss = append(ss, fmt.Sprintf("%d.%s", i, t.Test)) 124 | } 125 | packageName := fmt.Sprintf("%s\n[no tests to run]\n%s", packageName, strings.Join(ss, "\n")) 126 | row := summaryRow{ 127 | status: c.yellow("NOTEST"), 128 | elapsed: elapsed, 129 | packageName: packageName, 130 | cover: "--", pass: "--", fail: "--", skip: "--", 131 | } 132 | notests = append(notests, row) 133 | 134 | if len(pkg.TestsByAction(parse.ActionPass)) == len(pkg.NoTestSlice) { 135 | continue 136 | } 137 | } 138 | // TODO(mf): refactor this 139 | // Separate cover colorization from the delta output. 140 | coverage := "--" 141 | if pkg.Cover { 142 | coverage = fmt.Sprintf("%.1f%%", pkg.Coverage) 143 | if against != nil { 144 | againstP, ok := against.Packages[pkg.Summary.Package] 145 | if ok { 146 | var sign string 147 | if pkg.Coverage > againstP.Coverage { 148 | sign = "+" 149 | } 150 | coverage = fmt.Sprintf("%s (%s)", coverage, sign+strconv.FormatFloat(pkg.Coverage-againstP.Coverage, 'f', 1, 64)+"%") 151 | } else { 152 | coverage = fmt.Sprintf("%s (-)", coverage) 153 | } 154 | } 155 | // Showing coverage for a package that failed is a bit odd. 156 | // 157 | // Only colorize the coverage when everything passed AND the output is not markdown. 158 | if pkg.Summary.Action == parse.ActionPass && c.format != OutputFormatMarkdown { 159 | switch cover := pkg.Coverage; { 160 | case cover > 0.0 && cover <= 50.0: 161 | coverage = c.red(coverage) 162 | case pkg.Coverage > 50.0 && pkg.Coverage < 80.0: 163 | coverage = c.yellow(coverage) 164 | case pkg.Coverage >= 80.0: 165 | coverage = c.green(coverage) 166 | } 167 | } 168 | } 169 | 170 | status := strings.ToUpper(pkg.Summary.Action.String()) 171 | switch pkg.Summary.Action { 172 | case parse.ActionPass: 173 | status = c.green(status) 174 | case parse.ActionSkip: 175 | status = c.yellow(status) 176 | case parse.ActionFail: 177 | status = c.red(status) 178 | } 179 | row := summaryRow{ 180 | status: status, 181 | elapsed: elapsed, 182 | packageName: packageName, 183 | cover: coverage, 184 | pass: strconv.Itoa(len(pkg.TestsByAction(parse.ActionPass))), 185 | fail: strconv.Itoa(len(pkg.TestsByAction(parse.ActionFail))), 186 | skip: strconv.Itoa(len(pkg.TestsByAction(parse.ActionSkip))), 187 | } 188 | passed = append(passed, row) 189 | } 190 | 191 | if data.Rows() == 0 && len(passed) == 0 && len(notests) == 0 { 192 | return 193 | } 194 | for _, r := range passed { 195 | data.Append(r.toRow()) 196 | } 197 | 198 | // Only display the "no tests to run" cases if users want to see them when passed 199 | // tests are available. 200 | // An exception is made if there are no passed tests and only a single no test files 201 | // package. This is almost always because the user forgot to match one or more packages. 202 | if showNoTests || (len(passed) == 0 && len(notests) == 1) { 203 | for _, r := range notests { 204 | data.Append(r.toRow()) 205 | } 206 | } 207 | 208 | fmt.Fprintln(c.w, tbl.Data(data).Render()) 209 | } 210 | 211 | type summaryRow struct { 212 | status string 213 | elapsed string 214 | packageName string 215 | cover string 216 | pass string 217 | fail string 218 | skip string 219 | } 220 | 221 | func (r summaryRow) toRow() []string { 222 | return []string{ 223 | r.status, 224 | r.elapsed, 225 | r.packageName, 226 | r.cover, 227 | r.pass, 228 | r.fail, 229 | r.skip, 230 | } 231 | } 232 | 233 | func shortenPackageName( 234 | name string, 235 | prefix string, 236 | maxLength int, 237 | trim bool, 238 | trimPath string, 239 | ) string { 240 | if trimPath == "auto" { 241 | name = strings.TrimPrefix(name, prefix) 242 | } else if trimPath != "" { 243 | name = strings.TrimPrefix(name, trimPath) 244 | } 245 | if !trim { 246 | return name 247 | } 248 | 249 | if prefix == "" { 250 | dir, name := path.Split(name) 251 | // For SIV-style imports show the last non-versioned path identifier. 252 | // Example: github.com/foo/bar/helper/v3 returns helper/v3 253 | if dir != "" && versionMajorRe.MatchString(name) { 254 | _, subpath := path.Split(path.Clean(dir)) 255 | name = path.Join(subpath, name) 256 | } 257 | return name 258 | } 259 | 260 | name = strings.TrimPrefix(name, prefix) 261 | name = strings.TrimLeft(name, "/") 262 | name = shortenTestName(name, true, maxLength) 263 | 264 | return name 265 | } 266 | -------------------------------------------------------------------------------- /internal/app/table_tests.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "fmt" 5 | "regexp" 6 | "sort" 7 | "strconv" 8 | "strings" 9 | 10 | "github.com/charmbracelet/lipgloss" 11 | "github.com/charmbracelet/lipgloss/table" 12 | 13 | "github.com/mfridman/tparse/internal/utils" 14 | "github.com/mfridman/tparse/parse" 15 | ) 16 | 17 | var ( 18 | versionMajorRe = regexp.MustCompile(`(?m)v[0-9]+`) 19 | ) 20 | 21 | type TestTableOptions struct { 22 | // Display passed or skipped tests. If both are true this is equivalent to all. 23 | Pass, Skip bool 24 | // For narrow screens, trim long test identifiers vertically. Example: 25 | // TestNoVersioning/seed-up-down-to-zero 26 | // 27 | // TestNoVersioning 28 | // /seed-up-down-to-zero 29 | Trim bool 30 | 31 | // TrimPath is the path prefix to trim from the package name. 32 | TrimPath string 33 | 34 | // Display up to N slow tests for each package, tests are sorted by 35 | // calculated the elapsed time for the given test. 36 | Slow int 37 | } 38 | 39 | type packageTests struct { 40 | skippedCount int 41 | skipped []*parse.Test 42 | passedCount int 43 | passed []*parse.Test 44 | } 45 | 46 | func (c *consoleWriter) testsTable(packages []*parse.Package, option TestTableOptions) { 47 | // Print passed tests, sorted by elapsed DESC. Grouped by alphabetically sorted packages. 48 | tbl := newTable(c.format, func(style lipgloss.Style, row, col int) lipgloss.Style { 49 | switch row { 50 | case table.HeaderRow: 51 | default: 52 | if col == 2 || col == 3 { 53 | // Test name and package name 54 | style = style.Align(lipgloss.Left) 55 | } 56 | } 57 | return style 58 | }) 59 | header := testRow{ 60 | status: "Status", 61 | elapsed: "Elapsed", 62 | testName: "Test", 63 | packageName: "Package", 64 | } 65 | tbl.Headers(header.toRow()...) 66 | data := table.NewStringData() 67 | 68 | names := make([]string, 0, len(packages)) 69 | for _, pkg := range packages { 70 | names = append(names, pkg.Summary.Package) 71 | } 72 | packagePrefix := utils.FindLongestCommonPrefix(names) 73 | 74 | for i, pkg := range packages { 75 | // Discard packages where we cannot generate a sensible test summary. 76 | if pkg.NoTestFiles || pkg.NoTests || pkg.HasPanic { 77 | continue 78 | } 79 | pkgTests := getTestsFromPackages(pkg, option) 80 | all := make([]*parse.Test, 0, len(pkgTests.passed)+len(pkgTests.skipped)) 81 | all = append(all, pkgTests.passed...) 82 | all = append(all, pkgTests.skipped...) 83 | 84 | for _, t := range all { 85 | // TODO(mf): why are we sorting this? 86 | t.SortEvents() 87 | 88 | testName := shortenTestName(t.Name, option.Trim, 32) 89 | 90 | status := strings.ToUpper(t.Status().String()) 91 | switch t.Status() { 92 | case parse.ActionPass: 93 | status = c.green(status) 94 | case parse.ActionSkip: 95 | status = c.yellow(status) 96 | case parse.ActionFail: 97 | status = c.red(status) 98 | } 99 | 100 | packageName := shortenPackageName(t.Package, packagePrefix, 16, option.Trim, option.TrimPath) 101 | 102 | row := testRow{ 103 | status: status, 104 | elapsed: strconv.FormatFloat(t.Elapsed(), 'f', 2, 64), 105 | testName: testName, 106 | packageName: packageName, 107 | } 108 | data.Append(row.toRow()) 109 | } 110 | if i != (len(packages) - 1) { 111 | // Add a blank row between packages. 112 | data.Append(testRow{}.toRow()) 113 | } 114 | } 115 | 116 | if data.Rows() > 0 { 117 | fmt.Fprintln(c.w, tbl.Data(data).Render()) 118 | } 119 | } 120 | 121 | func (c *consoleWriter) testsTableMarkdown(packages []*parse.Package, option TestTableOptions) { 122 | for _, pkg := range packages { 123 | // Print passed tests, sorted by elapsed DESC. Grouped by alphabetically sorted packages. 124 | tbl := newTable(c.format, func(style lipgloss.Style, row, col int) lipgloss.Style { 125 | switch row { 126 | case table.HeaderRow: 127 | default: 128 | if col == 2 { 129 | // Test name 130 | style = style.Align(lipgloss.Left) 131 | } 132 | } 133 | return style 134 | }) 135 | header := []string{ 136 | "Status", 137 | "Elapsed", 138 | "Test", 139 | } 140 | tbl.Headers(header...) 141 | data := table.NewStringData() 142 | 143 | // Discard packages where we cannot generate a sensible test summary. 144 | if pkg.NoTestFiles || pkg.NoTests || pkg.HasPanic { 145 | continue 146 | } 147 | pkgTests := getTestsFromPackages(pkg, option) 148 | all := make([]*parse.Test, 0, len(pkgTests.passed)+len(pkgTests.skipped)) 149 | all = append(all, pkgTests.passed...) 150 | all = append(all, pkgTests.skipped...) 151 | 152 | for _, t := range all { 153 | // TODO(mf): why are we sorting this? 154 | t.SortEvents() 155 | 156 | testName := shortenTestName(t.Name, option.Trim, 32) 157 | 158 | status := strings.ToUpper(t.Status().String()) 159 | switch t.Status() { 160 | case parse.ActionPass: 161 | status = c.green(status) 162 | case parse.ActionSkip: 163 | status = c.yellow(status) 164 | case parse.ActionFail: 165 | status = c.red(status) 166 | } 167 | data.Append([]string{ 168 | status, 169 | strconv.FormatFloat(t.Elapsed(), 'f', 2, 64), 170 | testName, 171 | }) 172 | } 173 | if data.Rows() > 0 { 174 | fmt.Fprintf(c.w, "## 📦 Package **`%s`**\n", pkg.Summary.Package) 175 | fmt.Fprintln(c.w) 176 | 177 | msg := fmt.Sprintf("Tests: ✓ %d passed | %d skipped\n", 178 | pkgTests.passedCount, 179 | pkgTests.skippedCount, 180 | ) 181 | if option.Slow > 0 && option.Slow < pkgTests.passedCount { 182 | msg += fmt.Sprintf("↓ Slowest %d passed tests shown (of %d)\n", 183 | option.Slow, 184 | pkgTests.passedCount, 185 | ) 186 | } 187 | fmt.Fprint(c.w, msg) 188 | 189 | fmt.Fprintln(c.w) 190 | fmt.Fprintln(c.w, "
") 191 | fmt.Fprintln(c.w) 192 | fmt.Fprintln(c.w, "Click for test summary") 193 | fmt.Fprintln(c.w) 194 | fmt.Fprintln(c.w, tbl.Data(data).Render()) 195 | fmt.Fprintln(c.w, "
") 196 | fmt.Fprintln(c.w) 197 | } 198 | fmt.Fprintln(c.w) 199 | } 200 | } 201 | 202 | func getTestsFromPackages(pkg *parse.Package, option TestTableOptions) *packageTests { 203 | tests := &packageTests{} 204 | skipped := pkg.TestsByAction(parse.ActionSkip) 205 | tests.skippedCount = len(skipped) 206 | passed := pkg.TestsByAction(parse.ActionPass) 207 | tests.passedCount = len(passed) 208 | if option.Skip { 209 | tests.skipped = append(tests.skipped, skipped...) 210 | } 211 | if option.Pass { 212 | tests.passed = append(tests.passed, passed...) 213 | // Order passed tests within a package by elapsed time DESC (longest on top). 214 | sort.Slice(tests.passed, func(i, j int) bool { 215 | return tests.passed[i].Elapsed() > tests.passed[j].Elapsed() 216 | }) 217 | // Optional, display only the slowest N tests by elapsed time. 218 | if option.Slow > 0 && len(tests.passed) > option.Slow { 219 | tests.passed = tests.passed[:option.Slow] 220 | } 221 | } 222 | return tests 223 | } 224 | 225 | func shortenTestName(s string, trim bool, maxLength int) string { 226 | var testName strings.Builder 227 | testName.WriteString(s) 228 | if trim && testName.Len() > maxLength && strings.Count(testName.String(), "/") > 0 { 229 | testName.Reset() 230 | ss := strings.Split(s, "/") 231 | testName.WriteString(ss[0] + "\n") 232 | for i, s := range ss[1:] { 233 | testName.WriteString(" /") 234 | for len(s) > maxLength { 235 | testName.WriteString(s[:maxLength-2] + " …\n ") 236 | s = s[maxLength-2:] 237 | } 238 | testName.WriteString(s) 239 | if i != len(ss[1:])-1 { 240 | testName.WriteString("\n") 241 | } 242 | } 243 | } 244 | return testName.String() 245 | } 246 | 247 | type testRow struct { 248 | status string 249 | elapsed string 250 | testName string 251 | packageName string 252 | } 253 | 254 | func (r testRow) toRow() []string { 255 | return []string{ 256 | r.status, 257 | r.elapsed, 258 | r.testName, 259 | r.packageName, 260 | } 261 | } 262 | -------------------------------------------------------------------------------- /internal/utils/utils.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "io" 5 | "sort" 6 | "strings" 7 | ) 8 | 9 | // FindLongestCommonPrefix finds the longest common path prefix of a set of paths. For example, 10 | // given the following: 11 | // 12 | // github.com/owner/repo/cmd/foo 13 | // github.com/owner/repo/cmd/bar 14 | // 15 | // The longest common prefix is: github.com/owner/repo/cmd/ (note the trailing slash is included). 16 | func FindLongestCommonPrefix(paths []string) string { 17 | if len(paths) < 2 { 18 | return "" 19 | } 20 | // Sort the paths to optimize comparison. 21 | sort.Strings(paths) 22 | 23 | first, last := paths[0], paths[len(paths)-1] 24 | if first == last { 25 | return first 26 | } 27 | 28 | // Find the common prefix between the first and last sorted paths. 29 | commonPrefixLength := 0 30 | minLength := min(len(first), len(last)) 31 | for commonPrefixLength < minLength && first[commonPrefixLength] == last[commonPrefixLength] { 32 | commonPrefixLength++ 33 | } 34 | 35 | // Ensure the common prefix ends at a boundary. 36 | commonPrefix := first[:commonPrefixLength] 37 | if n := strings.LastIndex(commonPrefix, "/"); n != -1 { 38 | return commonPrefix[:n+1] 39 | } 40 | return "" 41 | } 42 | 43 | // DiscardCloser is an io.Writer that implements io.Closer by doing nothing. 44 | // 45 | // https://github.com/golang/go/issues/22823 46 | type WriteNopCloser struct { 47 | io.Writer 48 | } 49 | 50 | func (WriteNopCloser) Close() error { 51 | return nil 52 | } 53 | -------------------------------------------------------------------------------- /internal/utils/utils_test.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import "testing" 4 | 5 | func TestFindLongestCommonPrefix(t *testing.T) { 6 | t.Parallel() 7 | 8 | tests := []struct { 9 | paths []string 10 | want string 11 | }{ 12 | { 13 | paths: []string{}, 14 | want: "", 15 | }, 16 | { 17 | paths: []string{ 18 | "github.com/user/project/pkg", 19 | }, 20 | want: "", 21 | }, 22 | { 23 | paths: []string{ 24 | "github.com/user/project/pkg", 25 | "github.com/user/project/pkg", 26 | "github.com/user/project/pkg", 27 | }, 28 | want: "github.com/user/project/pkg", 29 | }, 30 | { 31 | paths: []string{ 32 | "github.com/user/project/pkg", 33 | "github.com/user/project/cmd", 34 | }, 35 | want: "github.com/user/project/", 36 | }, 37 | { 38 | paths: []string{ 39 | "github.com/user/project/pkg", 40 | "bitbucket.org/user/project/cmd", 41 | }, 42 | want: "", 43 | }, 44 | { 45 | paths: []string{ 46 | "github.com/user/project/pkg", 47 | "github.com/user/project/cmd", 48 | "github.com/user/project/cmd/subcmd", 49 | "github.com/nonuser/project/cmd/subcmd", 50 | }, 51 | want: "github.com/", 52 | }, 53 | { 54 | paths: []string{ 55 | "github.com/foo/bar/baz/qux", 56 | "github.com/foo/bar/baz", 57 | "github.com/foo/bar/baz/qux/quux", 58 | "github.com/foo/bar/baz/qux/quux/corge", 59 | "github.com/foo/bar/baz/foo", 60 | "github.com/foo/bar/baz/foo/bar", 61 | }, 62 | want: "github.com/foo/bar/", 63 | }, 64 | { 65 | paths: []string{ 66 | "/", 67 | }, 68 | want: "", 69 | }, 70 | { 71 | paths: []string{ 72 | "/", 73 | "/", 74 | }, 75 | want: "/", 76 | }, 77 | { 78 | paths: []string{ 79 | "/abc", 80 | "/abc", 81 | }, 82 | want: "/abc", 83 | }, 84 | { 85 | paths: []string{ 86 | "foo/bar/foo", 87 | "foo/foo/foo", 88 | }, 89 | want: "foo/", 90 | }, 91 | } 92 | for _, tt := range tests { 93 | t.Run("", func(t *testing.T) { 94 | actual := FindLongestCommonPrefix(tt.paths) 95 | if actual != tt.want { 96 | t.Errorf("want %s, got %s", tt.want, actual) 97 | } 98 | }) 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "flag" 6 | "fmt" 7 | "io" 8 | "log" 9 | "os" 10 | "runtime/debug" 11 | 12 | "github.com/mfridman/tparse/internal/app" 13 | "github.com/mfridman/tparse/internal/utils" 14 | "github.com/mfridman/tparse/parse" 15 | ) 16 | 17 | // Flags. 18 | var ( 19 | vPtr = flag.Bool("v", false, "") 20 | versionPtr = flag.Bool("version", false, "") 21 | hPtr = flag.Bool("h", false, "") 22 | helpPtr = flag.Bool("help", false, "") 23 | allPtr = flag.Bool("all", false, "") 24 | passPtr = flag.Bool("pass", false, "") 25 | skipPtr = flag.Bool("skip", false, "") 26 | showNoTestsPtr = flag.Bool("notests", false, "") 27 | smallScreenPtr = flag.Bool("smallscreen", false, "") 28 | noColorPtr = flag.Bool("nocolor", false, "") 29 | slowPtr = flag.Int("slow", 0, "") 30 | fileNamePtr = flag.String("file", "", "") 31 | formatPtr = flag.String("format", "", "") 32 | followPtr = flag.Bool("follow", false, "") 33 | followOutputPtr = flag.String("follow-output", "", "") 34 | sortPtr = flag.String("sort", "name", "") 35 | progressPtr = flag.Bool("progress", false, "") 36 | comparePtr = flag.String("compare", "", "") 37 | trimPathPtr = flag.String("trimpath", "", "") 38 | // Undocumented flags 39 | followVerbosePtr = flag.Bool("follow-verbose", false, "") 40 | 41 | // Legacy flags 42 | noBordersPtr = flag.Bool("noborders", false, "") 43 | ) 44 | 45 | var usage = `Usage: 46 | go test ./... -json | tparse [options...] 47 | go test [packages...] -json | tparse [options...] 48 | go test [packages...] -json > pkgs.out ; tparse [options...] -file pkgs.out 49 | 50 | Options: 51 | -h Show help. 52 | -v Show version. 53 | -all Display table event for pass and skip. (Failed items always displayed) 54 | -pass Display table for passed tests. 55 | -skip Display table for skipped tests. 56 | -notests Display packages containing no test files or empty test files. 57 | -smallscreen Split subtest names vertically to fit on smaller screens. 58 | -slow Number of slowest tests to display. Default is 0, display all. 59 | -sort Sort table output by attribute [name, elapsed, cover]. Default is name. 60 | -nocolor Disable all colors. (NO_COLOR also supported) 61 | -format The output format for tables [basic, plain, markdown]. Default is basic. 62 | -file Read test output from a file. 63 | -follow Follow raw output from go test to stdout. 64 | -follow-output Write raw output from go test to a file (takes precedence over -follow). 65 | -progress Print a single summary line for each package. Useful for long running test suites. 66 | -compare Compare against a previous test output file. (experimental) 67 | -trimpath Remove path prefix from package names in output, simplifying their display. 68 | ` 69 | 70 | var version string 71 | 72 | func main() { 73 | log.SetFlags(0) 74 | flag.Usage = func() { 75 | fmt.Fprint(flag.CommandLine.Output(), usage) 76 | } 77 | flag.Parse() 78 | 79 | if *vPtr || *versionPtr { 80 | if info, ok := debug.ReadBuildInfo(); ok { 81 | version = info.Main.Version 82 | } 83 | fmt.Fprintf(os.Stdout, "tparse version: %s\n", version) 84 | return 85 | } 86 | if *hPtr || *helpPtr { 87 | fmt.Print(usage) 88 | return 89 | } 90 | var format app.OutputFormat 91 | switch *formatPtr { 92 | case "basic": 93 | format = app.OutputFormatBasic 94 | case "plain": 95 | format = app.OutputFormatPlain 96 | case "markdown": 97 | format = app.OutputFormatMarkdown 98 | case "": 99 | // This was an existing flag, let's try to avoid breaking users. 100 | format = app.OutputFormatBasic 101 | if *noBordersPtr { 102 | format = app.OutputFormatPlain 103 | } 104 | default: 105 | fmt.Fprintf(os.Stderr, "invalid option:%q. The -format flag must be one of: basic, plain or markdown\n", *formatPtr) 106 | return 107 | } 108 | var sorter parse.PackageSorter 109 | switch *sortPtr { 110 | case "name": 111 | sorter = parse.SortByPackageName 112 | case "elapsed": 113 | sorter = parse.SortByElapsed 114 | case "cover": 115 | sorter = parse.SortByCoverage 116 | default: 117 | fmt.Fprintf(os.Stderr, "invalid option:%q. The -sort flag must be one of: name, elapsed or cover\n", *sortPtr) 118 | return 119 | } 120 | 121 | if *allPtr { 122 | *passPtr = true 123 | *skipPtr = true 124 | } 125 | // Show colors by default. 126 | var disableColor bool 127 | if _, ok := os.LookupEnv("NO_COLOR"); ok || *noColorPtr { 128 | disableColor = true 129 | } 130 | 131 | var followOutput io.WriteCloser 132 | switch { 133 | case *followOutputPtr != "": 134 | var err error 135 | followOutput, err = os.Create(*followOutputPtr) 136 | if err != nil { 137 | fmt.Fprintln(os.Stderr, err) 138 | return 139 | } 140 | *followPtr = true 141 | case *followPtr, *followVerbosePtr: 142 | followOutput = os.Stdout 143 | default: 144 | // If no follow flags are set, we should not write to followOutput. 145 | followOutput = utils.WriteNopCloser{Writer: io.Discard} 146 | } 147 | // TODO(mf): we should marry the options with the flags to avoid having to do this. 148 | options := app.Options{ 149 | Output: os.Stdout, 150 | DisableColor: disableColor, 151 | FollowOutput: *followPtr, 152 | FollowOutputWriter: followOutput, 153 | FollowOutputVerbose: *followVerbosePtr, 154 | FileName: *fileNamePtr, 155 | TestTableOptions: app.TestTableOptions{ 156 | Pass: *passPtr, 157 | Skip: *skipPtr, 158 | Trim: *smallScreenPtr, 159 | TrimPath: *trimPathPtr, 160 | Slow: *slowPtr, 161 | }, 162 | SummaryTableOptions: app.SummaryTableOptions{ 163 | Trim: *smallScreenPtr, 164 | TrimPath: *trimPathPtr, 165 | }, 166 | Format: format, 167 | Sorter: sorter, 168 | ShowNoTests: *showNoTestsPtr, 169 | Progress: *progressPtr, 170 | ProgressOutput: os.Stdout, 171 | Compare: *comparePtr, 172 | 173 | // Do not expose publicly. 174 | DisableTableOutput: false, 175 | } 176 | exitCode, err := app.Run(options) 177 | if err != nil { 178 | msg := err.Error() 179 | if errors.Is(err, parse.ErrNotParsable) { 180 | msg = "no parsable events: Make sure to run go test with -json flag" 181 | } 182 | fmt.Fprintln(os.Stderr, msg) 183 | } 184 | os.Exit(exitCode) 185 | } 186 | -------------------------------------------------------------------------------- /parse/event.go: -------------------------------------------------------------------------------- 1 | package parse 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "regexp" 7 | "strconv" 8 | "strings" 9 | "time" 10 | ) 11 | 12 | var ( 13 | coverRe = regexp.MustCompile(`[0-9]{1,3}\.[0-9]{1}\%`) 14 | failedBuildOrSetupRe = regexp.MustCompile(`^FAIL(.*)\[(build failed|setup failed)\]`) 15 | ) 16 | 17 | // Event represents a single line of JSON output from go test with the -json flag. 18 | // 19 | // For more info see, https://golang.org/cmd/test2json and 20 | // https://github.com/golang/go/blob/master/src/cmd/internal/test2json/test2json.go 21 | type Event struct { 22 | // Action can be one of: run, pause, cont, pass, bench, fail, output, skip 23 | // 24 | // Added in go1.20: 25 | // - start 26 | // 27 | // Added in go1.24: 28 | // - build-fail 29 | // - build-output 30 | Action Action 31 | 32 | // Portion of the test's output (standard output and standard error merged together) 33 | Output string 34 | 35 | // Time at which the event occurred, encodes as an RFC3339-format string. 36 | // It is conventionally omitted for cached test results. 37 | Time time.Time 38 | 39 | // The Package field, if present, specifies the package being tested. 40 | // When the go command runs parallel tests in -json mode, events from 41 | // different tests are interlaced; the Package field allows readers to separate them. 42 | Package string 43 | 44 | // The Test field, if present, specifies the test, example, or benchmark 45 | // function that caused the event. Events for the overall package test do not set Test. 46 | Test string 47 | 48 | // Elapsed is time elapsed (in seconds) for the specific test or 49 | // the overall package test that passed or failed. 50 | Elapsed float64 51 | 52 | // FailedBuild is the package ID that is the root cause of a build failure for this test. This 53 | // will be reported in the final "fail" event's FailedBuild field. 54 | FailedBuild string 55 | 56 | // BuildEvent specific fields. 57 | // 58 | // TODO(mf): Unfortunately the output has both BuildEvent and TestEvent interleaved in the 59 | // output so for now we just combine them. But in the future pre-v1 we'll want to improve this. 60 | // 61 | // type BuildEvent struct { 62 | // ImportPath string 63 | // Action string 64 | // Output string 65 | // } 66 | ImportPath string 67 | } 68 | 69 | func (e *Event) String() string { 70 | return fmt.Sprintf( 71 | "%-6s - %s - %s elapsed[%.2f] - time[%s]\n%v", 72 | strings.ToUpper(e.Action.String()), 73 | e.Package, 74 | e.Test, 75 | e.Elapsed, 76 | e.Time.Format(time.StampMicro), 77 | e.Output, 78 | ) 79 | } 80 | 81 | // NewEvent attempts to decode data into an Event. 82 | func NewEvent(data []byte) (*Event, error) { 83 | var e Event 84 | if err := json.Unmarshal(data, &e); err != nil { 85 | return nil, err 86 | } 87 | return &e, nil 88 | } 89 | 90 | // DiscardOutput reports whether to discard output that belongs to one of 91 | // the output update actions: 92 | // === RUN 93 | // === PAUSE 94 | // === CONT 95 | // If output is none one of the above return false. 96 | func (e *Event) DiscardOutput() bool { 97 | for i := range updates { 98 | if strings.HasPrefix(e.Output, updates[i]) { 99 | return true 100 | } 101 | } 102 | return false 103 | } 104 | 105 | func (e *Event) DiscardEmptyTestOutput() bool { 106 | return e.Action == ActionOutput && e.Test == "" 107 | } 108 | 109 | // Prefixes for the different types of test updates. See: 110 | // 111 | // https://github.com/golang/go/blob/38cfb3be9d486833456276777155980d1ec0823e/src/cmd/internal/test2json/test2json.go#L160-L168 112 | const ( 113 | updatePrefixRun = "=== RUN " 114 | updatePrefixPause = "=== PAUSE " 115 | updatePrefixCont = "=== CONT " 116 | updatePrefixName = "=== NAME " 117 | updatePrefixPass = "=== PASS " 118 | updatePrefixFail = "=== FAIL " 119 | updatePrefixSkip = "=== SKIP " 120 | ) 121 | 122 | var updates = []string{ 123 | updatePrefixRun, 124 | updatePrefixPause, 125 | updatePrefixCont, 126 | } 127 | 128 | // Prefix for the different types of test results. See 129 | // 130 | // https://github.com/golang/go/blob/38cfb3be9d486833456276777155980d1ec0823e/src/cmd/internal/test2json/test2json.go#L170-L175 131 | const ( 132 | resultPrefixPass = "--- PASS: " 133 | resultPrefixFail = "--- FAIL: " 134 | resultPrefixSkip = "--- SKIP: " 135 | resultPrefixBench = "--- BENCH: " 136 | ) 137 | 138 | // BigResult reports whether the test output is a big pass or big fail 139 | // 140 | // https://github.com/golang/go/blob/38cfb3be9d486833456276777155980d1ec0823e/src/cmd/internal/test2json/test2json.go#L146-L150 141 | const ( 142 | bigPass = "PASS" 143 | bigFail = "FAIL" 144 | ) 145 | 146 | // Let's try using the LastLine method to report the package result. 147 | // If there are issues with LastLine() we can switch to this method. 148 | // 149 | // BigResult reports whether the package passed or failed. 150 | // func (e *Event) BigResult() bool { 151 | // return e.Test == "" && (e.Output == "PASS\n" || e.Output == "FAIL\n") 152 | // } 153 | 154 | // LastLine reports whether the event is the final emitted output line summarizing the package run. 155 | // 156 | // ok github.com/astromail/rover/tests 0.583s 157 | // {Time:2018-10-14 11:45:03.489687 -0400 EDT Action:pass Output: Package:github.com/astromail/rover/tests Test: Elapsed:0.584} 158 | // 159 | // FAIL github.com/astromail/rover/tests 0.534s 160 | // {Time:2018-10-14 11:45:23.916729 -0400 EDT Action:fail Output: Package:github.com/astromail/rover/tests Test: Elapsed:0.53} 161 | func (e *Event) LastLine() bool { 162 | return e.Test == "" && e.Output == "" && (e.Action == ActionPass || e.Action == ActionFail) 163 | } 164 | 165 | // NoTestFiles reports special event case for packages containing no test files: 166 | // "? \tpackage\t[no test files]\n" 167 | func (e *Event) NoTestFiles() bool { 168 | return strings.HasPrefix(e.Output, "? \t") && strings.HasSuffix(e.Output, "[no test files]\n") 169 | } 170 | 171 | // NoTestsToRun reports special event case for no tests to run: 172 | // "ok \tgithub.com/some/awesome/module\t4.543s [no tests to run]\n" 173 | func (e *Event) NoTestsToRun() bool { 174 | return strings.HasPrefix(e.Output, "ok \t") && strings.HasSuffix(e.Output, "[no tests to run]\n") 175 | } 176 | 177 | // NoTestsWarn whether the event is a test that identifies as: "testing: warning: no tests to run\n" 178 | // 179 | // NOTE: can be found in a package or test event. Must check for non-empty test name in the event. 180 | func (e *Event) NoTestsWarn() bool { 181 | return e.Test != "" && e.Output == "testing: warning: no tests to run\n" 182 | } 183 | 184 | // IsCached reports special event case for cached packages: 185 | // "ok \tgithub.com/mfridman/tparse/tests\t(cached)\n" 186 | // "ok \tgithub.com/mfridman/srfax\t(cached)\tcoverage: 28.8% of statements\n" 187 | func (e *Event) IsCached() bool { 188 | return strings.HasPrefix(e.Output, "ok \t") && strings.Contains(e.Output, "\t(cached)") 189 | } 190 | 191 | // Cover reports special event case for package coverage: 192 | // "ok \tgithub.com/mfridman/srfax\t(cached)\tcoverage: 28.8% of statements\n" 193 | // "ok \tgithub.com/mfridman/srfax\t0.027s\tcoverage: 28.8% of statements\n" 194 | // "ok \tgithub.com/mfridman/tparse/tests\t0.516s\tcoverage: 34.5% of statements in ./...\n" 195 | func (e *Event) Cover() (float64, bool) { 196 | var f float64 197 | var err error 198 | if strings.Contains(e.Output, "coverage:") && strings.Contains(e.Output, "of statements") { 199 | s := coverRe.FindString(e.Output) 200 | f, err = strconv.ParseFloat(strings.TrimRight(s, "%"), 64) 201 | if err != nil { 202 | return f, false 203 | } 204 | return f, true 205 | } 206 | return f, false 207 | } 208 | 209 | // IsRace indicates a race event has been detected. 210 | func (e *Event) IsRace() bool { 211 | return strings.HasPrefix(e.Output, "WARNING: DATA RACE") 212 | } 213 | 214 | // IsPanic indicates a panic event has been detected. 215 | func (e *Event) IsPanic() bool { 216 | // Let's see how this goes. If a user has this in one of their output lines, I think it's 217 | // defensible to suggest updating their output. 218 | if strings.HasPrefix(e.Output, "panic: ") { 219 | return true 220 | } 221 | // The golang/go test suite occasionally outputs these keywords along with "as expected": 222 | // time_test.go:1359: panic in goroutine 7, as expected, with "runtime error: racy use of timers" 223 | if strings.Contains(e.Output, "runtime error:") && !strings.Contains(e.Output, "as expected") { 224 | return true 225 | } 226 | return false 227 | 228 | } 229 | 230 | // Action is one of a fixed set of actions describing a single emitted event. 231 | type Action string 232 | 233 | // Prefixed with Action for convenience. 234 | const ( 235 | ActionRun Action = "run" // test has started running 236 | ActionPause Action = "pause" // test has been paused 237 | ActionCont Action = "cont" // the test has continued running 238 | ActionPass Action = "pass" // test passed 239 | ActionBench Action = "bench" // benchmark printed log output but did not fail 240 | ActionFail Action = "fail" // test or benchmark failed 241 | ActionOutput Action = "output" // test printed output 242 | ActionSkip Action = "skip" // test was skipped or the package contained no tests 243 | 244 | // Added in go1.20 to denote the beginning of each test program's execution. 245 | ActionStart Action = "start" // the start at the beginning of each test program's execution 246 | 247 | // Added in go1.24 to denote a build failure. 248 | ActionBuildFail Action = "build-fail" // the build failed 249 | ActionBuildOutput Action = "build-output" // the toolchain printed output 250 | ) 251 | 252 | func (a Action) String() string { 253 | return string(a) 254 | } 255 | -------------------------------------------------------------------------------- /parse/package.go: -------------------------------------------------------------------------------- 1 | package parse 2 | 3 | import "time" 4 | 5 | // Package is the representation of a single package being tested. The 6 | // summary field is an event that contains all relevant information about the 7 | // package, namely Package (name), Elapsed and Action (big pass or fail). 8 | type Package struct { 9 | Summary *Event 10 | Tests []*Test 11 | 12 | // StartTime is the time the package started running. This is only available 13 | // in go1.20 and above. 14 | StartTime time.Time 15 | 16 | // NoTestFiles indicates whether the package contains tests: [no test files] 17 | // This only occurs at the package level 18 | NoTestFiles bool 19 | 20 | // NoTests indicates a package contains one or more files with no tests. This doesn't 21 | // necessarily mean the file is empty or that the package doesn't have any tests. 22 | // Unfortunately go test marks the package summary with [no tests to run]. 23 | NoTests bool 24 | // NoTestSlice holds events that contain "testing: warning: no tests to run" and 25 | // a non-empty test name. 26 | NoTestSlice []*Event 27 | 28 | // Cached indicates whether the test result was obtained from the cache. 29 | Cached bool 30 | 31 | // Cover reports whether the package contains coverage (go test run with -cover) 32 | Cover bool 33 | Coverage float64 34 | 35 | // HasPanic marks the entire package as panicked. Game over. 36 | HasPanic bool 37 | // Once a package has been marked HasPanic all subsequent events are added to PanicEvents. 38 | PanicEvents []*Event 39 | 40 | // HasDataRace marks the entire package as having a data race. 41 | HasDataRace bool 42 | // DataRaceTests captures an individual test names as having a data race. 43 | DataRaceTests []string 44 | 45 | // HasFailedBuildOrSetup marks the package as having a failed build or setup. 46 | // Example: [build failed] or [setup failed] 47 | HasFailedBuildOrSetup bool 48 | } 49 | 50 | // newPackage initializes and returns a Package. 51 | func newPackage() *Package { 52 | return &Package{ 53 | Summary: &Event{}, 54 | Tests: []*Test{}, 55 | } 56 | } 57 | 58 | // AddEvent adds the event to a test based on test name. 59 | func (p *Package) AddEvent(event *Event) { 60 | var t *Test 61 | if t = p.GetTest(event.Test); t == nil { 62 | // Test does not exist, add it to pkg. 63 | t = &Test{ 64 | Name: event.Test, 65 | Package: event.Package, 66 | } 67 | p.Tests = append(p.Tests, t) 68 | } 69 | 70 | t.Events = append(t.Events, event) 71 | } 72 | 73 | // GetTest returns a test based on given name, if no test is found 74 | // return nil 75 | func (p *Package) GetTest(name string) *Test { 76 | for _, t := range p.Tests { 77 | if t.Name == name { 78 | return t 79 | } 80 | } 81 | return nil 82 | } 83 | 84 | // TestsByAction returns all tests that identify as one of the following 85 | // actions: pass, skip or fail. 86 | // 87 | // An empty slice if returned if there are no tests. 88 | func (p *Package) TestsByAction(action Action) []*Test { 89 | var tests []*Test 90 | for _, t := range p.Tests { 91 | if t.Status() == action { 92 | tests = append(tests, t) 93 | } 94 | } 95 | return tests 96 | } 97 | -------------------------------------------------------------------------------- /parse/package_slice.go: -------------------------------------------------------------------------------- 1 | package parse 2 | 3 | import ( 4 | "sort" 5 | ) 6 | 7 | type PackageSorter func([]*Package) sort.Interface 8 | type PackageSlice []*Package 9 | 10 | type byCoverage struct{ PackageSlice } 11 | type byElapsed struct{ PackageSlice } 12 | 13 | // SortByPackageName sorts packages in ascending alphabetical order. 14 | func SortByPackageName(packages []*Package) sort.Interface { return PackageSlice(packages) } 15 | func (packages PackageSlice) Len() int { return len(packages) } 16 | func (packages PackageSlice) Swap(i, j int) { 17 | packages[i], packages[j] = packages[j], packages[i] 18 | } 19 | func (packages PackageSlice) Less(i, j int) bool { 20 | return packages[i].Summary.Package < packages[j].Summary.Package 21 | } 22 | 23 | // SortByCoverage sorts packages in descending order of code coverage. 24 | func SortByCoverage(packages []*Package) sort.Interface { return byCoverage{packages} } 25 | func (packages byCoverage) Less(i, j int) bool { 26 | return packages.PackageSlice[i].Coverage > packages.PackageSlice[j].Coverage 27 | } 28 | 29 | // SortByElapsed sorts packages in descending order of elapsed time per package. 30 | func SortByElapsed(packages []*Package) sort.Interface { return byElapsed{packages} } 31 | func (packages byElapsed) Less(i, j int) bool { 32 | return packages.PackageSlice[i].Summary.Elapsed > packages.PackageSlice[j].Summary.Elapsed 33 | } 34 | -------------------------------------------------------------------------------- /parse/process_options.go: -------------------------------------------------------------------------------- 1 | package parse 2 | 3 | import ( 4 | "io" 5 | ) 6 | 7 | type options struct { 8 | w io.Writer 9 | follow bool 10 | followVerbose bool 11 | debug bool 12 | 13 | progress bool 14 | progressOutput io.Writer 15 | } 16 | 17 | type OptionsFunc func(o *options) 18 | 19 | func WithFollowOutput(b bool) OptionsFunc { 20 | return func(o *options) { o.follow = b } 21 | } 22 | 23 | func WithFollowVersboseOutput(b bool) OptionsFunc { 24 | return func(o *options) { o.followVerbose = b } 25 | } 26 | 27 | func WithWriter(w io.Writer) OptionsFunc { 28 | return func(o *options) { o.w = w } 29 | } 30 | 31 | func WithDebug() OptionsFunc { 32 | return func(o *options) { o.debug = true } 33 | } 34 | 35 | func WithProgress(b bool) OptionsFunc { 36 | return func(o *options) { o.progress = b } 37 | } 38 | 39 | func WithProgressOutput(w io.Writer) OptionsFunc { 40 | return func(o *options) { o.progressOutput = w } 41 | } 42 | -------------------------------------------------------------------------------- /parse/test.go: -------------------------------------------------------------------------------- 1 | package parse 2 | 3 | import ( 4 | "sort" 5 | ) 6 | 7 | // Test represents a single, unique, package test. 8 | type Test struct { 9 | Name string 10 | Package string 11 | Events []*Event 12 | } 13 | 14 | // Elapsed indicates how long a given test ran (in seconds), by scanning for the largest 15 | // elapsed value from all events. 16 | func (t *Test) Elapsed() float64 { 17 | var f float64 18 | for _, e := range t.Events { 19 | if e.Elapsed > f { 20 | f = e.Elapsed 21 | } 22 | } 23 | return f 24 | } 25 | 26 | // Status reports the outcome of the test represented as a single Action: pass, fail or skip. 27 | func (t *Test) Status() Action { 28 | // sort by time and scan for an action in reverse order. 29 | // The first action we come across (in reverse order) is 30 | // the outcome of the test, which will be one of pass|fail|skip. 31 | t.SortEvents() 32 | for i := len(t.Events) - 1; i >= 0; i-- { 33 | switch t.Events[i].Action { 34 | case ActionPass: 35 | return ActionPass 36 | case ActionSkip: 37 | return ActionSkip 38 | case ActionFail: 39 | return ActionFail 40 | } 41 | } 42 | return ActionFail 43 | } 44 | 45 | // SortEvents sorts test events by elapsed time in ascending order, i.e., oldest to newest. 46 | func (t *Test) SortEvents() { 47 | sort.Slice(t.Events, func(i, j int) bool { 48 | return t.Events[i].Time.Before(t.Events[j].Time) 49 | }) 50 | } 51 | -------------------------------------------------------------------------------- /scripts/release-notes.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | # Check if the required argument is provided 6 | if [ $# -lt 1 ]; then 7 | echo "Usage: $0 []" 8 | exit 1 9 | fi 10 | 11 | version="$1" 12 | changelog_file="${2:-CHANGELOG.md}" 13 | 14 | # Check if the changelog file exists 15 | if [ ! -f "$changelog_file" ]; then 16 | echo "Error: $changelog_file does not exist" 17 | exit 1 18 | fi 19 | 20 | CAPTURE=0 21 | items="" 22 | # Read the changelog file line by line 23 | while IFS= read -r LINE; do 24 | # Stop capturing when we reach the next version sections 25 | if [[ "${LINE}" == "##"* ]] && [[ "${CAPTURE}" -eq 1 ]]; then 26 | break 27 | fi 28 | # Stop capturing when we reach the Unreleased section 29 | if [[ "${LINE}" == "[Unreleased]"* ]]; then 30 | break 31 | fi 32 | # Start capturing when we reach the specified version section 33 | if [[ "${LINE}" == "## [${version}]"* ]] && [[ "${CAPTURE}" -eq 0 ]]; then 34 | CAPTURE=1 35 | continue 36 | fi 37 | # Capture the lines between the specified version and the next version 38 | if [[ "${CAPTURE}" -eq 1 ]]; then 39 | # Ignore empty lines 40 | if [[ -z "${LINE}" ]]; then 41 | continue 42 | fi 43 | items+="$(echo "${LINE}" | xargs -0)" 44 | # Add a newline between each item 45 | if [[ -n "$items" ]]; then 46 | items+=$'\n' 47 | fi 48 | fi 49 | done <"${changelog_file}" 50 | 51 | if [[ -n "$items" ]]; then 52 | cat < f && !pkg.Cover { 81 | t.Fatalf("got %v, want package %q to have cover field marked as true when coverage %v>%v", 82 | pkg.Cover, 83 | name, 84 | pkg.Coverage, 85 | f, 86 | ) 87 | } 88 | }) 89 | } 90 | }) 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /tests/follow_test.go: -------------------------------------------------------------------------------- 1 | package parsetest 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "os" 7 | "path/filepath" 8 | "testing" 9 | 10 | "github.com/stretchr/testify/assert" 11 | 12 | "github.com/mfridman/tparse/internal/app" 13 | "github.com/mfridman/tparse/internal/utils" 14 | "github.com/mfridman/tparse/parse" 15 | ) 16 | 17 | func TestFollow(t *testing.T) { 18 | t.Parallel() 19 | 20 | t.Run("follow_verbose", func(t *testing.T) { 21 | base := filepath.Join("testdata", "follow-verbose") 22 | 23 | tt := []struct { 24 | fileName string 25 | err error 26 | exitCode int 27 | }{ 28 | // race detected 29 | {"test_01", nil, 1}, 30 | {"test_02", nil, 0}, 31 | {"test_03", nil, 0}, 32 | {"test_04", nil, 0}, 33 | {"test_05", parse.ErrNotParsable, 1}, 34 | // build failure in one package 35 | {"test_06", nil, 2}, 36 | } 37 | for _, tc := range tt { 38 | t.Run(tc.fileName, func(t *testing.T) { 39 | buf := bytes.NewBuffer(nil) 40 | inputFile := filepath.Join(base, tc.fileName+".jsonl") 41 | options := app.Options{ 42 | FileName: inputFile, 43 | FollowOutput: true, 44 | FollowOutputWriter: utils.WriteNopCloser{Writer: buf}, 45 | FollowOutputVerbose: true, 46 | DisableTableOutput: true, 47 | } 48 | gotExitCode, err := app.Run(options) 49 | if err != nil && !errors.Is(err, tc.err) { 50 | t.Fatal(err) 51 | } 52 | assert.Equal(t, gotExitCode, tc.exitCode) 53 | goldenFile := filepath.Join(base, tc.fileName+".golden") 54 | want, err := os.ReadFile(goldenFile) 55 | if err != nil { 56 | t.Fatal(err) 57 | } 58 | checkGolden(t, inputFile, goldenFile, buf.Bytes(), want) 59 | }) 60 | } 61 | }) 62 | 63 | t.Run("follow_no_verbose", func(t *testing.T) { 64 | base := filepath.Join("testdata", "follow") 65 | 66 | tt := []struct { 67 | fileName string 68 | err error 69 | exitCode int 70 | }{ 71 | {"test_01", nil, 0}, 72 | } 73 | for _, tc := range tt { 74 | t.Run(tc.fileName, func(t *testing.T) { 75 | buf := bytes.NewBuffer(nil) 76 | inputFile := filepath.Join(base, tc.fileName+".jsonl") 77 | options := app.Options{ 78 | FileName: inputFile, 79 | FollowOutput: true, 80 | FollowOutputWriter: utils.WriteNopCloser{Writer: buf}, 81 | DisableTableOutput: true, 82 | } 83 | gotExitCode, err := app.Run(options) 84 | if err != nil && !errors.Is(err, tc.err) { 85 | t.Fatal(err) 86 | } 87 | assert.Equal(t, gotExitCode, tc.exitCode) 88 | goldenFile := filepath.Join(base, tc.fileName+".golden") 89 | want, err := os.ReadFile(goldenFile) 90 | if err != nil { 91 | t.Fatal(err) 92 | } 93 | checkGolden(t, inputFile, goldenFile, buf.Bytes(), want) 94 | }) 95 | } 96 | }) 97 | } 98 | 99 | func checkGolden( 100 | t *testing.T, 101 | inputFile, goldenFile string, 102 | got, want []byte, 103 | ) { 104 | t.Helper() 105 | if !bytes.Equal(got, want) { 106 | t.Error("input does not match expected output; diff files in follow dir suffixed with .FAIL to debug") 107 | t.Logf("diff %v %v", 108 | "tests/"+goldenFile+".FAIL", 109 | "tests/"+inputFile+".FAIL", 110 | ) 111 | if err := os.WriteFile(goldenFile+".FAIL", got, 0644); err != nil { 112 | t.Fatal(err) 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /tests/outcome_test.go: -------------------------------------------------------------------------------- 1 | package parsetest 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | "github.com/stretchr/testify/require" 10 | 11 | "github.com/mfridman/tparse/parse" 12 | ) 13 | 14 | func TestFinalOutcome(t *testing.T) { 15 | t.Parallel() 16 | // key is the package name, action reports the final outcome of the package. 17 | type registry map[string]parse.Action 18 | 19 | base := filepath.Join("testdata", "outcome") 20 | 21 | tt := []struct { 22 | fileName string 23 | exitCode int 24 | registry 25 | }{ 26 | {"test_01.jsonl", 1, registry{ 27 | "github.com/mfridman/tparse/tests": parse.ActionFail, 28 | }}, 29 | {"test_02.jsonl", 1, registry{ 30 | "github.com/astromail/rover/tests": parse.ActionFail, 31 | "github.com/astromail/rover/cmd/roverd": parse.ActionPass, 32 | "github.com/astromail/rover/smtp": parse.ActionPass, 33 | "github.com/astromail/rover/storage": parse.ActionPass, 34 | "github.com/astromail/rover/errors": parse.ActionPass, 35 | "github.com/astromail/rover/storage/badger": parse.ActionPass, 36 | "github.com/astromail/rover": parse.ActionPass, 37 | }}, 38 | {"test_03.jsonl", 0, registry{ 39 | "fmt": parse.ActionPass, 40 | }}, 41 | {"test_04.jsonl", 0, registry{ 42 | "github.com/astromail/rover/tests": parse.ActionPass, 43 | }}, 44 | {"test_05.jsonl", 0, registry{ 45 | "github.com/astromail/rover/tests": parse.ActionPass, 46 | }}, 47 | {"test_06.jsonl", 0, registry{ 48 | "fmt": parse.ActionPass, 49 | }}, 50 | {"test_07.jsonl", 0, registry{ 51 | "debug/errorcause": parse.ActionPass, 52 | }}, 53 | {"test_08.jsonl", 0, registry{ 54 | "github.com/awesome/pkg": parse.ActionPass, 55 | }}, 56 | } 57 | for _, tc := range tt { 58 | t.Run(tc.fileName, func(t *testing.T) { 59 | f, err := os.Open(filepath.Join(base, tc.fileName)) 60 | if err != nil { 61 | t.Fatal(err) 62 | } 63 | summary, err := parse.Process(f) 64 | require.NoError(t, err) 65 | assert.Equal(t, len(summary.Packages), len(tc.registry)) 66 | assert.Equal(t, summary.ExitCode(), tc.exitCode) 67 | 68 | for name, pkg := range summary.Packages { 69 | want, ok := tc.registry[name] 70 | if !ok { 71 | t.Log("currently registered packages:") 72 | for k := range tc.registry { 73 | t.Log(k) 74 | } 75 | t.Fatalf("got unmapped package name %q. Check input file and record all unique package names in registry", name) 76 | } 77 | if pkg.Summary.Action != want { 78 | t.Fatalf("failed package summary action: got: %q, want: %q", pkg.Summary.Action, want) 79 | } 80 | if len(pkg.Tests) == 0 && pkg.Summary.Action != parse.ActionPass { 81 | t.Fatalf("zero test should always return pass: got: %q, want: %q", pkg.Summary.Action, parse.ActionPass) 82 | } 83 | if (pkg.NoTestFiles || pkg.NoTests) && pkg.Summary.Action != parse.ActionPass { 84 | t.Fatalf("packages marked as [no tests to run] or [no test files] should always return pass: got: %q, want: %q", 85 | pkg.Summary.Action, 86 | parse.ActionPass, 87 | ) 88 | } 89 | 90 | // As a sanity check, iterate over the tests and make sure all tests actually 91 | // reflect the package outcome. 92 | 93 | switch pkg.Summary.Action { 94 | case parse.ActionPass: 95 | // One or more tests must be marked as either pass or skip. 96 | // A single skipped test will still yield a pass package outcome. 97 | for _, test := range pkg.Tests { 98 | switch test.Status() { 99 | case parse.ActionPass, parse.ActionSkip: 100 | continue 101 | default: 102 | t.Fatalf("all tests within a passed package should have a status of pass or skip: got: %q", test.Status()) 103 | } 104 | } 105 | case parse.ActionFail: 106 | // One or more tests must be marked as failed. 107 | var failed bool 108 | for _, tc := range pkg.Tests { 109 | if tc.Status() == parse.ActionFail { 110 | failed = true 111 | break 112 | } 113 | } 114 | if !failed { 115 | t.Fatalf("got no failed tests, want one or more tests to be marked as: %q", parse.ActionFail) 116 | } 117 | default: 118 | // Catch all, should never get this. 119 | t.Fatalf("got package summary action %q, want pass or fail", pkg.Summary.Action) 120 | } 121 | } 122 | }) 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /tests/package_start_test.go: -------------------------------------------------------------------------------- 1 | package parsetest 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | "time" 7 | 8 | "github.com/stretchr/testify/assert" 9 | "github.com/stretchr/testify/require" 10 | 11 | "github.com/mfridman/tparse/parse" 12 | ) 13 | 14 | func TestPackageStartTime(t *testing.T) { 15 | t.Parallel() 16 | 17 | // This test depends on go120_start_action.jsonl, which contains test output from go1.20 18 | 19 | expected := map[string]string{ 20 | "github.com/pressly/goose/v4": "2023-05-28T18:36:01.280967-04:00", 21 | "github.com/pressly/goose/v4/internal/check": "2023-05-28T18:36:01.281088-04:00", 22 | "github.com/pressly/goose/v4/internal/cli": "2023-05-28T18:36:01.281147-04:00", 23 | "github.com/pressly/goose/v4/internal/dialectadapter": "2023-05-28T18:36:01.281218-04:00", 24 | "github.com/pressly/goose/v4/internal/dialectadapter/dialectquery": "2023-05-28T18:36:01.281253-04:00", 25 | "github.com/pressly/goose/v4/internal/migration": "2023-05-28T18:36:01.281269-04:00", 26 | "github.com/pressly/goose/v4/internal/migrationstats": "2023-05-28T18:36:01.281381-04:00", 27 | "github.com/pressly/goose/v4/internal/migrationstats/migrationstatsos": "2023-05-28T18:36:01.281426-04:00", 28 | "github.com/pressly/goose/v4/internal/normalizedsn": "2023-05-28T18:36:01.281465-04:00", 29 | "github.com/pressly/goose/v4/internal/sqlparser": "2023-05-28T18:36:01.446915-04:00", 30 | "github.com/pressly/goose/v4/internal/testdb": "2023-05-28T18:36:01.446973-04:00", 31 | } 32 | 33 | fileName := "./testdata/go120_start_action.jsonl" 34 | f, err := os.Open(fileName) 35 | require.NoError(t, err) 36 | defer f.Close() 37 | 38 | summary, err := parse.Process(f) 39 | require.NoError(t, err) 40 | assert.Equal(t, len(summary.Packages), len(expected)) 41 | 42 | for _, p := range summary.Packages { 43 | if p.StartTime.IsZero() { 44 | t.Fatalf("package %q cannot contain zero start time", p.Summary.Package) 45 | } 46 | unparsed, ok := expected[p.Summary.Package] 47 | if !ok { 48 | t.Fatalf("package %q not found in expected map", p.Summary.Package) 49 | } 50 | want, err := time.Parse(time.RFC3339, unparsed) 51 | require.NoError(t, err) 52 | if !p.StartTime.Equal(want) { 53 | t.Fatalf("package %q start time got %q want %q", p.Summary.Package, p.StartTime, want) 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /tests/panic_test.go: -------------------------------------------------------------------------------- 1 | package parsetest 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | "github.com/stretchr/testify/require" 10 | 11 | "github.com/mfridman/tparse/parse" 12 | ) 13 | 14 | func TestPanic(t *testing.T) { 15 | t.Parallel() 16 | 17 | // Key is the package name, bool reports whether the packages is expected to be marked as panic. 18 | type expected map[string]bool 19 | 20 | base := filepath.Join("testdata", "panic") 21 | 22 | tt := []struct { 23 | fileName string 24 | expected 25 | }{ 26 | { 27 | "test_01.jsonl", expected{ 28 | "github.com/pressly/goose/v3/tests/e2e": true, 29 | }, 30 | }, 31 | { 32 | "test_02.jsonl", expected{ 33 | "github.com/mfridman/tparse/parse": true, 34 | }, 35 | }, 36 | { 37 | "test_03.jsonl", expected{ 38 | "github.com/mfridman/tparse/tests": true, 39 | }, 40 | }, 41 | { 42 | "test_04.jsonl", expected{ 43 | "github.com/mfridman/tparse/tests": true, 44 | "github.com/mfridman/tparse/ignore": false, 45 | "github.com/mfridman/tparse/parse": false, 46 | "github.com/mfridman/tparse": false, 47 | }, 48 | }, 49 | { 50 | "test_05.jsonl", expected{ 51 | "github.com/mfridman/tparse/tests": true, 52 | "github.com/mfridman/tparse/parse": false, 53 | "github.com/mfridman/tparse": false, 54 | "github.com/mfridman/tparse/ignore": false, 55 | }, 56 | }, 57 | { 58 | "test_06.jsonl", expected{ 59 | "github.com/mfridman/tparse/tests": false, 60 | "github.com/mfridman/tparse/parse": true, 61 | "github.com/mfridman/tparse": false, 62 | "github.com/mfridman/tparse/ignore": false, 63 | }, 64 | }, 65 | } 66 | 67 | for _, tc := range tt { 68 | t.Run(tc.fileName, func(t *testing.T) { 69 | f, err := os.Open(filepath.Join(base, tc.fileName)) 70 | require.NoError(t, err) 71 | 72 | summary, err := parse.Process(f) 73 | require.NoError(t, err) 74 | assert.Equal(t, 1, summary.ExitCode()) 75 | 76 | for name, pkg := range summary.Packages { 77 | want, ok := tc.expected[name] 78 | if !ok { 79 | t.Log("currently registered packages:") 80 | for k := range tc.expected { 81 | t.Log(k) 82 | } 83 | t.Fatalf("got unmapped package name %q. Check input file and record all unique package names in registry", name) 84 | } 85 | if pkg.HasPanic != want { 86 | t.Log("package: ", name) 87 | t.Logf("summary: %+v", pkg.Summary) 88 | t.Fatal("got no panic, expecting package to be marked as has panic") 89 | } 90 | } 91 | }) 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /tests/prescan_test.go: -------------------------------------------------------------------------------- 1 | package parsetest 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/require" 9 | 10 | "github.com/mfridman/tparse/parse" 11 | ) 12 | 13 | func TestPrescan(t *testing.T) { 14 | t.Parallel() 15 | 16 | base := filepath.Join("testdata", "prescan") 17 | 18 | tt := []struct { 19 | fileName string 20 | desc string 21 | err error 22 | }{ 23 | {"test_01.txt", "want err", nil}, 24 | {"test_02.txt", "want failure after reading >50 lines of non-parsable events", parse.ErrNotParsable}, 25 | // logic: unparsable event(s), good event(s), at least one event = fail. 26 | // Once we get a good event, we expect only good events to follow until EOF. 27 | {"test_03.txt", "want failure when stream contains a bad event(s) -> good event(s) -> bad event", parse.ErrNotParsable}, 28 | {"test_04.txt", "want failure reading <50 lines of non-parsable events", parse.ErrNotParsable}, 29 | } 30 | 31 | for _, tc := range tt { 32 | t.Run(tc.fileName, func(t *testing.T) { 33 | inputFile := filepath.Join(base, tc.fileName) 34 | f, err := os.Open(inputFile) 35 | require.NoError(t, err) 36 | 37 | _, err = parse.Process(f) 38 | if tc.err != nil { 39 | require.ErrorIs(t, err, tc.err) 40 | return 41 | } 42 | require.NoError(t, err) 43 | }) 44 | 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /tests/race_test.go: -------------------------------------------------------------------------------- 1 | package parsetest 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | "github.com/stretchr/testify/require" 10 | 11 | "github.com/mfridman/tparse/parse" 12 | ) 13 | 14 | func TestRaceDetected(t *testing.T) { 15 | t.Parallel() 16 | 17 | // Key is the package name, and the value may be zero, one or more test name(s). 18 | // Not all data races may be associated with a test. 19 | type expected map[string][]string 20 | 21 | base := filepath.Join("testdata", "race") 22 | 23 | var tt = []struct { 24 | fileName string 25 | expected 26 | }{ 27 | { 28 | "test_01", expected{"command-line-arguments": {"TestA"}}, 29 | }, 30 | { 31 | "test_02", expected{"github.com/mfridman/tparse/parse": {"TestB", "TestElapsed"}}, 32 | }, 33 | { 34 | "test_03", expected{"debug/tparse-24": {}}, 35 | }, 36 | // This is a race directly from Test only. 37 | { 38 | "test_04", expected{"github.com/mfridman/debug-go/testing": {"TestRace"}}, 39 | }, 40 | // This is a race directly from TestMain with other tests that have failed. 41 | { 42 | "test_05", expected{"github.com/mfridman/debug-go/testing": {}}, 43 | }, 44 | // This is a race directly from TestMain only. 45 | { 46 | "test_06", expected{"github.com/mfridman/debug-go/testing": {}}, 47 | }, 48 | // This is a race from a Test that calls into a package that has a race condition. (failed assertion) 49 | { 50 | "test_07", expected{"github.com/mfridman/debug-go/testing": {"TestRace"}}, 51 | }, 52 | // This is a race from a Test that calls into a package that has a race condition. (passed assertion) 53 | { 54 | "test_08", expected{"github.com/mfridman/debug-go/testing": {"TestRace"}}, 55 | }, 56 | } 57 | 58 | for _, tc := range tt { 59 | t.Run(tc.fileName, func(t *testing.T) { 60 | inputFile := filepath.Join(base, tc.fileName+".jsonl") 61 | f, err := os.Open(inputFile) 62 | require.NoError(t, err) 63 | defer f.Close() 64 | 65 | summary, err := parse.Process(f) 66 | require.NoError(t, err) 67 | 68 | if summary.ExitCode() == 0 { 69 | t.Fatalf("expecting non-zero exit code") 70 | } 71 | for name, pkg := range summary.Packages { 72 | wantTestName, ok := tc.expected[name] 73 | if !ok { 74 | t.Fatalf("failed to find package: %q", name) 75 | } 76 | assert.Equal(t, len(pkg.DataRaceTests), len(wantTestName)) 77 | if len(pkg.DataRaceTests) > 0 { 78 | for i := range pkg.DataRaceTests { 79 | assert.Equal(t, pkg.DataRaceTests[i], wantTestName[i]) 80 | } 81 | } 82 | } 83 | }) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /tests/sort_test.go: -------------------------------------------------------------------------------- 1 | package parsetest 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | "github.com/stretchr/testify/require" 10 | 11 | "github.com/mfridman/tparse/parse" 12 | ) 13 | 14 | func TestSortName(t *testing.T) { 15 | t.Parallel() 16 | 17 | base := filepath.Join("testdata", "cover") 18 | 19 | // expected package name and corresponding cover % 20 | // Note, these numbers will vary between on go versions. 21 | type expected []string 22 | 23 | tt := []struct { 24 | fileName string 25 | expected 26 | }{ 27 | { 28 | // go test -count=1 bytes log sort -json -cover | tparse -sort name 29 | "test_01", 30 | expected{ 31 | "bytes", 32 | "log", 33 | "sort", 34 | }, 35 | }, 36 | { 37 | // go test -count=1 bufio bytes crypto fmt log mime net sort strings time -json -cover | tparse -sort name 38 | "test_02", 39 | expected{ 40 | "bufio", 41 | "bytes", 42 | "crypto", 43 | "fmt", 44 | "log", 45 | "mime", 46 | "net", 47 | "sort", 48 | "strings", 49 | "time", 50 | }, 51 | }, 52 | } 53 | for _, tc := range tt { 54 | t.Run(tc.fileName, func(t *testing.T) { 55 | inputFile := filepath.Join(base, tc.fileName+".jsonl") 56 | f, err := os.Open(inputFile) 57 | require.NoError(t, err) 58 | 59 | summary, err := parse.Process(f) 60 | require.NoError(t, err) 61 | assert.Equal(t, len(summary.Packages), len(tc.expected)) 62 | packages := summary.GetSortedPackages(parse.SortByPackageName) 63 | 64 | for i, pkg := range packages { 65 | t.Run(pkg.Summary.Package, func(t *testing.T) { 66 | wantName := tc.expected[i] 67 | if pkg.Summary.Package != wantName { 68 | t.Fatalf("got name: %s, want name: %s", pkg.Summary.Package, wantName) 69 | } 70 | }) 71 | } 72 | }) 73 | } 74 | } 75 | 76 | func TestSortCoverage(t *testing.T) { 77 | t.Parallel() 78 | 79 | base := filepath.Join("testdata", "cover") 80 | 81 | // expected package name and corresponding cover % 82 | // Note, these numbers will vary between on go versions. 83 | type expected []float64 84 | 85 | tt := []struct { 86 | fileName string 87 | expected 88 | }{ 89 | { 90 | // go test -count=1 bytes log sort -json -cover | tparse -sort cover 91 | "test_01", 92 | expected{ 93 | 86.7, // "bytes" 94 | 68.0, // "log" 95 | 60.8, // "sort" 96 | }, 97 | }, 98 | { 99 | // go test -count=1 bufio bytes crypto fmt log mime net sort strings time -json -cover | tparse -sort cover 100 | "test_02", 101 | expected{ 102 | 98.1, // "strings" 103 | 95.6, // "bytes" 104 | 95.2, // "fmt" 105 | 93.8, // "mime" 106 | 93.3, // "bufio" 107 | 91.8, // "time" 108 | 81.2, // "net" 109 | 68.0, // "log" 110 | 60.8, // "sort" 111 | 5.9, // "crypto" 112 | }, 113 | }, 114 | } 115 | for _, tc := range tt { 116 | t.Run(tc.fileName, func(t *testing.T) { 117 | inputFile := filepath.Join(base, tc.fileName+".jsonl") 118 | f, err := os.Open(inputFile) 119 | require.NoError(t, err) 120 | 121 | summary, err := parse.Process(f) 122 | require.NoError(t, err) 123 | assert.Equal(t, len(summary.Packages), len(tc.expected)) 124 | packages := summary.GetSortedPackages(parse.SortByCoverage) 125 | 126 | for i, pkg := range packages { 127 | t.Run(pkg.Summary.Package, func(t *testing.T) { 128 | wantCover := tc.expected[i] 129 | if pkg.Coverage != wantCover { 130 | t.Fatalf("got cover: %v(%s), want cover: %v", pkg.Coverage, pkg.Summary.Package, wantCover) 131 | } 132 | }) 133 | } 134 | }) 135 | } 136 | } 137 | 138 | func TestSortElapsed(t *testing.T) { 139 | t.Parallel() 140 | 141 | base := filepath.Join("testdata", "cached") 142 | 143 | // expected package name and corresponding cover % 144 | // Note, these numbers will vary between on go versions. 145 | type expected []float64 146 | 147 | tt := []struct { 148 | fileName string 149 | expected 150 | }{ 151 | { 152 | // go test -count=1 fmt mime strings time -json | tparse -sort elapsed 153 | "test_01", 154 | expected{ 155 | 7.168, // "time" 156 | 0.020, // "mime" 157 | 0.007, // "strings" 158 | 0.003, // "fmt" 159 | }, 160 | }, 161 | { 162 | // go test -count=1 bufio bytes crypto fmt log mime sort strings time -json | tparse -sort elapsed 163 | "test_02", 164 | expected{ 165 | 7.641, // "time", 166 | 1.176, // "bytes", 167 | 0.220, // "fmt", 168 | 0.134, // "bufio", 169 | 0.070, // "crypto", 170 | 0.002, // "strings", 171 | 0.001, // "mime", 172 | 0.001, // "sort", 173 | 0.000, // "log", 174 | }, 175 | }, 176 | } 177 | for _, tc := range tt { 178 | t.Run(tc.fileName, func(t *testing.T) { 179 | inputFile := filepath.Join(base, tc.fileName+".jsonl") 180 | f, err := os.Open(inputFile) 181 | require.NoError(t, err) 182 | 183 | summary, err := parse.Process(f) 184 | require.NoError(t, err) 185 | assert.Equal(t, len(summary.Packages), len(tc.expected)) 186 | packages := summary.GetSortedPackages(parse.SortByElapsed) 187 | 188 | for i, pkg := range packages { 189 | t.Run(pkg.Summary.Package, func(t *testing.T) { 190 | wantElapsed := tc.expected[i] 191 | if pkg.Summary.Elapsed != wantElapsed { 192 | t.Fatalf("got elapsed: %v (%s), want elapsed: %v", pkg.Summary.Elapsed, pkg.Summary.Package, wantElapsed) 193 | } 194 | }) 195 | } 196 | }) 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /tests/summary_counts_test.go: -------------------------------------------------------------------------------- 1 | package parsetest 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | "github.com/stretchr/testify/require" 9 | 10 | "github.com/mfridman/tparse/parse" 11 | ) 12 | 13 | func TestSummaryCounts(t *testing.T) { 14 | t.Parallel() 15 | 16 | // This test depends on metrics_test.jsonl, which contains the output of 9 std lib packages: 17 | // go test -count=1 fmt strings bytes bufio crypto log mime sort time -json 18 | 19 | fileName := "./testdata/metrics_test.jsonl" 20 | f, err := os.Open(fileName) 21 | require.NoError(t, err) 22 | 23 | summary, err := parse.Process(f) 24 | require.NoError(t, err) 25 | assert.Len(t, summary.Packages, 9) 26 | 27 | for name, pkg := range summary.Packages { 28 | if pkg.Summary == nil { 29 | t.Fatalf("package %q cannot contain nil summary", name) 30 | } 31 | if pkg.Summary.Action != parse.ActionPass { 32 | t.Logf("failed pkg: %v", name) 33 | t.Fatalf("unexpected action %q, want %q", pkg.Summary.Action, parse.ActionPass) 34 | } 35 | } 36 | 37 | tests := []struct { 38 | name string 39 | total, passed, skipped, failed int 40 | elapsed float64 41 | }{ 42 | {"fmt", 59, 58, 1, 0, 0.22}, 43 | {"strings", 107, 107, 0, 0, 5.494}, 44 | {"bytes", 123, 123, 0, 0, 3.5380000000000003}, 45 | {"bufio", 69, 69, 0, 0, 0.07}, 46 | {"crypto", 5, 5, 0, 0, 0.016}, 47 | {"log", 8, 8, 0, 0, 0.085}, 48 | {"mime", 20, 20, 0, 0, 0.025}, 49 | {"sort", 37, 36, 1, 0, 3.117}, 50 | {"time", 118, 117, 1, 0, 7.157}, 51 | } 52 | 53 | for _, test := range tests { 54 | t.Run(test.name+"_test", func(t *testing.T) { 55 | pkg := summary.Packages[test.name] 56 | if len(pkg.Tests) != test.total { 57 | t.Fatalf("got %d total tests in package %q, want %d total tests", len(pkg.Tests), test.name, test.total) 58 | } 59 | 60 | pa := pkg.TestsByAction(parse.ActionPass) 61 | if len(pa) != test.passed { 62 | t.Fatalf("got %d passed tests in package %q, want %d passed tests", len(pa), test.name, test.passed) 63 | } 64 | 65 | sk := pkg.TestsByAction(parse.ActionSkip) 66 | if len(sk) != test.skipped { 67 | t.Fatalf("got %d passed tests in package %q, want %d passed tests", len(sk), test.name, test.skipped) 68 | } 69 | 70 | fa := pkg.TestsByAction(parse.ActionFail) 71 | if len(fa) != test.failed { 72 | t.Fatalf("got %d failed tests in package %q, want %d failed tests", len(fa), test.name, test.failed) 73 | } 74 | 75 | if pkg.Summary.Elapsed != test.elapsed { 76 | t.Fatalf("got elapsed time %f for package %q, want %f", pkg.Summary.Elapsed, test.name, test.elapsed) 77 | } 78 | }) 79 | } 80 | } 81 | 82 | func TestElapsed(t *testing.T) { 83 | t.Parallel() 84 | 85 | // This test depends on elapsed_test.jsonl, which contains the output of 2 std lib tests 86 | // with known elapsed time. 87 | // go test -count=1 strings -run="^(TestCompareStrings|TestCaseConsistency$)" -json -cover 88 | 89 | expected := map[string]float64{ 90 | "TestCompareStrings": 3.49, 91 | "TestCaseConsistency": 0.17, 92 | } 93 | 94 | fileName := "./testdata/elapsed_test.jsonl" 95 | f, err := os.Open(fileName) 96 | require.NoError(t, err) 97 | defer f.Close() 98 | 99 | summary, err := parse.Process(f) 100 | require.NoError(t, err) 101 | assert.Len(t, summary.Packages, 1) 102 | 103 | pkg, ok := summary.Packages["strings"] 104 | if !ok { 105 | t.Fatalf(`got unexpected pkg: %v\nwant "strings"`, pkg) 106 | } 107 | assert.Len(t, pkg.Tests, 2) 108 | 109 | for _, test := range pkg.Tests { 110 | wantElapsed, ok := expected[test.Name] 111 | if !ok { 112 | t.Errorf("got unknown test name %q", test.Name) 113 | } 114 | if test.Elapsed() != wantElapsed { 115 | t.Errorf("got %v elapsed time for test: %q, want %v", test.Elapsed(), test.Name, wantElapsed) 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /tests/testdata/elapsed_test.jsonl: -------------------------------------------------------------------------------- 1 | {"Time":"2018-11-24T21:54:39.153656-05:00","Action":"run","Package":"strings","Test":"TestCompareStrings"} 2 | {"Time":"2018-11-24T21:54:39.154006-05:00","Action":"output","Package":"strings","Test":"TestCompareStrings","Output":"=== RUN TestCompareStrings\n"} 3 | {"Time":"2018-11-24T21:54:42.644837-05:00","Action":"output","Package":"strings","Test":"TestCompareStrings","Output":"--- PASS: TestCompareStrings (3.49s)\n"} 4 | {"Time":"2018-11-24T21:54:42.644875-05:00","Action":"pass","Package":"strings","Test":"TestCompareStrings","Elapsed":3.49} 5 | {"Time":"2018-11-24T21:54:42.644896-05:00","Action":"run","Package":"strings","Test":"TestCaseConsistency"} 6 | {"Time":"2018-11-24T21:54:42.644906-05:00","Action":"output","Package":"strings","Test":"TestCaseConsistency","Output":"=== RUN TestCaseConsistency\n"} 7 | {"Time":"2018-11-24T21:54:42.818776-05:00","Action":"output","Package":"strings","Test":"TestCaseConsistency","Output":"--- PASS: TestCaseConsistency (0.17s)\n"} 8 | {"Time":"2018-11-24T21:54:42.818824-05:00","Action":"pass","Package":"strings","Test":"TestCaseConsistency","Elapsed":0.17} 9 | {"Time":"2018-11-24T21:54:42.818878-05:00","Action":"output","Package":"strings","Output":"PASS\n"} 10 | {"Time":"2018-11-24T21:54:42.824353-05:00","Action":"output","Package":"strings","Output":"ok \tstrings\t3.775s\n"} 11 | {"Time":"2018-11-24T21:54:42.824402-05:00","Action":"pass","Package":"strings","Elapsed":3.775} -------------------------------------------------------------------------------- /tests/testdata/failed/test_02.jsonl: -------------------------------------------------------------------------------- 1 | {"Time":"2022-05-20T09:24:29.849233-04:00","Action":"run","Package":"github.com/mfridman/debug-go/testing","Test":"TestWhatever"} 2 | {"Time":"2022-05-20T09:24:29.849381-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestWhatever","Output":"=== RUN TestWhatever\n"} 3 | {"Time":"2022-05-20T09:24:29.849391-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestWhatever","Output":" main_test.go:13: assert error\n"} 4 | {"Time":"2022-05-20T09:24:29.8494-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestWhatever","Output":" main_test.go:14: \n"} 5 | {"Time":"2022-05-20T09:24:29.849402-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestWhatever","Output":" \tError Trace:\tmain_test.go:14\n"} 6 | {"Time":"2022-05-20T09:24:29.849404-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestWhatever","Output":" \tError: \t\"does not contain\" does not contain \"ostriche\"\n"} 7 | {"Time":"2022-05-20T09:24:29.849412-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestWhatever","Output":" \tTest: \tTestWhatever\n"} 8 | {"Time":"2022-05-20T09:24:29.849435-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestWhatever","Output":" main_test.go:15: \n"} 9 | {"Time":"2022-05-20T09:24:29.849556-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestWhatever","Output":" \tError Trace:\tmain_test.go:15\n"} 10 | {"Time":"2022-05-20T09:24:29.849562-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestWhatever","Output":" \tError: \tReceived unexpected error:\n"} 11 | {"Time":"2022-05-20T09:24:29.849569-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestWhatever","Output":" \t \tmake the errorss..\n"} 12 | {"Time":"2022-05-20T09:24:29.849571-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestWhatever","Output":" \tTest: \tTestWhatever\n"} 13 | {"Time":"2022-05-20T09:24:30.850912-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestWhatever","Output":" main_test.go:17: skdhjfg\n"} 14 | {"Time":"2022-05-20T09:24:30.851026-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestWhatever","Output":"--- FAIL: TestWhatever (1.00s)\n"} 15 | {"Time":"2022-05-20T09:24:30.851526-04:00","Action":"fail","Package":"github.com/mfridman/debug-go/testing","Test":"TestWhatever","Elapsed":1} 16 | {"Time":"2022-05-20T09:24:30.851551-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Output":"FAIL\n"} 17 | {"Time":"2022-05-20T09:24:30.854596-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Output":"FAIL\tgithub.com/mfridman/debug-go/testing\t1.113s\n"} 18 | {"Time":"2022-05-20T09:24:30.854827-04:00","Action":"fail","Package":"github.com/mfridman/debug-go/testing","Elapsed":1.114} -------------------------------------------------------------------------------- /tests/testdata/failed/test_04.jsonl: -------------------------------------------------------------------------------- 1 | {"Time":"2022-05-21T09:04:11.074034-04:00","Action":"run","Package":"command-line-arguments","Test":"TestWhatever"} 2 | {"Time":"2022-05-21T09:04:11.074252-04:00","Action":"output","Package":"command-line-arguments","Test":"TestWhatever","Output":"=== RUN TestWhatever\n"} 3 | {"Time":"2022-05-21T09:04:11.074268-04:00","Action":"output","Package":"command-line-arguments","Test":"TestWhatever","Output":" main_test.go:12: assert error\n"} 4 | {"Time":"2022-05-21T09:04:11.074275-04:00","Action":"output","Package":"command-line-arguments","Test":"TestWhatever","Output":" main_test.go:13: \n"} 5 | {"Time":"2022-05-21T09:04:11.07428-04:00","Action":"output","Package":"command-line-arguments","Test":"TestWhatever","Output":" \tError Trace:\tmain_test.go:13\n"} 6 | {"Time":"2022-05-21T09:04:11.074284-04:00","Action":"output","Package":"command-line-arguments","Test":"TestWhatever","Output":" \tError: \t\"does not contain\" does not contain \"ostriche\"\n"} 7 | {"Time":"2022-05-21T09:04:11.07429-04:00","Action":"output","Package":"command-line-arguments","Test":"TestWhatever","Output":" \tTest: \tTestWhatever\n"} 8 | {"Time":"2022-05-21T09:04:12.075418-04:00","Action":"run","Package":"command-line-arguments","Test":"TestWhatever/foo"} 9 | {"Time":"2022-05-21T09:04:12.075597-04:00","Action":"output","Package":"command-line-arguments","Test":"TestWhatever/foo","Output":"=== RUN TestWhatever/foo\n"} 10 | {"Time":"2022-05-21T09:04:12.075641-04:00","Action":"output","Package":"command-line-arguments","Test":"TestWhatever/foo","Output":" main_test.go:17: some random output from foo only\n"} 11 | {"Time":"2022-05-21T09:04:12.075689-04:00","Action":"run","Package":"command-line-arguments","Test":"TestWhatever/foo/bar"} 12 | {"Time":"2022-05-21T09:04:12.075718-04:00","Action":"output","Package":"command-line-arguments","Test":"TestWhatever/foo/bar","Output":"=== RUN TestWhatever/foo/bar\n"} 13 | {"Time":"2022-05-21T09:04:12.075748-04:00","Action":"output","Package":"command-line-arguments","Test":"TestWhatever/foo/bar","Output":"=== PAUSE TestWhatever/foo/bar\n"} 14 | {"Time":"2022-05-21T09:04:12.075774-04:00","Action":"pause","Package":"command-line-arguments","Test":"TestWhatever/foo/bar"} 15 | {"Time":"2022-05-21T09:04:12.075799-04:00","Action":"run","Package":"command-line-arguments","Test":"TestWhatever/foo/baz"} 16 | {"Time":"2022-05-21T09:04:12.075823-04:00","Action":"output","Package":"command-line-arguments","Test":"TestWhatever/foo/baz","Output":"=== RUN TestWhatever/foo/baz\n"} 17 | {"Time":"2022-05-21T09:04:12.075849-04:00","Action":"output","Package":"command-line-arguments","Test":"TestWhatever/foo/baz","Output":"=== PAUSE TestWhatever/foo/baz\n"} 18 | {"Time":"2022-05-21T09:04:12.075873-04:00","Action":"pause","Package":"command-line-arguments","Test":"TestWhatever/foo/baz"} 19 | {"Time":"2022-05-21T09:04:12.075935-04:00","Action":"cont","Package":"command-line-arguments","Test":"TestWhatever/foo/bar"} 20 | {"Time":"2022-05-21T09:04:12.075964-04:00","Action":"output","Package":"command-line-arguments","Test":"TestWhatever/foo/bar","Output":"=== CONT TestWhatever/foo/bar\n"} 21 | {"Time":"2022-05-21T09:04:12.07599-04:00","Action":"output","Package":"command-line-arguments","Test":"TestWhatever/foo/bar","Output":" main_test.go:20: some random output from bar only\n"} 22 | {"Time":"2022-05-21T09:04:12.076047-04:00","Action":"run","Package":"command-line-arguments","Test":"TestWhatever/foo/bar/inner-bar"} 23 | {"Time":"2022-05-21T09:04:12.076076-04:00","Action":"output","Package":"command-line-arguments","Test":"TestWhatever/foo/bar/inner-bar","Output":"=== RUN TestWhatever/foo/bar/inner-bar\n"} 24 | {"Time":"2022-05-21T09:04:12.076103-04:00","Action":"output","Package":"command-line-arguments","Test":"TestWhatever/foo/bar/inner-bar","Output":"=== PAUSE TestWhatever/foo/bar/inner-bar\n"} 25 | {"Time":"2022-05-21T09:04:12.076127-04:00","Action":"pause","Package":"command-line-arguments","Test":"TestWhatever/foo/bar/inner-bar"} 26 | {"Time":"2022-05-21T09:04:12.076152-04:00","Action":"cont","Package":"command-line-arguments","Test":"TestWhatever/foo/bar/inner-bar"} 27 | {"Time":"2022-05-21T09:04:12.076177-04:00","Action":"output","Package":"command-line-arguments","Test":"TestWhatever/foo/bar/inner-bar","Output":"=== CONT TestWhatever/foo/bar/inner-bar\n"} 28 | {"Time":"2022-05-21T09:04:12.076291-04:00","Action":"output","Package":"command-line-arguments","Test":"TestWhatever/foo/bar/inner-bar","Output":" main_test.go:23: another inner-bar\n"} 29 | {"Time":"2022-05-21T09:04:12.076317-04:00","Action":"cont","Package":"command-line-arguments","Test":"TestWhatever/foo/baz"} 30 | {"Time":"2022-05-21T09:04:12.076336-04:00","Action":"output","Package":"command-line-arguments","Test":"TestWhatever/foo/baz","Output":"=== CONT TestWhatever/foo/baz\n"} 31 | {"Time":"2022-05-21T09:04:12.076355-04:00","Action":"run","Package":"command-line-arguments","Test":"TestWhatever/foo/baz/inner-baz"} 32 | {"Time":"2022-05-21T09:04:12.076373-04:00","Action":"output","Package":"command-line-arguments","Test":"TestWhatever/foo/baz/inner-baz","Output":"=== RUN TestWhatever/foo/baz/inner-baz\n"} 33 | {"Time":"2022-05-21T09:04:12.076393-04:00","Action":"output","Package":"command-line-arguments","Test":"TestWhatever/foo/baz/inner-baz","Output":"=== PAUSE TestWhatever/foo/baz/inner-baz\n"} 34 | {"Time":"2022-05-21T09:04:12.07641-04:00","Action":"pause","Package":"command-line-arguments","Test":"TestWhatever/foo/baz/inner-baz"} 35 | {"Time":"2022-05-21T09:04:12.076428-04:00","Action":"cont","Package":"command-line-arguments","Test":"TestWhatever/foo/baz/inner-baz"} 36 | {"Time":"2022-05-21T09:04:12.076446-04:00","Action":"output","Package":"command-line-arguments","Test":"TestWhatever/foo/baz/inner-baz","Output":"=== CONT TestWhatever/foo/baz/inner-baz\n"} 37 | {"Time":"2022-05-21T09:04:12.076465-04:00","Action":"output","Package":"command-line-arguments","Test":"TestWhatever/foo/baz/inner-baz","Output":" main_test.go:30: some inner-baz error\n"} 38 | {"Time":"2022-05-21T09:04:12.076504-04:00","Action":"cont","Package":"command-line-arguments","Test":"TestWhatever"} 39 | {"Time":"2022-05-21T09:04:12.076524-04:00","Action":"output","Package":"command-line-arguments","Test":"TestWhatever","Output":"=== CONT TestWhatever\n"} 40 | {"Time":"2022-05-21T09:04:12.07657-04:00","Action":"output","Package":"command-line-arguments","Test":"TestWhatever","Output":" main_test.go:35: \n"} 41 | {"Time":"2022-05-21T09:04:12.076594-04:00","Action":"output","Package":"command-line-arguments","Test":"TestWhatever","Output":" \tError Trace:\tmain_test.go:35\n"} 42 | {"Time":"2022-05-21T09:04:12.076614-04:00","Action":"output","Package":"command-line-arguments","Test":"TestWhatever","Output":" \tError: \tNot equal: \n"} 43 | {"Time":"2022-05-21T09:04:12.076632-04:00","Action":"output","Package":"command-line-arguments","Test":"TestWhatever","Output":" \t \texpected: 7823456\n"} 44 | {"Time":"2022-05-21T09:04:12.076651-04:00","Action":"output","Package":"command-line-arguments","Test":"TestWhatever","Output":" \t \tactual : 1\n"} 45 | {"Time":"2022-05-21T09:04:12.076669-04:00","Action":"output","Package":"command-line-arguments","Test":"TestWhatever","Output":" \tTest: \tTestWhatever\n"} 46 | {"Time":"2022-05-21T09:04:12.076688-04:00","Action":"output","Package":"command-line-arguments","Test":"TestWhatever","Output":" \tMessages: \tnot what I was expecting\n"} 47 | {"Time":"2022-05-21T09:04:12.076716-04:00","Action":"output","Package":"command-line-arguments","Test":"TestWhatever","Output":"--- FAIL: TestWhatever (1.00s)\n"} 48 | {"Time":"2022-05-21T09:04:12.076737-04:00","Action":"output","Package":"command-line-arguments","Test":"TestWhatever/foo","Output":" --- FAIL: TestWhatever/foo (0.00s)\n"} 49 | {"Time":"2022-05-21T09:04:12.07676-04:00","Action":"output","Package":"command-line-arguments","Test":"TestWhatever/foo/bar","Output":" --- FAIL: TestWhatever/foo/bar (0.00s)\n"} 50 | {"Time":"2022-05-21T09:04:12.076781-04:00","Action":"output","Package":"command-line-arguments","Test":"TestWhatever/foo/bar/inner-bar","Output":" --- FAIL: TestWhatever/foo/bar/inner-bar (0.00s)\n"} 51 | {"Time":"2022-05-21T09:04:12.076825-04:00","Action":"fail","Package":"command-line-arguments","Test":"TestWhatever/foo/bar/inner-bar","Elapsed":0} 52 | {"Time":"2022-05-21T09:04:12.076874-04:00","Action":"fail","Package":"command-line-arguments","Test":"TestWhatever/foo/bar","Elapsed":0} 53 | {"Time":"2022-05-21T09:04:12.076892-04:00","Action":"output","Package":"command-line-arguments","Test":"TestWhatever/foo/baz","Output":" --- FAIL: TestWhatever/foo/baz (0.00s)\n"} 54 | {"Time":"2022-05-21T09:04:12.076912-04:00","Action":"output","Package":"command-line-arguments","Test":"TestWhatever/foo/baz/inner-baz","Output":" --- FAIL: TestWhatever/foo/baz/inner-baz (0.00s)\n"} 55 | {"Time":"2022-05-21T09:04:12.076934-04:00","Action":"fail","Package":"command-line-arguments","Test":"TestWhatever/foo/baz/inner-baz","Elapsed":0} 56 | {"Time":"2022-05-21T09:04:12.076952-04:00","Action":"fail","Package":"command-line-arguments","Test":"TestWhatever/foo/baz","Elapsed":0} 57 | {"Time":"2022-05-21T09:04:12.07697-04:00","Action":"fail","Package":"command-line-arguments","Test":"TestWhatever/foo","Elapsed":0} 58 | {"Time":"2022-05-21T09:04:12.076986-04:00","Action":"fail","Package":"command-line-arguments","Test":"TestWhatever","Elapsed":1} 59 | {"Time":"2022-05-21T09:04:12.077054-04:00","Action":"output","Package":"command-line-arguments","Output":"FAIL\n"} 60 | {"Time":"2022-05-21T09:04:12.078217-04:00","Action":"output","Package":"command-line-arguments","Output":"FAIL\tcommand-line-arguments\t1.127s\n"} 61 | {"Time":"2022-05-21T09:04:12.078255-04:00","Action":"fail","Package":"command-line-arguments","Elapsed":1.127} 62 | -------------------------------------------------------------------------------- /tests/testdata/follow-verbose/test_01.golden: -------------------------------------------------------------------------------- 1 | === RUN TestRace 2 | ================== 3 | WARNING: DATA RACE 4 | Write at 0x00c000090090 by goroutine 7: 5 | debug/tparse-24.TestRace.func1() 6 | /Users/michael.fridman/go/src/debug/tparse-24/some_test.go:7 +0x38 7 | 8 | Previous write at 0x00c000090090 by goroutine 6: 9 | debug/tparse-24.TestRace() 10 | /Users/michael.fridman/go/src/debug/tparse-24/some_test.go:8 +0x88 11 | testing.tRunner() 12 | /usr/local/go/src/testing/testing.go:827 +0x162 13 | 14 | Goroutine 7 (running) created at: 15 | debug/tparse-24.TestRace() 16 | /Users/michael.fridman/go/src/debug/tparse-24/some_test.go:7 +0x7a 17 | testing.tRunner() 18 | /usr/local/go/src/testing/testing.go:827 +0x162 19 | 20 | Goroutine 6 (running) created at: 21 | testing.(*T).Run() 22 | /usr/local/go/src/testing/testing.go:878 +0x650 23 | testing.runTests.func1() 24 | /usr/local/go/src/testing/testing.go:1119 +0xa8 25 | testing.tRunner() 26 | /usr/local/go/src/testing/testing.go:827 +0x162 27 | testing.runTests() 28 | /usr/local/go/src/testing/testing.go:1117 +0x4ee 29 | testing.(*M).Run() 30 | /usr/local/go/src/testing/testing.go:1034 +0x2ee 31 | main.main() 32 | _testmain.go:42 +0x221 33 | ================== 34 | --- FAIL: TestRace (0.00s) 35 | some_test.go:9: 64 36 | testing.go:771: race detected during execution of test 37 | FAIL 38 | exit status 1 39 | FAIL debug/tparse-24 0.020s 40 | -------------------------------------------------------------------------------- /tests/testdata/follow-verbose/test_01.jsonl: -------------------------------------------------------------------------------- 1 | {"Time":"2018-12-04T22:42:29.634612-05:00","Action":"run","Package":"debug/tparse-24","Test":"TestRace"} 2 | {"Time":"2018-12-04T22:42:29.634912-05:00","Action":"output","Package":"debug/tparse-24","Test":"TestRace","Output":"=== RUN TestRace\n"} 3 | {"Time":"2018-12-04T22:42:29.635129-05:00","Action":"output","Package":"debug/tparse-24","Test":"TestRace","Output":"==================\n"} 4 | {"Time":"2018-12-04T22:42:29.635163-05:00","Action":"output","Package":"debug/tparse-24","Test":"TestRace","Output":"WARNING: DATA RACE\n"} 5 | {"Time":"2018-12-04T22:42:29.635172-05:00","Action":"output","Package":"debug/tparse-24","Test":"TestRace","Output":"Write at 0x00c000090090 by goroutine 7:\n"} 6 | {"Time":"2018-12-04T22:42:29.635205-05:00","Action":"output","Package":"debug/tparse-24","Test":"TestRace","Output":" debug/tparse-24.TestRace.func1()\n"} 7 | {"Time":"2018-12-04T22:42:29.635213-05:00","Action":"output","Package":"debug/tparse-24","Test":"TestRace","Output":" /Users/michael.fridman/go/src/debug/tparse-24/some_test.go:7 +0x38\n"} 8 | {"Time":"2018-12-04T22:42:29.635226-05:00","Action":"output","Package":"debug/tparse-24","Test":"TestRace","Output":"\n"} 9 | {"Time":"2018-12-04T22:42:29.635236-05:00","Action":"output","Package":"debug/tparse-24","Test":"TestRace","Output":"Previous write at 0x00c000090090 by goroutine 6:\n"} 10 | {"Time":"2018-12-04T22:42:29.635254-05:00","Action":"output","Package":"debug/tparse-24","Test":"TestRace","Output":" debug/tparse-24.TestRace()\n"} 11 | {"Time":"2018-12-04T22:42:29.63527-05:00","Action":"output","Package":"debug/tparse-24","Test":"TestRace","Output":" /Users/michael.fridman/go/src/debug/tparse-24/some_test.go:8 +0x88\n"} 12 | {"Time":"2018-12-04T22:42:29.635283-05:00","Action":"output","Package":"debug/tparse-24","Test":"TestRace","Output":" testing.tRunner()\n"} 13 | {"Time":"2018-12-04T22:42:29.63529-05:00","Action":"output","Package":"debug/tparse-24","Test":"TestRace","Output":" /usr/local/go/src/testing/testing.go:827 +0x162\n"} 14 | {"Time":"2018-12-04T22:42:29.635302-05:00","Action":"output","Package":"debug/tparse-24","Test":"TestRace","Output":"\n"} 15 | {"Time":"2018-12-04T22:42:29.635322-05:00","Action":"output","Package":"debug/tparse-24","Test":"TestRace","Output":"Goroutine 7 (running) created at:\n"} 16 | {"Time":"2018-12-04T22:42:29.635334-05:00","Action":"output","Package":"debug/tparse-24","Test":"TestRace","Output":" debug/tparse-24.TestRace()\n"} 17 | {"Time":"2018-12-04T22:42:29.635341-05:00","Action":"output","Package":"debug/tparse-24","Test":"TestRace","Output":" /Users/michael.fridman/go/src/debug/tparse-24/some_test.go:7 +0x7a\n"} 18 | {"Time":"2018-12-04T22:42:29.635348-05:00","Action":"output","Package":"debug/tparse-24","Test":"TestRace","Output":" testing.tRunner()\n"} 19 | {"Time":"2018-12-04T22:42:29.635354-05:00","Action":"output","Package":"debug/tparse-24","Test":"TestRace","Output":" /usr/local/go/src/testing/testing.go:827 +0x162\n"} 20 | {"Time":"2018-12-04T22:42:29.635361-05:00","Action":"output","Package":"debug/tparse-24","Test":"TestRace","Output":"\n"} 21 | {"Time":"2018-12-04T22:42:29.635367-05:00","Action":"output","Package":"debug/tparse-24","Test":"TestRace","Output":"Goroutine 6 (running) created at:\n"} 22 | {"Time":"2018-12-04T22:42:29.635376-05:00","Action":"output","Package":"debug/tparse-24","Test":"TestRace","Output":" testing.(*T).Run()\n"} 23 | {"Time":"2018-12-04T22:42:29.635382-05:00","Action":"output","Package":"debug/tparse-24","Test":"TestRace","Output":" /usr/local/go/src/testing/testing.go:878 +0x650\n"} 24 | {"Time":"2018-12-04T22:42:29.635388-05:00","Action":"output","Package":"debug/tparse-24","Test":"TestRace","Output":" testing.runTests.func1()\n"} 25 | {"Time":"2018-12-04T22:42:29.635397-05:00","Action":"output","Package":"debug/tparse-24","Test":"TestRace","Output":" /usr/local/go/src/testing/testing.go:1119 +0xa8\n"} 26 | {"Time":"2018-12-04T22:42:29.635403-05:00","Action":"output","Package":"debug/tparse-24","Test":"TestRace","Output":" testing.tRunner()\n"} 27 | {"Time":"2018-12-04T22:42:29.635409-05:00","Action":"output","Package":"debug/tparse-24","Test":"TestRace","Output":" /usr/local/go/src/testing/testing.go:827 +0x162\n"} 28 | {"Time":"2018-12-04T22:42:29.635415-05:00","Action":"output","Package":"debug/tparse-24","Test":"TestRace","Output":" testing.runTests()\n"} 29 | {"Time":"2018-12-04T22:42:29.635422-05:00","Action":"output","Package":"debug/tparse-24","Test":"TestRace","Output":" /usr/local/go/src/testing/testing.go:1117 +0x4ee\n"} 30 | {"Time":"2018-12-04T22:42:29.635448-05:00","Action":"output","Package":"debug/tparse-24","Test":"TestRace","Output":" testing.(*M).Run()\n"} 31 | {"Time":"2018-12-04T22:42:29.635465-05:00","Action":"output","Package":"debug/tparse-24","Test":"TestRace","Output":" /usr/local/go/src/testing/testing.go:1034 +0x2ee\n"} 32 | {"Time":"2018-12-04T22:42:29.635472-05:00","Action":"output","Package":"debug/tparse-24","Test":"TestRace","Output":" main.main()\n"} 33 | {"Time":"2018-12-04T22:42:29.635478-05:00","Action":"output","Package":"debug/tparse-24","Test":"TestRace","Output":" _testmain.go:42 +0x221\n"} 34 | {"Time":"2018-12-04T22:42:29.635485-05:00","Action":"output","Package":"debug/tparse-24","Test":"TestRace","Output":"==================\n"} 35 | {"Time":"2018-12-04T22:42:29.635807-05:00","Action":"output","Package":"debug/tparse-24","Test":"TestRace","Output":"--- FAIL: TestRace (0.00s)\n"} 36 | {"Time":"2018-12-04T22:42:29.635821-05:00","Action":"output","Package":"debug/tparse-24","Test":"TestRace","Output":" some_test.go:9: 64\n"} 37 | {"Time":"2018-12-04T22:42:29.635829-05:00","Action":"output","Package":"debug/tparse-24","Test":"TestRace","Output":" testing.go:771: race detected during execution of test\n"} 38 | {"Time":"2018-12-04T22:42:29.636065-05:00","Action":"fail","Package":"debug/tparse-24","Test":"TestRace","Elapsed":0} 39 | {"Time":"2018-12-04T22:42:29.636085-05:00","Action":"output","Package":"debug/tparse-24","Output":"FAIL\n"} 40 | {"Time":"2018-12-04T22:42:29.63706-05:00","Action":"output","Package":"debug/tparse-24","Output":"exit status 1\n"} 41 | {"Time":"2018-12-04T22:42:29.637098-05:00","Action":"output","Package":"debug/tparse-24","Output":"FAIL\tdebug/tparse-24\t0.020s\n"} 42 | {"Time":"2018-12-04T22:42:29.637108-05:00","Action":"fail","Package":"debug/tparse-24","Elapsed":0.02} -------------------------------------------------------------------------------- /tests/testdata/follow-verbose/test_02.golden: -------------------------------------------------------------------------------- 1 | ? github.com/mfridman/rover [no test files] 2 | ? github.com/mfridman/rover/cmd/assets [no test files] 3 | ? github.com/mfridman/rover/cmd/roverd [no test files] 4 | === RUN TestStringPager 5 | === RUN TestStringPager/PageableWithLimit 6 | === RUN TestStringPager/NonPageable 7 | --- PASS: TestStringPager (0.00s) 8 | --- PASS: TestStringPager/PageableWithLimit (0.00s) 9 | --- PASS: TestStringPager/NonPageable (0.00s) 10 | PASS 11 | coverage: 100.0% of statements 12 | ok github.com/mfridman/rover/pkg/paging/stringpager 0.033s coverage: 100.0% of statements 13 | ? github.com/mfridman/rover/pkg/web [no test files] 14 | ? github.com/mfridman/rover/rpc/mailer [no test files] 15 | ? github.com/mfridman/rover/smtp [no test files] 16 | ? github.com/mfridman/rover/smtp/smtpserver [no test files] 17 | ? github.com/mfridman/rover/storage [no test files] 18 | ? github.com/mfridman/rover/storage/badger [no test files] 19 | === RUN TestNameFormat 20 | --- PASS: TestNameFormat (0.00s) 21 | PASS 22 | coverage: 0.0% of statements 23 | ok github.com/mfridman/rover/tests/helper 0.030s coverage: 0.0% of statements 24 | 2018/12/16 20:40:12 Replaying from value pointer: {Fid:0 Len:0 Offset:0} 25 | 2018/12/16 20:40:12 Iterating file id: 0 26 | 2018/12/16 20:40:12 Iteration took: 80.538µs 27 | 2018/12/16 20:40:12 Replaying from value pointer: {Fid:0 Len:0 Offset:0} 28 | 2018/12/16 20:40:12 Iterating file id: 0 29 | 2018/12/16 20:40:12 Iteration took: 43.759µs 30 | === RUN TestAddEmail 31 | === PAUSE TestAddEmail 32 | === RUN TestDeleteEmail 33 | === PAUSE TestDeleteEmail 34 | === RUN TestMarkAsRead 35 | === PAUSE TestMarkAsRead 36 | === RUN TestMarkAsUnRead 37 | === PAUSE TestMarkAsUnRead 38 | === CONT TestAddEmail 39 | === CONT TestMarkAsUnRead 40 | === CONT TestMarkAsRead 41 | === CONT TestDeleteEmail 42 | === RUN TestAddEmail/retrieveEmail 43 | --- PASS: TestAddEmail (0.00s) 44 | --- PASS: TestAddEmail/retrieveEmail (0.00s) 45 | --- PASS: TestDeleteEmail (0.00s) 46 | --- PASS: TestMarkAsRead (0.00s) 47 | --- PASS: TestMarkAsUnRead (0.00s) 48 | PASS 49 | coverage: 0.0% of statements 50 | server closed 51 | ok github.com/mfridman/rover/tests 0.046s coverage: 0.0% of statements 52 | -------------------------------------------------------------------------------- /tests/testdata/follow-verbose/test_04.golden: -------------------------------------------------------------------------------- 1 | === RUN TestFollow 2 | --- PASS: TestFollow (0.00s) 3 | testing: warning: no tests to run 4 | PASS 5 | ok github.com/mfridman/tparse/tests (cached) [no tests to run] 6 | -------------------------------------------------------------------------------- /tests/testdata/follow-verbose/test_04.jsonl: -------------------------------------------------------------------------------- 1 | {"Time":"2022-05-15T22:44:38.670279-04:00","Action":"run","Package":"github.com/mfridman/tparse/tests","Test":"TestFollow"} 2 | {"Time":"2022-05-15T22:44:38.670387-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestFollow","Output":"=== RUN TestFollow\n"} 3 | {"Time":"2022-05-15T22:44:38.670399-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestFollow","Output":"--- PASS: TestFollow (0.00s)\n"} 4 | {"Time":"2022-05-15T22:44:38.670403-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestFollow","Output":"testing: warning: no tests to run\n"} 5 | {"Time":"2022-05-15T22:44:38.670406-04:00","Action":"pass","Package":"github.com/mfridman/tparse/tests","Test":"TestFollow","Elapsed":0} 6 | {"Time":"2022-05-15T22:44:38.67041-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Output":"PASS\n"} 7 | {"Time":"2022-05-15T22:44:38.670413-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Output":"ok \tgithub.com/mfridman/tparse/tests\t(cached) [no tests to run]\n"} 8 | {"Time":"2022-05-15T22:44:38.67042-04:00","Action":"pass","Package":"github.com/mfridman/tparse/tests","Elapsed":0} 9 | -------------------------------------------------------------------------------- /tests/testdata/follow-verbose/test_05.golden: -------------------------------------------------------------------------------- 1 | It 2 | is 3 | a 4 | long 5 | established 6 | fact 7 | that 8 | a 9 | reader 10 | will 11 | be 12 | distracted 13 | by 14 | the 15 | readable 16 | content 17 | of 18 | a 19 | page 20 | when 21 | looking 22 | at 23 | its 24 | layout. 25 | The 26 | point 27 | of 28 | using 29 | Lorem 30 | Ipsum 31 | is 32 | that 33 | it 34 | has 35 | a 36 | more-or-less 37 | normal 38 | distribution 39 | of 40 | letters, 41 | as 42 | opposed 43 | to 44 | using 45 | 'Content 46 | here, 47 | content 48 | here', 49 | making 50 | it 51 | -------------------------------------------------------------------------------- /tests/testdata/follow-verbose/test_05.jsonl: -------------------------------------------------------------------------------- 1 | It 2 | is 3 | a 4 | long 5 | established 6 | fact 7 | that 8 | a 9 | reader 10 | will 11 | be 12 | distracted 13 | by 14 | the 15 | readable 16 | content 17 | of 18 | a 19 | page 20 | when 21 | looking 22 | at 23 | its 24 | layout. 25 | The 26 | point 27 | of 28 | using 29 | Lorem 30 | Ipsum 31 | is 32 | that 33 | it 34 | has 35 | a 36 | more-or-less 37 | normal 38 | distribution 39 | of 40 | letters, 41 | as 42 | opposed 43 | to 44 | using 45 | 'Content 46 | here, 47 | content 48 | here', 49 | making 50 | it 51 | look 52 | like 53 | readable 54 | English. 55 | Many 56 | desktop 57 | publishing 58 | packages 59 | and 60 | web 61 | page 62 | editors 63 | now 64 | use 65 | Lorem 66 | Ipsum 67 | as 68 | their 69 | default 70 | model 71 | text, 72 | and 73 | a 74 | search 75 | for 76 | 'lorem 77 | ipsum' 78 | will 79 | uncover 80 | many 81 | web 82 | sites 83 | still 84 | in 85 | their 86 | infancy. 87 | Various 88 | versions 89 | have 90 | evolved 91 | over 92 | the 93 | years, 94 | sometimes 95 | by 96 | accident, 97 | sometimes 98 | on 99 | purpose 100 | (injected 101 | humour 102 | and 103 | the 104 | like). 105 | It 106 | is 107 | a 108 | long 109 | established 110 | fact 111 | that 112 | a 113 | reader 114 | will 115 | be 116 | distracted 117 | by 118 | the 119 | readable 120 | content 121 | of 122 | a 123 | page 124 | when 125 | looking 126 | at 127 | its 128 | layout. 129 | The 130 | point 131 | of 132 | using 133 | Lorem 134 | Ipsum 135 | is 136 | that 137 | it 138 | has 139 | a 140 | more-or-less 141 | normal 142 | distribution 143 | of 144 | letters, 145 | as 146 | opposed 147 | to 148 | using 149 | 'Content 150 | here, 151 | content 152 | here', 153 | making 154 | it 155 | look 156 | like 157 | readable 158 | English. 159 | Many 160 | desktop 161 | publishing 162 | packages 163 | and 164 | web 165 | page 166 | editors 167 | now 168 | use 169 | Lorem 170 | Ipsum 171 | as 172 | their 173 | default 174 | model 175 | text, 176 | and 177 | a 178 | search 179 | for 180 | 'lorem 181 | ipsum' 182 | will 183 | uncover 184 | many 185 | web 186 | sites 187 | still 188 | in 189 | their 190 | infancy. 191 | Various 192 | versions 193 | have 194 | evolved 195 | over 196 | the 197 | years, 198 | sometimes 199 | by 200 | accident, 201 | sometimes 202 | on 203 | purpose 204 | (injected 205 | humour 206 | and 207 | the 208 | like). 209 | -------------------------------------------------------------------------------- /tests/testdata/follow-verbose/test_06.golden: -------------------------------------------------------------------------------- 1 | # github.com/marco-m/tparse-bugs [github.com/marco-m/tparse-bugs.test] 2 | ./a_test.go:6:2: undefined: hello 3 | FAIL github.com/marco-m/tparse-bugs [build failed] 4 | === RUN TestB 5 | --- PASS: TestB (0.00s) 6 | PASS 7 | ok github.com/marco-m/tparse-bugs/b 0.098s 8 | -------------------------------------------------------------------------------- /tests/testdata/follow-verbose/test_06.jsonl: -------------------------------------------------------------------------------- 1 | # github.com/marco-m/tparse-bugs [github.com/marco-m/tparse-bugs.test] 2 | ./a_test.go:6:2: undefined: hello 3 | FAIL github.com/marco-m/tparse-bugs [build failed] 4 | {"Time":"2022-05-25T23:11:20.775252-04:00","Action":"run","Package":"github.com/marco-m/tparse-bugs/b","Test":"TestB"} 5 | {"Time":"2022-05-25T23:11:20.775449-04:00","Action":"output","Package":"github.com/marco-m/tparse-bugs/b","Test":"TestB","Output":"=== RUN TestB\n"} 6 | {"Time":"2022-05-25T23:11:20.775462-04:00","Action":"output","Package":"github.com/marco-m/tparse-bugs/b","Test":"TestB","Output":"--- PASS: TestB (0.00s)\n"} 7 | {"Time":"2022-05-25T23:11:20.775464-04:00","Action":"pass","Package":"github.com/marco-m/tparse-bugs/b","Test":"TestB","Elapsed":0} 8 | {"Time":"2022-05-25T23:11:20.775467-04:00","Action":"output","Package":"github.com/marco-m/tparse-bugs/b","Output":"PASS\n"} 9 | {"Time":"2022-05-25T23:11:20.7755-04:00","Action":"output","Package":"github.com/marco-m/tparse-bugs/b","Output":"ok \tgithub.com/marco-m/tparse-bugs/b\t0.098s\n"} 10 | {"Time":"2022-05-25T23:11:20.77551-04:00","Action":"pass","Package":"github.com/marco-m/tparse-bugs/b","Elapsed":0.098} -------------------------------------------------------------------------------- /tests/testdata/follow/test_01.golden: -------------------------------------------------------------------------------- 1 | fmt_test.go:1457: skipping; GOMAXPROCS>1 2 | ok fmt 0.143s 3 | -------------------------------------------------------------------------------- /tests/testdata/outcome/test_01.jsonl: -------------------------------------------------------------------------------- 1 | {"Time":"2018-10-17T22:05:12.535482-04:00","Action":"run","Package":"github.com/mfridman/tparse/tests","Test":"TestStatus"} 2 | {"Time":"2018-10-17T22:05:12.535868-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestStatus","Output":"=== RUN TestStatus\n"} 3 | {"Time":"2018-10-17T22:05:12.535902-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestStatus","Output":"=== PAUSE TestStatus\n"} 4 | {"Time":"2018-10-17T22:05:12.535919-04:00","Action":"pause","Package":"github.com/mfridman/tparse/tests","Test":"TestStatus"} 5 | {"Time":"2018-10-17T22:05:12.535942-04:00","Action":"cont","Package":"github.com/mfridman/tparse/tests","Test":"TestStatus"} 6 | {"Time":"2018-10-17T22:05:12.535959-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestStatus","Output":"=== CONT TestStatus\n"} 7 | {"Time":"2018-10-17T22:05:12.541357-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestStatus","Output":"--- FAIL: TestStatus (0.01s)\n"} 8 | {"Time":"2018-10-17T22:05:12.54139-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestStatus","Output":" status_test.go:56: log: file: package_skip02.json\n"} 9 | {"Time":"2018-10-17T22:05:12.541412-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestStatus","Output":" status_test.go:57: failed package summary action: got \"FAIL\", want \"PASS\"\n"} 10 | {"Time":"2018-10-17T22:05:12.541432-04:00","Action":"fail","Package":"github.com/mfridman/tparse/tests","Test":"TestStatus","Elapsed":0.01} 11 | {"Time":"2018-10-17T22:05:12.541458-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Output":"FAIL\n"} 12 | {"Time":"2018-10-17T22:05:12.541857-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Output":"FAIL\tgithub.com/mfridman/tparse/tests\t0.017s\n"} 13 | {"Time":"2018-10-17T22:05:12.541908-04:00","Action":"fail","Package":"github.com/mfridman/tparse/tests","Elapsed":0.017} 14 | -------------------------------------------------------------------------------- /tests/testdata/outcome/test_02.jsonl: -------------------------------------------------------------------------------- 1 | {"Time":"2018-10-28T00:06:53.478265-04:00","Action":"output","Package":"github.com/astromail/rover","Output":"? \tgithub.com/astromail/rover\t[no test files]\n"} 2 | {"Time":"2018-10-28T00:06:53.478512-04:00","Action":"skip","Package":"github.com/astromail/rover","Elapsed":0} 3 | {"Time":"2018-10-28T00:06:53.511804-04:00","Action":"output","Package":"github.com/astromail/rover/cmd/roverd","Output":"? \tgithub.com/astromail/rover/cmd/roverd\t[no test files]\n"} 4 | {"Time":"2018-10-28T00:06:53.511862-04:00","Action":"skip","Package":"github.com/astromail/rover/cmd/roverd","Elapsed":0} 5 | {"Time":"2018-10-28T00:06:53.511882-04:00","Action":"output","Package":"github.com/astromail/rover/errors","Output":"? \tgithub.com/astromail/rover/errors\t[no test files]\n"} 6 | {"Time":"2018-10-28T00:06:53.511891-04:00","Action":"skip","Package":"github.com/astromail/rover/errors","Elapsed":0} 7 | {"Time":"2018-10-28T00:06:53.511907-04:00","Action":"output","Package":"github.com/astromail/rover/smtp","Output":"? \tgithub.com/astromail/rover/smtp\t[no test files]\n"} 8 | {"Time":"2018-10-28T00:06:53.511916-04:00","Action":"skip","Package":"github.com/astromail/rover/smtp","Elapsed":0} 9 | {"Time":"2018-10-28T00:06:53.511933-04:00","Action":"output","Package":"github.com/astromail/rover/storage","Output":"? \tgithub.com/astromail/rover/storage\t[no test files]\n"} 10 | {"Time":"2018-10-28T00:06:53.511942-04:00","Action":"skip","Package":"github.com/astromail/rover/storage","Elapsed":0} 11 | {"Time":"2018-10-28T00:06:53.511957-04:00","Action":"output","Package":"github.com/astromail/rover/storage/badger","Output":"? \tgithub.com/astromail/rover/storage/badger\t[no test files]\n"} 12 | {"Time":"2018-10-28T00:06:53.511969-04:00","Action":"skip","Package":"github.com/astromail/rover/storage/badger","Elapsed":0} 13 | {"Time":"2018-10-28T00:06:54.007207-04:00","Action":"output","Package":"github.com/astromail/rover/tests","Output":"2018/10/28 00:06:54 Replaying from value pointer: {Fid:0 Len:0 Offset:0}\n"} 14 | {"Time":"2018-10-28T00:06:54.007282-04:00","Action":"output","Package":"github.com/astromail/rover/tests","Output":"2018/10/28 00:06:54 Iterating file id: 0\n"} 15 | {"Time":"2018-10-28T00:06:54.007321-04:00","Action":"output","Package":"github.com/astromail/rover/tests","Output":"2018/10/28 00:06:54 Replaying from value pointer: {Fid:0 Len:0 Offset:0}\n"} 16 | {"Time":"2018-10-28T00:06:54.00733-04:00","Action":"output","Package":"github.com/astromail/rover/tests","Output":"2018/10/28 00:06:54 Iterating file id: 0\n"} 17 | {"Time":"2018-10-28T00:06:54.007389-04:00","Action":"output","Package":"github.com/astromail/rover/tests","Output":"2018/10/28 00:06:54 Iteration took: 144.649µs\n"} 18 | {"Time":"2018-10-28T00:06:54.007462-04:00","Action":"output","Package":"github.com/astromail/rover/tests","Output":"2018/10/28 00:06:54 Iteration took: 259.316µs\n"} 19 | {"Time":"2018-10-28T00:06:54.503762-04:00","Action":"run","Package":"github.com/astromail/rover/tests","Test":"TestCatch"} 20 | {"Time":"2018-10-28T00:06:54.503795-04:00","Action":"output","Package":"github.com/astromail/rover/tests","Test":"TestCatch","Output":"=== RUN TestCatch\n"} 21 | {"Time":"2018-10-28T00:06:54.503829-04:00","Action":"run","Package":"github.com/astromail/rover/tests","Test":"TestCatch/catchAndRetrieve"} 22 | {"Time":"2018-10-28T00:06:54.503838-04:00","Action":"output","Package":"github.com/astromail/rover/tests","Test":"TestCatch/catchAndRetrieve","Output":"=== RUN TestCatch/catchAndRetrieve\n"} 23 | {"Time":"2018-10-28T00:06:54.507445-04:00","Action":"output","Package":"github.com/astromail/rover/tests","Test":"TestCatch","Output":"--- FAIL: TestCatch (0.00s)\n"} 24 | {"Time":"2018-10-28T00:06:54.507485-04:00","Action":"output","Package":"github.com/astromail/rover/tests","Test":"TestCatch/catchAndRetrieve","Output":" --- FAIL: TestCatch/catchAndRetrieve (0.00s)\n"} 25 | {"Time":"2018-10-28T00:06:54.507507-04:00","Action":"output","Package":"github.com/astromail/rover/tests","Test":"TestCatch/catchAndRetrieve","Output":" catch_test.go:29: got id \"ad0892h\", want empty id\n"} 26 | {"Time":"2018-10-28T00:06:54.507517-04:00","Action":"output","Package":"github.com/astromail/rover/tests","Test":"TestCatch/catchAndRetrieve","Output":" catch_test.go:37: email id does not match: got \"69c47b65-0ad5-47d5-9346-f4f8bd22c56e\", want \"oops@example.com\"\n"} 27 | {"Time":"2018-10-28T00:06:54.507528-04:00","Action":"output","Package":"github.com/astromail/rover/tests","Test":"TestCatch/catchAndRetrieve","Output":" catch_test.go:41: failed to mark email as read: failed to mark email id \"123\" as read: PUT \"http://localhost:8026/api/v1/emails/123/read\": expecting valid status code, got 500 Internal Server Error\n"} 28 | {"Time":"2018-10-28T00:06:54.507536-04:00","Action":"fail","Package":"github.com/astromail/rover/tests","Test":"TestCatch/catchAndRetrieve","Elapsed":0} 29 | {"Time":"2018-10-28T00:06:54.507544-04:00","Action":"fail","Package":"github.com/astromail/rover/tests","Test":"TestCatch","Elapsed":0} 30 | {"Time":"2018-10-28T00:06:54.507549-04:00","Action":"run","Package":"github.com/astromail/rover/tests","Test":"TestNameFormat"} 31 | {"Time":"2018-10-28T00:06:54.507554-04:00","Action":"output","Package":"github.com/astromail/rover/tests","Test":"TestNameFormat","Output":"=== RUN TestNameFormat\n"} 32 | {"Time":"2018-10-28T00:06:54.507571-04:00","Action":"output","Package":"github.com/astromail/rover/tests","Test":"TestNameFormat","Output":"--- PASS: TestNameFormat (0.00s)\n"} 33 | {"Time":"2018-10-28T00:06:54.507578-04:00","Action":"pass","Package":"github.com/astromail/rover/tests","Test":"TestNameFormat","Elapsed":0} 34 | {"Time":"2018-10-28T00:06:54.507583-04:00","Action":"output","Package":"github.com/astromail/rover/tests","Output":"FAIL\n"} 35 | {"Time":"2018-10-28T00:06:54.50967-04:00","Action":"output","Package":"github.com/astromail/rover/tests","Output":"FAIL\tgithub.com/astromail/rover/tests\t0.532s\n"} 36 | {"Time":"2018-10-28T00:06:54.509705-04:00","Action":"fail","Package":"github.com/astromail/rover/tests","Elapsed":0.532} -------------------------------------------------------------------------------- /tests/testdata/outcome/test_04.jsonl: -------------------------------------------------------------------------------- 1 | {"Time":"2018-10-15T22:57:27.729679-04:00","Action":"output","Package":"github.com/astromail/rover/tests","Output":"2018/10/15 22:57:27 Replaying from value pointer: {Fid:0 Len:0 Offset:0}\n"} 2 | {"Time":"2018-10-15T22:57:27.730067-04:00","Action":"output","Package":"github.com/astromail/rover/tests","Output":"2018/10/15 22:57:27 Iterating file id: 0\n"} 3 | {"Time":"2018-10-15T22:57:27.730109-04:00","Action":"output","Package":"github.com/astromail/rover/tests","Output":"2018/10/15 22:57:27 Iteration took: 49.09µs\n"} 4 | {"Time":"2018-10-15T22:57:27.732221-04:00","Action":"output","Package":"github.com/astromail/rover/tests","Output":"2018/10/15 22:57:27 Replaying from value pointer: {Fid:0 Len:0 Offset:0}\n"} 5 | {"Time":"2018-10-15T22:57:27.732263-04:00","Action":"output","Package":"github.com/astromail/rover/tests","Output":"2018/10/15 22:57:27 Iterating file id: 0\n"} 6 | {"Time":"2018-10-15T22:57:27.732285-04:00","Action":"output","Package":"github.com/astromail/rover/tests","Output":"2018/10/15 22:57:27 Iteration took: 63.3µs\n"} 7 | {"Time":"2018-10-15T22:57:28.228957-04:00","Action":"run","Package":"github.com/astromail/rover/tests","Test":"TestCatch"} 8 | {"Time":"2018-10-15T22:57:28.229033-04:00","Action":"output","Package":"github.com/astromail/rover/tests","Test":"TestCatch","Output":"=== RUN TestCatch\n"} 9 | {"Time":"2018-10-15T22:57:28.229083-04:00","Action":"run","Package":"github.com/astromail/rover/tests","Test":"TestCatch/catchAndRetrieve"} 10 | {"Time":"2018-10-15T22:57:28.229112-04:00","Action":"output","Package":"github.com/astromail/rover/tests","Test":"TestCatch/catchAndRetrieve","Output":"=== RUN TestCatch/catchAndRetrieve\n"} 11 | {"Time":"2018-10-15T22:57:28.236421-04:00","Action":"output","Package":"github.com/astromail/rover/tests","Test":"TestCatch","Output":"--- PASS: TestCatch (0.01s)\n"} 12 | {"Time":"2018-10-15T22:57:28.236461-04:00","Action":"output","Package":"github.com/astromail/rover/tests","Test":"TestCatch/catchAndRetrieve","Output":" --- PASS: TestCatch/catchAndRetrieve (0.01s)\n"} 13 | {"Time":"2018-10-15T22:57:28.236492-04:00","Action":"pass","Package":"github.com/astromail/rover/tests","Test":"TestCatch/catchAndRetrieve","Elapsed":0.01} 14 | {"Time":"2018-10-15T22:57:28.236516-04:00","Action":"pass","Package":"github.com/astromail/rover/tests","Test":"TestCatch","Elapsed":0.01} 15 | {"Time":"2018-10-15T22:57:28.236568-04:00","Action":"run","Package":"github.com/astromail/rover/tests","Test":"TestNameFormat"} 16 | {"Time":"2018-10-15T22:57:28.236597-04:00","Action":"output","Package":"github.com/astromail/rover/tests","Test":"TestNameFormat","Output":"=== RUN TestNameFormat\n"} 17 | {"Time":"2018-10-15T22:57:28.236633-04:00","Action":"output","Package":"github.com/astromail/rover/tests","Test":"TestNameFormat","Output":"--- PASS: TestNameFormat (0.00s)\n"} 18 | {"Time":"2018-10-15T22:57:28.236657-04:00","Action":"pass","Package":"github.com/astromail/rover/tests","Test":"TestNameFormat","Elapsed":0} 19 | {"Time":"2018-10-15T22:57:28.236673-04:00","Action":"output","Package":"github.com/astromail/rover/tests","Output":"PASS\n"} 20 | {"Time":"2018-10-15T22:57:28.237956-04:00","Action":"output","Package":"github.com/astromail/rover/tests","Output":"ok \tgithub.com/astromail/rover/tests\t0.582s\n"} 21 | {"Time":"2018-10-15T22:57:28.23799-04:00","Action":"pass","Package":"github.com/astromail/rover/tests","Elapsed":0.582} 22 | -------------------------------------------------------------------------------- /tests/testdata/outcome/test_05.jsonl: -------------------------------------------------------------------------------- 1 | {"Time":"2018-10-15T23:00:27.929094-04:00","Action":"output","Package":"github.com/astromail/rover/tests","Output":"2018/10/15 23:00:27 Replaying from value pointer: {Fid:0 Len:0 Offset:0}\n"} 2 | {"Time":"2018-10-15T23:00:27.929492-04:00","Action":"output","Package":"github.com/astromail/rover/tests","Output":"2018/10/15 23:00:27 Iterating file id: 0\n"} 3 | {"Time":"2018-10-15T23:00:27.929529-04:00","Action":"output","Package":"github.com/astromail/rover/tests","Output":"2018/10/15 23:00:27 Replaying from value pointer: {Fid:0 Len:0 Offset:0}\n"} 4 | {"Time":"2018-10-15T23:00:27.929543-04:00","Action":"output","Package":"github.com/astromail/rover/tests","Output":"2018/10/15 23:00:27 Iterating file id: 0\n"} 5 | {"Time":"2018-10-15T23:00:27.929572-04:00","Action":"output","Package":"github.com/astromail/rover/tests","Output":"2018/10/15 23:00:27 Iteration took: 73.332µs\n"} 6 | {"Time":"2018-10-15T23:00:27.929588-04:00","Action":"output","Package":"github.com/astromail/rover/tests","Output":"2018/10/15 23:00:27 Iteration took: 39.372µs\n"} 7 | {"Time":"2018-10-15T23:00:28.430669-04:00","Action":"run","Package":"github.com/astromail/rover/tests","Test":"TestNameFormat"} 8 | {"Time":"2018-10-15T23:00:28.430728-04:00","Action":"output","Package":"github.com/astromail/rover/tests","Test":"TestNameFormat","Output":"=== RUN TestNameFormat\n"} 9 | {"Time":"2018-10-15T23:00:28.43076-04:00","Action":"output","Package":"github.com/astromail/rover/tests","Test":"TestNameFormat","Output":"--- SKIP: TestNameFormat (0.00s)\n"} 10 | {"Time":"2018-10-15T23:00:28.430781-04:00","Action":"output","Package":"github.com/astromail/rover/tests","Test":"TestNameFormat","Output":" testutil_test.go:9: \n"} 11 | {"Time":"2018-10-15T23:00:28.430798-04:00","Action":"skip","Package":"github.com/astromail/rover/tests","Test":"TestNameFormat","Elapsed":0} 12 | {"Time":"2018-10-15T23:00:28.430825-04:00","Action":"output","Package":"github.com/astromail/rover/tests","Output":"PASS\n"} 13 | {"Time":"2018-10-15T23:00:28.432239-04:00","Action":"output","Package":"github.com/astromail/rover/tests","Output":"ok \tgithub.com/astromail/rover/tests\t0.530s\n"} 14 | {"Time":"2018-10-15T23:00:28.436039-04:00","Action":"pass","Package":"github.com/astromail/rover/tests","Elapsed":0.534} 15 | -------------------------------------------------------------------------------- /tests/testdata/outcome/test_06.jsonl: -------------------------------------------------------------------------------- 1 | {"Time":"2018-10-17T22:01:48.798229-04:00","Action":"output","Package":"fmt","Output":"testing: warning: no tests to run\n"} 2 | {"Time":"2018-10-17T22:01:48.798553-04:00","Action":"output","Package":"fmt","Output":"PASS\n"} 3 | {"Time":"2018-10-17T22:01:48.798617-04:00","Action":"output","Package":"fmt","Output":"ok \tfmt\t0.011s [no tests to run]\n"} 4 | {"Time":"2018-10-17T22:01:48.798647-04:00","Action":"pass","Package":"fmt","Elapsed":0.011} 5 | -------------------------------------------------------------------------------- /tests/testdata/outcome/test_07.jsonl: -------------------------------------------------------------------------------- 1 | {"Time":"2018-10-20T13:32:27.452736-04:00","Action":"output","Package":"debug/errorcause","Output":"? \tdebug/errorcause\t[no test files]\n"} 2 | {"Time":"2018-10-20T13:32:27.452995-04:00","Action":"skip","Package":"debug/errorcause","Elapsed":0} -------------------------------------------------------------------------------- /tests/testdata/outcome/test_08.jsonl: -------------------------------------------------------------------------------- 1 | {"Time":"2018-10-27T23:15:21.909874-04:00","Action":"output","Package":"github.com/awesome/pkg","Output":"PASS\n"} 2 | {"Time":"2018-10-27T23:15:21.914955-04:00","Action":"output","Package":"github.com/awesome/pkg","Output":"ok \tgithub.com/awesome/pkg\t4.543s [no tests to run]\n"} 3 | {"Time":"2018-10-27T23:15:21.914992-04:00","Action":"pass","Package":"github.com/awesome/pkg","Elapsed":4.543} -------------------------------------------------------------------------------- /tests/testdata/panic/test_03.jsonl: -------------------------------------------------------------------------------- 1 | {"Time":"2018-10-21T22:15:24.47322-04:00","Action":"run","Package":"github.com/mfridman/tparse/tests","Test":"TestStatus"} 2 | {"Time":"2018-10-21T22:15:24.473515-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestStatus","Output":"=== RUN TestStatus\n"} 3 | {"Time":"2018-10-21T22:15:24.473542-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestStatus","Output":"=== PAUSE TestStatus\n"} 4 | {"Time":"2018-10-21T22:15:24.47355-04:00","Action":"pause","Package":"github.com/mfridman/tparse/tests","Test":"TestStatus"} 5 | {"Time":"2018-10-21T22:15:24.473565-04:00","Action":"cont","Package":"github.com/mfridman/tparse/tests","Test":"TestStatus"} 6 | {"Time":"2018-10-21T22:15:24.473573-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestStatus","Output":"=== CONT TestStatus\n"} 7 | {"Time":"2018-10-21T22:15:24.473588-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestStatus","Output":"--- FAIL: TestStatus (0.00s)\n"} 8 | {"Time":"2018-10-21T22:15:24.47549-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestStatus","Output":"panic: runtime error: invalid memory address or nil pointer dereference [recovered]\n"} 9 | {"Time":"2018-10-21T22:15:24.475513-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestStatus","Output":"\tpanic: runtime error: invalid memory address or nil pointer dereference\n"} 10 | {"Time":"2018-10-21T22:15:24.475532-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestStatus","Output":"[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x1112389]\n"} 11 | {"Time":"2018-10-21T22:15:24.47554-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestStatus","Output":"\n"} 12 | {"Time":"2018-10-21T22:15:24.475549-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestStatus","Output":"goroutine 18 [running]:\n"} 13 | {"Time":"2018-10-21T22:15:24.475559-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestStatus","Output":"testing.tRunner.func1(0xc0000b6300)\n"} 14 | {"Time":"2018-10-21T22:15:24.475567-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestStatus","Output":"\t/usr/local/go/src/testing/testing.go:792 +0x387\n"} 15 | {"Time":"2018-10-21T22:15:24.475581-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestStatus","Output":"panic(0x1137980, 0x1262100)\n"} 16 | {"Time":"2018-10-21T22:15:24.475651-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestStatus","Output":"\t/usr/local/go/src/runtime/panic.go:513 +0x1b9\n"} 17 | {"Time":"2018-10-21T22:15:24.475682-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestStatus","Output":"github.com/mfridman/tparse/tests_test.TestStatus.func1(0x116177e, 0xe, 0x1185120, 0xc00006c820, 0x0, 0x0, 0x0, 0xc00002e6c0)\n"} 18 | {"Time":"2018-10-21T22:15:24.475695-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestStatus","Output":"\t/Users/michael.fridman/go/src/github.com/mfridman/tparse/tests/status_test.go:26 +0x69\n"} 19 | {"Time":"2018-10-21T22:15:24.475749-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestStatus","Output":"path/filepath.walk(0x116177e, 0xe, 0x1185120, 0xc00006c820, 0xc0000666a0, 0x0, 0x10)\n"} 20 | {"Time":"2018-10-21T22:15:24.475773-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestStatus","Output":"\t/usr/local/go/src/path/filepath/path.go:362 +0xf6\n"} 21 | {"Time":"2018-10-21T22:15:24.475781-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestStatus","Output":"path/filepath.Walk(0x116177e, 0xe, 0xc0000666a0, 0x1c338b20, 0xf815f)\n"} 22 | {"Time":"2018-10-21T22:15:24.475788-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestStatus","Output":"\t/usr/local/go/src/path/filepath/path.go:404 +0x105\n"} 23 | {"Time":"2018-10-21T22:15:24.475798-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestStatus","Output":"github.com/mfridman/tparse/tests_test.TestStatus(0xc0000b6300)\n"} 24 | {"Time":"2018-10-21T22:15:24.475936-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestStatus","Output":"\t/Users/michael.fridman/go/src/github.com/mfridman/tparse/tests/status_test.go:19 +0x7e\n"} 25 | {"Time":"2018-10-21T22:15:24.475945-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestStatus","Output":"testing.tRunner(0xc0000b6300, 0x116ab18)\n"} 26 | {"Time":"2018-10-21T22:15:24.475952-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestStatus","Output":"\t/usr/local/go/src/testing/testing.go:827 +0xbf\n"} 27 | {"Time":"2018-10-21T22:15:24.475959-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestStatus","Output":"created by testing.(*T).Run\n"} 28 | {"Time":"2018-10-21T22:15:24.475975-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestStatus","Output":"\t/usr/local/go/src/testing/testing.go:878 +0x353\n"} 29 | {"Time":"2018-10-21T22:15:24.476216-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestStatus","Output":"FAIL\tgithub.com/mfridman/tparse/tests\t0.014s\n"} 30 | {"Time":"2018-10-21T22:15:24.476261-04:00","Action":"fail","Package":"github.com/mfridman/tparse/tests","Test":"TestStatus","Elapsed":0.014} -------------------------------------------------------------------------------- /tests/testdata/panic/test_04.jsonl: -------------------------------------------------------------------------------- 1 | {"Time":"2018-10-21T23:42:51.496472-04:00","Action":"output","Package":"github.com/mfridman/tparse","Output":"? \tgithub.com/mfridman/tparse\t[no test files]\n"} 2 | {"Time":"2018-10-21T23:42:51.496734-04:00","Action":"skip","Package":"github.com/mfridman/tparse","Elapsed":0} 3 | {"Time":"2018-10-21T23:42:51.49677-04:00","Action":"output","Package":"github.com/mfridman/tparse/ignore","Output":"? \tgithub.com/mfridman/tparse/ignore\t[no test files]\n"} 4 | {"Time":"2018-10-21T23:42:51.496782-04:00","Action":"skip","Package":"github.com/mfridman/tparse/ignore","Elapsed":0} 5 | {"Time":"2018-10-21T23:42:51.496805-04:00","Action":"output","Package":"github.com/mfridman/tparse/parse","Output":"? \tgithub.com/mfridman/tparse/parse\t[no test files]\n"} 6 | {"Time":"2018-10-21T23:42:51.496813-04:00","Action":"skip","Package":"github.com/mfridman/tparse/parse","Elapsed":0} 7 | {"Time":"2018-10-21T23:42:51.673696-04:00","Action":"run","Package":"github.com/mfridman/tparse/tests","Test":"TestNewEvent"} 8 | {"Time":"2018-10-21T23:42:51.673742-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestNewEvent","Output":"=== RUN TestNewEvent\n"} 9 | {"Time":"2018-10-21T23:42:51.673772-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestNewEvent","Output":"=== PAUSE TestNewEvent\n"} 10 | {"Time":"2018-10-21T23:42:51.673795-04:00","Action":"pause","Package":"github.com/mfridman/tparse/tests","Test":"TestNewEvent"} 11 | {"Time":"2018-10-21T23:42:51.673815-04:00","Action":"run","Package":"github.com/mfridman/tparse/tests","Test":"TestPanicEvent"} 12 | {"Time":"2018-10-21T23:42:51.673834-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestPanicEvent","Output":"=== RUN TestPanicEvent\n"} 13 | {"Time":"2018-10-21T23:42:51.674274-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestPanicEvent","Output":"--- PASS: TestPanicEvent (0.00s)\n"} 14 | {"Time":"2018-10-21T23:42:51.674295-04:00","Action":"pass","Package":"github.com/mfridman/tparse/tests","Test":"TestPanicEvent","Elapsed":0} 15 | {"Time":"2018-10-21T23:42:51.674307-04:00","Action":"run","Package":"github.com/mfridman/tparse/tests","Test":"TestStack"} 16 | {"Time":"2018-10-21T23:42:51.674314-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestStack","Output":"=== RUN TestStack\n"} 17 | {"Time":"2018-10-21T23:42:51.674328-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestStack","Output":"--- FAIL: TestStack (0.00s)\n"} 18 | {"Time":"2018-10-21T23:42:51.676397-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestStack","Output":"panic: oops [recovered]\n"} 19 | {"Time":"2018-10-21T23:42:51.676427-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestStack","Output":"\tpanic: oops\n"} 20 | {"Time":"2018-10-21T23:42:51.676437-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestStack","Output":"\n"} 21 | {"Time":"2018-10-21T23:42:51.676453-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestStack","Output":"goroutine 20 [running]:\n"} 22 | {"Time":"2018-10-21T23:42:51.676462-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestStack","Output":"testing.tRunner.func1(0xc0000b4600)\n"} 23 | {"Time":"2018-10-21T23:42:51.676489-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestStack","Output":"\t/usr/local/go/src/testing/testing.go:792 +0x387\n"} 24 | {"Time":"2018-10-21T23:42:51.676501-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestStack","Output":"panic(0x112ad60, 0x1182f90)\n"} 25 | {"Time":"2018-10-21T23:42:51.67651-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestStack","Output":"\t/usr/local/go/src/runtime/panic.go:513 +0x1b9\n"} 26 | {"Time":"2018-10-21T23:42:51.676523-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestStack","Output":"github.com/mfridman/tparse/tests_test.TestStack(0xc0000b4600)\n"} 27 | {"Time":"2018-10-21T23:42:51.676542-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestStack","Output":"\t/Users/michael.fridman/go/src/github.com/mfridman/tparse/tests/stack_test.go:12 +0x39\n"} 28 | {"Time":"2018-10-21T23:42:51.676555-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestStack","Output":"testing.tRunner(0xc0000b4600, 0x116a730)\n"} 29 | {"Time":"2018-10-21T23:42:51.676585-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestStack","Output":"\t/usr/local/go/src/testing/testing.go:827 +0xbf\n"} 30 | {"Time":"2018-10-21T23:42:51.676596-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestStack","Output":"created by testing.(*T).Run\n"} 31 | {"Time":"2018-10-21T23:42:51.67666-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestStack","Output":"\t/usr/local/go/src/testing/testing.go:878 +0x353\n"} 32 | {"Time":"2018-10-21T23:42:51.676943-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestStack","Output":"FAIL\tgithub.com/mfridman/tparse/tests\t0.016s\n"} 33 | {"Time":"2018-10-21T23:42:51.676966-04:00","Action":"fail","Package":"github.com/mfridman/tparse/tests","Test":"TestStack","Elapsed":0.016} -------------------------------------------------------------------------------- /tests/testdata/panic/test_05.jsonl: -------------------------------------------------------------------------------- 1 | {"Time":"2018-10-24T14:32:01.310248-04:00","Action":"output","Package":"github.com/mfridman/tparse","Output":"? \tgithub.com/mfridman/tparse\t[no test files]\n"} 2 | {"Time":"2018-10-24T14:32:01.31052-04:00","Action":"skip","Package":"github.com/mfridman/tparse","Elapsed":0} 3 | {"Time":"2018-10-24T14:32:01.31056-04:00","Action":"output","Package":"github.com/mfridman/tparse/ignore","Output":"? \tgithub.com/mfridman/tparse/ignore\t[no test files]\n"} 4 | {"Time":"2018-10-24T14:32:01.310588-04:00","Action":"skip","Package":"github.com/mfridman/tparse/ignore","Elapsed":0} 5 | {"Time":"2018-10-24T14:32:01.310626-04:00","Action":"output","Package":"github.com/mfridman/tparse/parse","Output":"? \tgithub.com/mfridman/tparse/parse\t[no test files]\n"} 6 | {"Time":"2018-10-24T14:32:01.310638-04:00","Action":"skip","Package":"github.com/mfridman/tparse/parse","Elapsed":0} 7 | {"Time":"2018-10-24T14:32:01.499657-04:00","Action":"run","Package":"github.com/mfridman/tparse/tests","Test":"TestNewEvent"} 8 | {"Time":"2018-10-24T14:32:01.499717-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestNewEvent","Output":"=== RUN TestNewEvent\n"} 9 | {"Time":"2018-10-24T14:32:01.499741-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestNewEvent","Output":"=== PAUSE TestNewEvent\n"} 10 | {"Time":"2018-10-24T14:32:01.499758-04:00","Action":"pause","Package":"github.com/mfridman/tparse/tests","Test":"TestNewEvent"} 11 | {"Time":"2018-10-24T14:32:01.499771-04:00","Action":"run","Package":"github.com/mfridman/tparse/tests","Test":"TestCachedPackage"} 12 | {"Time":"2018-10-24T14:32:01.499781-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestCachedPackage","Output":"=== RUN TestCachedPackage\n"} 13 | {"Time":"2018-10-24T14:32:01.499789-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestCachedPackage","Output":"=== PAUSE TestCachedPackage\n"} 14 | {"Time":"2018-10-24T14:32:01.499799-04:00","Action":"pause","Package":"github.com/mfridman/tparse/tests","Test":"TestCachedPackage"} 15 | {"Time":"2018-10-24T14:32:01.499809-04:00","Action":"run","Package":"github.com/mfridman/tparse/tests","Test":"TestCover"} 16 | {"Time":"2018-10-24T14:32:01.499816-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestCover","Output":"=== RUN TestCover\n"} 17 | {"Time":"2018-10-24T14:32:01.499829-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestCover","Output":"=== PAUSE TestCover\n"} 18 | {"Time":"2018-10-24T14:32:01.499836-04:00","Action":"pause","Package":"github.com/mfridman/tparse/tests","Test":"TestCover"} 19 | {"Time":"2018-10-24T14:32:01.499843-04:00","Action":"run","Package":"github.com/mfridman/tparse/tests","Test":"TestPanicEvent"} 20 | {"Time":"2018-10-24T14:32:01.499849-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestPanicEvent","Output":"=== RUN TestPanicEvent\n"} 21 | {"Time":"2018-10-24T14:32:01.500824-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestPanicEvent","Output":"--- PASS: TestPanicEvent (0.00s)\n"} 22 | {"Time":"2018-10-24T14:32:01.500848-04:00","Action":"pass","Package":"github.com/mfridman/tparse/tests","Test":"TestPanicEvent","Elapsed":0} 23 | {"Time":"2018-10-24T14:32:01.500859-04:00","Action":"run","Package":"github.com/mfridman/tparse/tests","Test":"TestPrescan"} 24 | {"Time":"2018-10-24T14:32:01.500869-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestPrescan","Output":"=== RUN TestPrescan\n"} 25 | {"Time":"2018-10-24T14:32:01.50088-04:00","Action":"run","Package":"github.com/mfridman/tparse/tests","Test":"TestPrescan/pass"} 26 | {"Time":"2018-10-24T14:32:01.50089-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestPrescan/pass","Output":"=== RUN TestPrescan/pass\n"} 27 | {"Time":"2018-10-24T14:32:01.501224-04:00","Action":"run","Package":"github.com/mfridman/tparse/tests","Test":"TestPrescan/fail01"} 28 | {"Time":"2018-10-24T14:32:01.501234-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestPrescan/fail01","Output":"=== RUN TestPrescan/fail01\n"} 29 | {"Time":"2018-10-24T14:32:01.503535-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestPrescan/fail01","Output":"panic: runtime error: invalid memory address or nil pointer dereference [recovered]\n"} 30 | {"Time":"2018-10-24T14:32:01.503561-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestPrescan/fail01","Output":"\tpanic: runtime error: invalid memory address or nil pointer dereference\n"} 31 | {"Time":"2018-10-24T14:32:01.503574-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestPrescan/fail01","Output":"[signal SIGSEGV: segmentation violation code=0x1 addr=0x40 pc=0x110f2f1]\n"} 32 | {"Time":"2018-10-24T14:32:01.503582-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestPrescan/fail01","Output":"\n"} 33 | {"Time":"2018-10-24T14:32:01.503592-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestPrescan/fail01","Output":"goroutine 11 [running]:\n"} 34 | {"Time":"2018-10-24T14:32:01.5036-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestPrescan/fail01","Output":"testing.tRunner.func1(0xc0000a0b00)\n"} 35 | {"Time":"2018-10-24T14:32:01.503612-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestPrescan/fail01","Output":"\t/usr/local/go/src/testing/testing.go:792 +0x387\n"} 36 | {"Time":"2018-10-24T14:32:01.503664-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestPrescan/fail01","Output":"panic(0x1138e80, 0x126a100)\n"} 37 | {"Time":"2018-10-24T14:32:01.50368-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestPrescan/fail01","Output":"\t/usr/local/go/src/runtime/panic.go:513 +0x1b9\n"} 38 | {"Time":"2018-10-24T14:32:01.503689-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestPrescan/fail01","Output":"github.com/mfridman/tparse/parse.Start(0x118b220, 0xc0000fb8a0, 0x5bd0baa1, 0xc000034798, 0x106e0b6)\n"} 39 | {"Time":"2018-10-24T14:32:01.503697-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestPrescan/fail01","Output":"\t/Users/michael.fridman/go/src/github.com/mfridman/tparse/parse/package.go:136 +0x151\n"} 40 | {"Time":"2018-10-24T14:32:01.503707-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestPrescan/fail01","Output":"github.com/mfridman/tparse/tests_test.TestPrescan.func2(0xc0000a0b00)\n"} 41 | {"Time":"2018-10-24T14:32:01.503779-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestPrescan/fail01","Output":"\t/Users/michael.fridman/go/src/github.com/mfridman/tparse/tests/prescan_test.go:144 +0x7a\n"} 42 | {"Time":"2018-10-24T14:32:01.503791-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestPrescan/fail01","Output":"testing.tRunner(0xc0000a0b00, 0x1171820)\n"} 43 | {"Time":"2018-10-24T14:32:01.503798-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestPrescan/fail01","Output":"\t/usr/local/go/src/testing/testing.go:827 +0xbf\n"} 44 | {"Time":"2018-10-24T14:32:01.503805-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestPrescan/fail01","Output":"created by testing.(*T).Run\n"} 45 | {"Time":"2018-10-24T14:32:01.503813-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestPrescan/fail01","Output":"\t/usr/local/go/src/testing/testing.go:878 +0x353\n"} 46 | {"Time":"2018-10-24T14:32:01.504138-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestPrescan/fail01","Output":"FAIL\tgithub.com/mfridman/tparse/tests\t0.016s\n"} 47 | {"Time":"2018-10-24T14:32:01.50418-04:00","Action":"fail","Package":"github.com/mfridman/tparse/tests","Test":"TestPrescan/fail01","Elapsed":0.016} -------------------------------------------------------------------------------- /tests/testdata/prescan/test_01.txt: -------------------------------------------------------------------------------- 1 | git.checkout 2 | 0.55s$ git clone --depth=50 --branch=master https://github.com/mfridman/tparse.git mfridman/tparse 3 | Cloning into 'mfridman/tparse'... 4 | remote: Enumerating objects: 485, done. 5 | remote: Counting objects: 100% (485/485), done. 6 | remote: Compressing objects: 100% (310/310), done. 7 | remote: Total 485 (delta 209), reused 442 (delta 168), pack-reused 0 8 | Receiving objects: 100% (485/485), 1.17 MiB | 20.58 MiB/s, done. 9 | Resolving deltas: 100% (209/209), done. 10 | $ cd mfridman/tparse 11 | $ git checkout -qf d7a97f658463e3abd90357d9988575e8138d9a86 12 | 4.38s$ GIMME_OUTPUT="$(gimme 1.10 | tee -a ${TRAVIS_HOME}/.bashrc)" && eval "$GIMME_OUTPUT" 13 | go version go1.10 linux/amd64 14 | $ export GOPATH=${TRAVIS_HOME}/gopath 15 | $ export PATH=${TRAVIS_HOME}/gopath/bin:$PATH 16 | $ mkdir -p ${TRAVIS_HOME}/gopath/src/github.com/mfridman/tparse 17 | $ tar -Pczf ${TRAVIS_TMPDIR}/src_archive.tar.gz -C ${TRAVIS_BUILD_DIR} . && tar -Pxzf ${TRAVIS_TMPDIR}/src_archive.tar.gz -C ${TRAVIS_HOME}/gopath/src/github.com/mfridman/tparse 18 | $ export TRAVIS_BUILD_DIR=${TRAVIS_HOME}/gopath/src/github.com/mfridman/tparse 19 | $ cd ${TRAVIS_HOME}/gopath/src/github.com/mfridman/tparse 20 | 0.00s 21 | $ gimme version 22 | v1.5.3 23 | $ go version 24 | go version go1.10 linux/amd64 25 | go.env 26 | $ go env 27 | GOARCH="amd64" 28 | GOBIN="" 29 | GOCACHE="/home/travis/.cache/go-build" 30 | GOEXE="" 31 | GOHOSTARCH="amd64" 32 | GOHOSTOS="linux" 33 | GOOS="linux" 34 | GOPATH="/home/travis/gopath" 35 | GORACE="" 36 | GOROOT="/home/travis/.gimme/versions/go1.10.linux.amd64" 37 | GOTMPDIR="" 38 | GOTOOLDIR="/home/travis/.gimme/versions/go1.10.linux.amd64/pkg/tool/linux_amd64" 39 | GCCGO="gccgo" 40 | CC="gcc" 41 | CXX="g++" 42 | CGO_ENABLED="1" 43 | CGO_CFLAGS="-g -O2" 44 | CGO_CPPFLAGS="" 45 | CGO_CXXFLAGS="-g -O2" 46 | CGO_FFLAGS="-g -O2" 47 | CGO_LDFLAGS="-g -O2" 48 | PKG_CONFIG="pkg-config" 49 | GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build714003296=/tmp/go-build -gno-record-gcc-switches" 50 | before_install 51 | {"Time":"2018-10-24T13:13:35.445816-04:00","Action":"run","Package":"github.com/mfridman/tparse/tests","Test":"TestStack"} 52 | {"Time":"2018-10-24T13:13:35.447209-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestStack","Output":"=== RUN TestStack\n"} 53 | {"Time":"2018-10-24T13:13:35.44729-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestStack","Output":"=== PAUSE TestStack\n"} 54 | {"Time":"2018-10-24T13:13:35.447302-04:00","Action":"pause","Package":"github.com/mfridman/tparse/tests","Test":"TestStack"} 55 | {"Time":"2018-10-24T13:13:35.447311-04:00","Action":"cont","Package":"github.com/mfridman/tparse/tests","Test":"TestStack"} 56 | {"Time":"2018-10-24T13:13:35.447317-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestStack","Output":"=== CONT TestStack\n"} 57 | {"Time":"2018-10-24T13:13:35.447348-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestStack","Output":"--- PASS: TestStack (0.00s)\n"} 58 | {"Time":"2018-10-24T13:13:35.447394-04:00","Action":"pass","Package":"github.com/mfridman/tparse/tests","Test":"TestStack","Elapsed":0} 59 | {"Time":"2018-10-24T13:13:35.447481-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Output":"PASS\n"} 60 | {"Time":"2018-10-24T13:13:35.447759-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Output":"ok \tgithub.com/mfridman/tparse/tests\t0.014s\n"} 61 | {"Time":"2018-10-24T13:13:35.454438-04:00","Action":"pass","Package":"github.com/mfridman/tparse/tests","Elapsed":0.021} 62 | -------------------------------------------------------------------------------- /tests/testdata/prescan/test_02.txt: -------------------------------------------------------------------------------- 1 | git.checkout 2 | 0.55s$ git clone --depth=50 --branch=master https://github.com/mfridman/tparse.git mfridman/tparse 3 | Cloning into 'mfridman/tparse'... 4 | remote: Enumerating objects: 485, done. 5 | remote: Counting objects: 100% (485/485), done. 6 | remote: Compressing objects: 100% (310/310), done. 7 | remote: Total 485 (delta 209), reused 442 (delta 168), pack-reused 0 8 | Receiving objects: 100% (485/485), 1.17 MiB | 20.58 MiB/s, done. 9 | Resolving deltas: 100% (209/209), done. 10 | $ cd mfridman/tparse 11 | $ git checkout -qf d7a97f658463e3abd90357d9988575e8138d9a86 12 | 4.38s$ GIMME_OUTPUT="$(gimme 1.10 | tee -a ${TRAVIS_HOME}/.bashrc)" && eval "$GIMME_OUTPUT" 13 | go version go1.10 linux/amd64 14 | $ export GOPATH=${TRAVIS_HOME}/gopath 15 | $ export PATH=${TRAVIS_HOME}/gopath/bin:$PATH 16 | $ mkdir -p ${TRAVIS_HOME}/gopath/src/github.com/mfridman/tparse 17 | $ tar -Pczf ${TRAVIS_TMPDIR}/src_archive.tar.gz -C ${TRAVIS_BUILD_DIR} . && tar -Pxzf ${TRAVIS_TMPDIR}/src_archive.tar.gz -C ${TRAVIS_HOME}/gopath/src/github.com/mfridman/tparse 18 | $ export TRAVIS_BUILD_DIR=${TRAVIS_HOME}/gopath/src/github.com/mfridman/tparse 19 | $ cd ${TRAVIS_HOME}/gopath/src/github.com/mfridman/tparse 20 | 0.00s 21 | $ gimme version 22 | v1.5.3 23 | $ go version 24 | go version go1.10 linux/amd64 25 | go.env 26 | $ go env 27 | GOARCH="amd64" 28 | GOBIN="" 29 | GOCACHE="/home/travis/.cache/go-build" 30 | GOEXE="" 31 | GOHOSTARCH="amd64" 32 | GOHOSTOS="linux" 33 | GOOS="linux" 34 | GOPATH="/home/travis/gopath" 35 | GORACE="" 36 | GOROOT="/home/travis/.gimme/versions/go1.10.linux.amd64" 37 | GOTMPDIR="" 38 | GOTOOLDIR="/home/travis/.gimme/versions/go1.10.linux.amd64/pkg/tool/linux_amd64" 39 | GCCGO="gccgo" 40 | CC="gcc" 41 | CXX="g++" 42 | CGO_ENABLED="1" 43 | CGO_CFLAGS="-g -O2" 44 | CGO_CPPFLAGS="" 45 | CGO_CXXFLAGS="-g -O2" 46 | CGO_FFLAGS="-g -O2" 47 | CGO_LDFLAGS="-g -O2" 48 | PKG_CONFIG="pkg-config" 49 | GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build714003296=/tmp/go-build -gno-record-gcc-switches" 50 | before_install 51 | pirate ipsum! 52 | {"Time":"2018-10-24T13:13:35.445816-04:00","Action":"run","Package":"github.com/mfridman/tparse/tests","Test":"TestStack"} 53 | {"Time":"2018-10-24T13:13:35.447209-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestStack","Output":"=== RUN TestStack\n"} 54 | {"Time":"2018-10-24T13:13:35.44729-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestStack","Output":"=== PAUSE TestStack\n"} 55 | {"Time":"2018-10-24T13:13:35.447302-04:00","Action":"pause","Package":"github.com/mfridman/tparse/tests","Test":"TestStack"} 56 | {"Time":"2018-10-24T13:13:35.447311-04:00","Action":"cont","Package":"github.com/mfridman/tparse/tests","Test":"TestStack"} 57 | {"Time":"2018-10-24T13:13:35.447317-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestStack","Output":"=== CONT TestStack\n"} 58 | {"Time":"2018-10-24T13:13:35.447348-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestStack","Output":"--- PASS: TestStack (0.00s)\n"} 59 | {"Time":"2018-10-24T13:13:35.447394-04:00","Action":"pass","Package":"github.com/mfridman/tparse/tests","Test":"TestStack","Elapsed":0} 60 | {"Time":"2018-10-24T13:13:35.447481-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Output":"PASS\n"} 61 | {"Time":"2018-10-24T13:13:35.447759-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Output":"ok \tgithub.com/mfridman/tparse/tests\t0.014s\n"} 62 | {"Time":"2018-10-24T13:13:35.454438-04:00","Action":"pass","Package":"github.com/mfridman/tparse/tests","Elapsed":0.021} -------------------------------------------------------------------------------- /tests/testdata/prescan/test_03.txt: -------------------------------------------------------------------------------- 1 | git.checkout 2 | 0.55s$ git clone --depth=50 --branch=master https://github.com/mfridman/tparse.git mfridman/tparse 3 | Cloning into 'mfridman/tparse'... 4 | remote: Enumerating objects: 485, done. 5 | remote: Counting objects: 100% (485/485), done. 6 | {"Time":"2018-10-24T13:13:35.447209-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestStack","Output":"=== RUN TestStack\n"} 7 | remote: Compressing objects: 100% (310/310), done. 8 | {"Time":"2018-10-24T13:13:35.447317-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestStack", 9 | remote: Total 485 (delta 209), reused 442 (delta 168), pack-reused 0 10 | Receiving objects: 100% (485/485), 1.17 MiB | 20.58 MiB/s, done. 11 | Resolving deltas: 100% (209/209), done. 12 | $ cd mfridman/tparse 13 | $ git checkout -qf d7a97f658463e3abd90357d9988575e8138d9a86 14 | {"Time":"2018-10-24T13:13:35.445816-04:00","Action":"run","Package":"github.com/mfridman/tparse/tests","Test":"TestStack"} 15 | {"Time":"2018-10-24T13:13:35.447209-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestStack","Output":"=== RUN TestStack\n"} 16 | {"Time":"2018-10-24T13:13:35.44729-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestStack","Output":"=== PAUSE TestStack\n"} 17 | {"Time":"2018-10-24T13:13:35.447302-04:00","Action":"pause","Package":"github.com/mfridman/tparse/tests","Test":"TestStack"} 18 | {"Time":"2018-10-24T13:13:35.447311-04:00","Action":"cont","Package":"github.com/mfridman/tparse/tests","Test":"TestStack"} 19 | {"Time":"2018-10-24T13:13:35.447317-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestStack","Output":"=== CONT TestStack\n"} 20 | {"Time":"2018-10-24T13:13:35.447348-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Test":"TestStack","Output":"--- PASS: TestStack (0.00s)\n"} 21 | {"Time":"2018-10-24T13:13:35.447394-04:00","Action":"pass","Package":"github.com/mfridman/tparse/tests","Test":"TestStack","Elapsed":0} 22 | {"Time":"2018-10-24T13:13:35.447481-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Output":"PASS\n"} 23 | {"Time":"2018-10-24T13:13:35.447759-04:00","Action":"output","Package":"github.com/mfridman/tparse/tests","Output":"ok \tgithub.com/mfridman/tparse/tests\t0.014s\n"} 24 | {"Time":"2018-10-24T13:13:35.454438-04:00","Action":"pass","Package":"github.com/mfridman/tparse/tests","Elapsed":0.021} -------------------------------------------------------------------------------- /tests/testdata/prescan/test_04.txt: -------------------------------------------------------------------------------- 1 | Previous write at 0x00c000090090 by goroutine 6: 2 | debug/tparse-24.TestRace() 3 | /Users/michael.fridman/go/src/debug/tparse-24/some_test.go:8 +0x88 4 | testing.tRunner() 5 | /usr/local/go/src/testing/testing.go:827 +0x162 6 | 7 | Goroutine 7 (running) created at: 8 | debug/tparse-24.TestRace() 9 | /Users/michael.fridman/go/src/debug/tparse-24/some_test.go:7 +0x7a 10 | testing.tRunner() 11 | /usr/local/go/src/testing/testing.go:827 +0x162 12 | 13 | Goroutine 6 (running) created at: 14 | testing.(*T).Run() 15 | /usr/local/go/src/testing/testing.go:878 +0x650 16 | testing.runTests.func1() 17 | /usr/local/go/src/testing/testing.go:1119 +0xa8 18 | testing.tRunner() 19 | /usr/local/go/src/testing/testing.go:827 +0x162 20 | testing.runTests() 21 | /usr/local/go/src/testing/testing.go:1117 +0x4ee 22 | testing.(*M).Run() 23 | /usr/local/go/src/testing/testing.go:1034 +0x2ee 24 | main.main() 25 | _testmain.go:42 +0x221 26 | -------------------------------------------------------------------------------- /tests/testdata/race/test_01.jsonl: -------------------------------------------------------------------------------- 1 | {"Time":"2018-12-16T20:28:29.256021-05:00","Action":"run","Package":"command-line-arguments","Test":"TestRace1"} 2 | {"Time":"2018-12-16T20:28:29.256317-05:00","Action":"output","Package":"command-line-arguments","Test":"TestRace1","Output":"=== RUN TestRace1\n"} 3 | {"Time":"2018-12-16T20:28:29.256347-05:00","Action":"output","Package":"command-line-arguments","Test":"TestRace1","Output":"=== PAUSE TestRace1\n"} 4 | {"Time":"2018-12-16T20:28:29.256355-05:00","Action":"pause","Package":"command-line-arguments","Test":"TestRace1"} 5 | {"Time":"2018-12-16T20:28:29.256363-05:00","Action":"run","Package":"command-line-arguments","Test":"TestA"} 6 | {"Time":"2018-12-16T20:28:29.25637-05:00","Action":"output","Package":"command-line-arguments","Test":"TestA","Output":"=== RUN TestA\n"} 7 | {"Time":"2018-12-16T20:28:29.256377-05:00","Action":"output","Package":"command-line-arguments","Test":"TestA","Output":"=== PAUSE TestA\n"} 8 | {"Time":"2018-12-16T20:28:29.256384-05:00","Action":"pause","Package":"command-line-arguments","Test":"TestA"} 9 | {"Time":"2018-12-16T20:28:29.256538-05:00","Action":"cont","Package":"command-line-arguments","Test":"TestA"} 10 | {"Time":"2018-12-16T20:28:29.256559-05:00","Action":"output","Package":"command-line-arguments","Test":"TestA","Output":"=== CONT TestA\n"} 11 | {"Time":"2018-12-16T20:28:29.256572-05:00","Action":"cont","Package":"command-line-arguments","Test":"TestRace1"} 12 | {"Time":"2018-12-16T20:28:29.256579-05:00","Action":"output","Package":"command-line-arguments","Test":"TestRace1","Output":"=== CONT TestRace1\n"} 13 | {"Time":"2018-12-16T20:28:29.256742-05:00","Action":"output","Package":"command-line-arguments","Test":"TestA","Output":"--- PASS: TestA (0.00s)\n"} 14 | {"Time":"2018-12-16T20:28:29.256827-05:00","Action":"output","Package":"command-line-arguments","Test":"TestA","Output":"==================\n"} 15 | {"Time":"2018-12-16T20:28:29.256859-05:00","Action":"output","Package":"command-line-arguments","Test":"TestA","Output":"WARNING: DATA RACE\n"} 16 | {"Time":"2018-12-16T20:28:29.256868-05:00","Action":"output","Package":"command-line-arguments","Test":"TestA","Output":"Write at 0x00c0000be010 by goroutine 9:\n"} 17 | {"Time":"2018-12-16T20:28:29.25689-05:00","Action":"output","Package":"command-line-arguments","Test":"TestA","Output":" command-line-arguments.TestRace1.func1()\n"} 18 | {"Time":"2018-12-16T20:28:29.256899-05:00","Action":"output","Package":"command-line-arguments","Test":"TestA","Output":" /Users/michael.fridman/go/src/debug/tparse-24/some_test.go:8 +0x38\n"} 19 | {"Time":"2018-12-16T20:28:29.256915-05:00","Action":"output","Package":"command-line-arguments","Test":"TestA","Output":"\n"} 20 | {"Time":"2018-12-16T20:28:29.256929-05:00","Action":"output","Package":"command-line-arguments","Test":"TestA","Output":"Previous write at 0x00c0000be010 by goroutine 6:\n"} 21 | {"Time":"2018-12-16T20:28:29.256936-05:00","Action":"output","Package":"command-line-arguments","Test":"TestA","Output":" command-line-arguments.TestRace1()\n"} 22 | {"Time":"2018-12-16T20:28:29.256942-05:00","Action":"output","Package":"command-line-arguments","Test":"TestA","Output":" /Users/michael.fridman/go/src/debug/tparse-24/some_test.go:9 +0x96\n"} 23 | {"Time":"2018-12-16T20:28:29.256948-05:00","Action":"output","Package":"command-line-arguments","Test":"TestA","Output":" testing.tRunner()\n"} 24 | {"Time":"2018-12-16T20:28:29.256961-05:00","Action":"output","Package":"command-line-arguments","Test":"TestA","Output":" /usr/local/go/src/testing/testing.go:827 +0x162\n"} 25 | {"Time":"2018-12-16T20:28:29.256968-05:00","Action":"output","Package":"command-line-arguments","Test":"TestA","Output":"\n"} 26 | {"Time":"2018-12-16T20:28:29.256974-05:00","Action":"output","Package":"command-line-arguments","Test":"TestA","Output":"Goroutine 9 (running) created at:\n"} 27 | {"Time":"2018-12-16T20:28:29.25698-05:00","Action":"output","Package":"command-line-arguments","Test":"TestA","Output":" command-line-arguments.TestRace1()\n"} 28 | {"Time":"2018-12-16T20:28:29.256986-05:00","Action":"output","Package":"command-line-arguments","Test":"TestA","Output":" /Users/michael.fridman/go/src/debug/tparse-24/some_test.go:8 +0x88\n"} 29 | {"Time":"2018-12-16T20:28:29.256993-05:00","Action":"output","Package":"command-line-arguments","Test":"TestA","Output":" testing.tRunner()\n"} 30 | {"Time":"2018-12-16T20:28:29.257034-05:00","Action":"output","Package":"command-line-arguments","Test":"TestA","Output":" /usr/local/go/src/testing/testing.go:827 +0x162\n"} 31 | {"Time":"2018-12-16T20:28:29.257044-05:00","Action":"output","Package":"command-line-arguments","Test":"TestA","Output":"\n"} 32 | {"Time":"2018-12-16T20:28:29.257097-05:00","Action":"output","Package":"command-line-arguments","Test":"TestA","Output":"Goroutine 6 (running) created at:\n"} 33 | {"Time":"2018-12-16T20:28:29.257111-05:00","Action":"output","Package":"command-line-arguments","Test":"TestA","Output":" testing.(*T).Run()\n"} 34 | {"Time":"2018-12-16T20:28:29.257121-05:00","Action":"output","Package":"command-line-arguments","Test":"TestA","Output":" /usr/local/go/src/testing/testing.go:878 +0x650\n"} 35 | {"Time":"2018-12-16T20:28:29.257127-05:00","Action":"output","Package":"command-line-arguments","Test":"TestA","Output":" testing.runTests.func1()\n"} 36 | {"Time":"2018-12-16T20:28:29.257134-05:00","Action":"output","Package":"command-line-arguments","Test":"TestA","Output":" /usr/local/go/src/testing/testing.go:1119 +0xa8\n"} 37 | {"Time":"2018-12-16T20:28:29.25714-05:00","Action":"output","Package":"command-line-arguments","Test":"TestA","Output":" testing.tRunner()\n"} 38 | {"Time":"2018-12-16T20:28:29.257146-05:00","Action":"output","Package":"command-line-arguments","Test":"TestA","Output":" /usr/local/go/src/testing/testing.go:827 +0x162\n"} 39 | {"Time":"2018-12-16T20:28:29.257153-05:00","Action":"output","Package":"command-line-arguments","Test":"TestA","Output":" testing.runTests()\n"} 40 | {"Time":"2018-12-16T20:28:29.257162-05:00","Action":"output","Package":"command-line-arguments","Test":"TestA","Output":" /usr/local/go/src/testing/testing.go:1117 +0x4ee\n"} 41 | {"Time":"2018-12-16T20:28:29.25717-05:00","Action":"output","Package":"command-line-arguments","Test":"TestA","Output":" testing.(*M).Run()\n"} 42 | {"Time":"2018-12-16T20:28:29.257187-05:00","Action":"output","Package":"command-line-arguments","Test":"TestA","Output":" /usr/local/go/src/testing/testing.go:1034 +0x2ee\n"} 43 | {"Time":"2018-12-16T20:28:29.257196-05:00","Action":"output","Package":"command-line-arguments","Test":"TestA","Output":" main.main()\n"} 44 | {"Time":"2018-12-16T20:28:29.257209-05:00","Action":"output","Package":"command-line-arguments","Test":"TestA","Output":" _testmain.go:44 +0x221\n"} 45 | {"Time":"2018-12-16T20:28:29.257216-05:00","Action":"output","Package":"command-line-arguments","Test":"TestA","Output":"==================\n"} 46 | {"Time":"2018-12-16T20:28:29.257467-05:00","Action":"pass","Package":"command-line-arguments","Test":"TestA","Elapsed":0} 47 | {"Time":"2018-12-16T20:28:29.257487-05:00","Action":"output","Package":"command-line-arguments","Test":"TestRace1","Output":"--- FAIL: TestRace1 (0.00s)\n"} 48 | {"Time":"2018-12-16T20:28:29.257498-05:00","Action":"output","Package":"command-line-arguments","Test":"TestRace1","Output":" some_test.go:10: 64\n"} 49 | {"Time":"2018-12-16T20:28:29.257508-05:00","Action":"output","Package":"command-line-arguments","Test":"TestRace1","Output":" testing.go:771: race detected during execution of test\n"} 50 | {"Time":"2018-12-16T20:28:29.257518-05:00","Action":"fail","Package":"command-line-arguments","Test":"TestRace1","Elapsed":0} 51 | {"Time":"2018-12-16T20:28:29.257524-05:00","Action":"output","Package":"command-line-arguments","Output":"FAIL\n"} 52 | {"Time":"2018-12-16T20:28:29.258518-05:00","Action":"output","Package":"command-line-arguments","Output":"FAIL\tcommand-line-arguments\t0.016s\n"} 53 | {"Time":"2018-12-16T20:28:29.258549-05:00","Action":"fail","Package":"command-line-arguments","Elapsed":0.017} -------------------------------------------------------------------------------- /tests/testdata/race/test_03.jsonl: -------------------------------------------------------------------------------- 1 | {"Time":"2018-12-16T21:21:52.633949-05:00","Action":"run","Package":"debug/tparse-24","Test":"TestRace1"} 2 | {"Time":"2018-12-16T21:21:52.63424-05:00","Action":"output","Package":"debug/tparse-24","Test":"TestRace1","Output":"=== RUN TestRace1\n"} 3 | {"Time":"2018-12-16T21:21:52.634262-05:00","Action":"output","Package":"debug/tparse-24","Test":"TestRace1","Output":"=== PAUSE TestRace1\n"} 4 | {"Time":"2018-12-16T21:21:52.63427-05:00","Action":"pause","Package":"debug/tparse-24","Test":"TestRace1"} 5 | {"Time":"2018-12-16T21:21:52.634277-05:00","Action":"run","Package":"debug/tparse-24","Test":"TestA"} 6 | {"Time":"2018-12-16T21:21:52.634283-05:00","Action":"output","Package":"debug/tparse-24","Test":"TestA","Output":"=== RUN TestA\n"} 7 | {"Time":"2018-12-16T21:21:52.63429-05:00","Action":"output","Package":"debug/tparse-24","Test":"TestA","Output":"=== PAUSE TestA\n"} 8 | {"Time":"2018-12-16T21:21:52.634296-05:00","Action":"pause","Package":"debug/tparse-24","Test":"TestA"} 9 | {"Time":"2018-12-16T21:21:52.634392-05:00","Action":"cont","Package":"debug/tparse-24","Test":"TestA"} 10 | {"Time":"2018-12-16T21:21:52.634498-05:00","Action":"output","Package":"debug/tparse-24","Test":"TestA","Output":"=== CONT TestA\n"} 11 | {"Time":"2018-12-16T21:21:52.634516-05:00","Action":"cont","Package":"debug/tparse-24","Test":"TestRace1"} 12 | {"Time":"2018-12-16T21:21:52.634522-05:00","Action":"output","Package":"debug/tparse-24","Test":"TestRace1","Output":"=== CONT TestRace1\n"} 13 | {"Time":"2018-12-16T21:21:52.634537-05:00","Action":"output","Package":"debug/tparse-24","Test":"TestA","Output":"--- PASS: TestA (0.00s)\n"} 14 | {"Time":"2018-12-16T21:21:52.635006-05:00","Action":"pass","Package":"debug/tparse-24","Test":"TestA","Elapsed":0} 15 | {"Time":"2018-12-16T21:21:52.635058-05:00","Action":"output","Package":"debug/tparse-24","Test":"TestRace1","Output":"--- PASS: TestRace1 (0.00s)\n"} 16 | {"Time":"2018-12-16T21:21:52.635074-05:00","Action":"output","Package":"debug/tparse-24","Test":"TestRace1","Output":" some_test.go:10: 64\n"} 17 | {"Time":"2018-12-16T21:21:52.635111-05:00","Action":"pass","Package":"debug/tparse-24","Test":"TestRace1","Elapsed":0} 18 | {"Time":"2018-12-16T21:21:52.63512-05:00","Action":"output","Package":"debug/tparse-24","Output":"PASS\n"} 19 | {"Time":"2018-12-16T21:21:52.635128-05:00","Action":"output","Package":"debug/tparse-24","Output":"==================\n"} 20 | {"Time":"2018-12-16T21:21:52.635145-05:00","Action":"output","Package":"debug/tparse-24","Output":"WARNING: DATA RACE\n"} 21 | {"Time":"2018-12-16T21:21:52.635154-05:00","Action":"output","Package":"debug/tparse-24","Output":"Write at 0x00c0000c8010 by goroutine 9:\n"} 22 | {"Time":"2018-12-16T21:21:52.635161-05:00","Action":"output","Package":"debug/tparse-24","Output":" debug/tparse-24.TestRace1.func1()\n"} 23 | {"Time":"2018-12-16T21:21:52.635168-05:00","Action":"output","Package":"debug/tparse-24","Output":" /Users/michael.fridman/go/src/debug/tparse-24/some_test.go:8 +0x38\n"} 24 | {"Time":"2018-12-16T21:21:52.635178-05:00","Action":"output","Package":"debug/tparse-24","Output":"\n"} 25 | {"Time":"2018-12-16T21:21:52.635184-05:00","Action":"output","Package":"debug/tparse-24","Output":"Previous write at 0x00c0000c8010 by goroutine 6:\n"} 26 | {"Time":"2018-12-16T21:21:52.63519-05:00","Action":"output","Package":"debug/tparse-24","Output":" debug/tparse-24.TestRace1()\n"} 27 | {"Time":"2018-12-16T21:21:52.635201-05:00","Action":"output","Package":"debug/tparse-24","Output":" /Users/michael.fridman/go/src/debug/tparse-24/some_test.go:9 +0x96\n"} 28 | {"Time":"2018-12-16T21:21:52.635208-05:00","Action":"output","Package":"debug/tparse-24","Output":" testing.tRunner()\n"} 29 | {"Time":"2018-12-16T21:21:52.635217-05:00","Action":"output","Package":"debug/tparse-24","Output":" /usr/local/go/src/testing/testing.go:827 +0x162\n"} 30 | {"Time":"2018-12-16T21:21:52.635223-05:00","Action":"output","Package":"debug/tparse-24","Output":"\n"} 31 | {"Time":"2018-12-16T21:21:52.635228-05:00","Action":"output","Package":"debug/tparse-24","Output":"Goroutine 9 (running) created at:\n"} 32 | {"Time":"2018-12-16T21:21:52.635237-05:00","Action":"output","Package":"debug/tparse-24","Output":" debug/tparse-24.TestRace1()\n"} 33 | {"Time":"2018-12-16T21:21:52.635242-05:00","Action":"output","Package":"debug/tparse-24","Output":" /Users/michael.fridman/go/src/debug/tparse-24/some_test.go:8 +0x88\n"} 34 | {"Time":"2018-12-16T21:21:52.635248-05:00","Action":"output","Package":"debug/tparse-24","Output":" testing.tRunner()\n"} 35 | {"Time":"2018-12-16T21:21:52.635253-05:00","Action":"output","Package":"debug/tparse-24","Output":" /usr/local/go/src/testing/testing.go:827 +0x162\n"} 36 | {"Time":"2018-12-16T21:21:52.635276-05:00","Action":"output","Package":"debug/tparse-24","Output":"\n"} 37 | {"Time":"2018-12-16T21:21:52.635285-05:00","Action":"output","Package":"debug/tparse-24","Output":"Goroutine 6 (finished) created at:\n"} 38 | {"Time":"2018-12-16T21:21:52.635291-05:00","Action":"output","Package":"debug/tparse-24","Output":" testing.(*T).Run()\n"} 39 | {"Time":"2018-12-16T21:21:52.635298-05:00","Action":"output","Package":"debug/tparse-24","Output":" /usr/local/go/src/testing/testing.go:878 +0x650\n"} 40 | {"Time":"2018-12-16T21:21:52.635304-05:00","Action":"output","Package":"debug/tparse-24","Output":" testing.runTests.func1()\n"} 41 | {"Time":"2018-12-16T21:21:52.635309-05:00","Action":"output","Package":"debug/tparse-24","Output":" /usr/local/go/src/testing/testing.go:1119 +0xa8\n"} 42 | {"Time":"2018-12-16T21:21:52.635319-05:00","Action":"output","Package":"debug/tparse-24","Output":" testing.tRunner()\n"} 43 | {"Time":"2018-12-16T21:21:52.635329-05:00","Action":"output","Package":"debug/tparse-24","Output":" /usr/local/go/src/testing/testing.go:827 +0x162\n"} 44 | {"Time":"2018-12-16T21:21:52.635335-05:00","Action":"output","Package":"debug/tparse-24","Output":" testing.runTests()\n"} 45 | {"Time":"2018-12-16T21:21:52.635344-05:00","Action":"output","Package":"debug/tparse-24","Output":" /usr/local/go/src/testing/testing.go:1117 +0x4ee\n"} 46 | {"Time":"2018-12-16T21:21:52.63535-05:00","Action":"output","Package":"debug/tparse-24","Output":" testing.(*M).Run()\n"} 47 | {"Time":"2018-12-16T21:21:52.635356-05:00","Action":"output","Package":"debug/tparse-24","Output":" /usr/local/go/src/testing/testing.go:1034 +0x2ee\n"} 48 | {"Time":"2018-12-16T21:21:52.635362-05:00","Action":"output","Package":"debug/tparse-24","Output":" main.main()\n"} 49 | {"Time":"2018-12-16T21:21:52.63537-05:00","Action":"output","Package":"debug/tparse-24","Output":" _testmain.go:44 +0x221\n"} 50 | {"Time":"2018-12-16T21:21:52.635378-05:00","Action":"output","Package":"debug/tparse-24","Output":"==================\n"} 51 | {"Time":"2018-12-16T21:21:53.635797-05:00","Action":"output","Package":"debug/tparse-24","Output":"Found 1 data race(s)\n"} 52 | {"Time":"2018-12-16T21:21:53.637742-05:00","Action":"output","Package":"debug/tparse-24","Output":"FAIL\tdebug/tparse-24\t1.017s\n"} 53 | {"Time":"2018-12-16T21:21:53.637783-05:00","Action":"pass","Package":"debug/tparse-24","Elapsed":1.018} -------------------------------------------------------------------------------- /tests/testdata/race/test_04.jsonl: -------------------------------------------------------------------------------- 1 | {"Time":"2022-05-23T10:24:01.895489-04:00","Action":"run","Package":"github.com/mfridman/debug-go/testing","Test":"TestRace"} 2 | {"Time":"2022-05-23T10:24:01.895869-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestRace","Output":"=== RUN TestRace\n"} 3 | {"Time":"2022-05-23T10:24:01.895882-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestRace","Output":"2\n"} 4 | {"Time":"2022-05-23T10:24:01.895886-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestRace","Output":"3\n"} 5 | {"Time":"2022-05-23T10:24:01.896021-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestRace","Output":"==================\n"} 6 | {"Time":"2022-05-23T10:24:01.896034-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestRace","Output":"WARNING: DATA RACE\n"} 7 | {"Time":"2022-05-23T10:24:01.896039-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestRace","Output":"Read at 0x00c0000b0188 by goroutine 8:\n"} 8 | {"Time":"2022-05-23T10:24:01.896047-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestRace","Output":" github.com/mfridman/debug-go/testing_test.TestRace.func1()\n"} 9 | {"Time":"2022-05-23T10:24:01.896053-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestRace","Output":" /Users/mfridman/src/github.com/mfridman/debug-go/testing/main_test.go:55 +0x3c\n"} 10 | {"Time":"2022-05-23T10:24:01.896059-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestRace","Output":"\n"} 11 | {"Time":"2022-05-23T10:24:01.896063-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestRace","Output":"Previous write at 0x00c0000b0188 by goroutine 7:\n"} 12 | {"Time":"2022-05-23T10:24:01.896066-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestRace","Output":" github.com/mfridman/debug-go/testing_test.TestRace()\n"} 13 | {"Time":"2022-05-23T10:24:01.896069-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestRace","Output":" /Users/mfridman/src/github.com/mfridman/debug-go/testing/main_test.go:53 +0x88\n"} 14 | {"Time":"2022-05-23T10:24:01.896075-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestRace","Output":" testing.tRunner()\n"} 15 | {"Time":"2022-05-23T10:24:01.896082-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestRace","Output":" /opt/homebrew/Cellar/go/1.18.1/libexec/src/testing/testing.go:1439 +0x18c\n"} 16 | {"Time":"2022-05-23T10:24:01.896088-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestRace","Output":" testing.(*T).Run.func1()\n"} 17 | {"Time":"2022-05-23T10:24:01.896091-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestRace","Output":" /opt/homebrew/Cellar/go/1.18.1/libexec/src/testing/testing.go:1486 +0x44\n"} 18 | {"Time":"2022-05-23T10:24:01.896094-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestRace","Output":"\n"} 19 | {"Time":"2022-05-23T10:24:01.896097-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestRace","Output":"Goroutine 8 (running) created at:\n"} 20 | {"Time":"2022-05-23T10:24:01.8961-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestRace","Output":" github.com/mfridman/debug-go/testing_test.TestRace()\n"} 21 | {"Time":"2022-05-23T10:24:01.896103-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestRace","Output":" /Users/mfridman/src/github.com/mfridman/debug-go/testing/main_test.go:54 +0x70\n"} 22 | {"Time":"2022-05-23T10:24:01.896106-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestRace","Output":" testing.tRunner()\n"} 23 | {"Time":"2022-05-23T10:24:01.896109-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestRace","Output":" /opt/homebrew/Cellar/go/1.18.1/libexec/src/testing/testing.go:1439 +0x18c\n"} 24 | {"Time":"2022-05-23T10:24:01.89612-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestRace","Output":" testing.(*T).Run.func1()\n"} 25 | {"Time":"2022-05-23T10:24:01.896123-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestRace","Output":" /opt/homebrew/Cellar/go/1.18.1/libexec/src/testing/testing.go:1486 +0x44\n"} 26 | {"Time":"2022-05-23T10:24:01.896125-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestRace","Output":"\n"} 27 | {"Time":"2022-05-23T10:24:01.896128-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestRace","Output":"Goroutine 7 (running) created at:\n"} 28 | {"Time":"2022-05-23T10:24:01.896131-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestRace","Output":" testing.(*T).Run()\n"} 29 | {"Time":"2022-05-23T10:24:01.896134-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestRace","Output":" /opt/homebrew/Cellar/go/1.18.1/libexec/src/testing/testing.go:1486 +0x560\n"} 30 | {"Time":"2022-05-23T10:24:01.896137-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestRace","Output":" testing.runTests.func1()\n"} 31 | {"Time":"2022-05-23T10:24:01.89614-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestRace","Output":" /opt/homebrew/Cellar/go/1.18.1/libexec/src/testing/testing.go:1839 +0x94\n"} 32 | {"Time":"2022-05-23T10:24:01.896143-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestRace","Output":" testing.tRunner()\n"} 33 | {"Time":"2022-05-23T10:24:01.896146-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestRace","Output":" /opt/homebrew/Cellar/go/1.18.1/libexec/src/testing/testing.go:1439 +0x18c\n"} 34 | {"Time":"2022-05-23T10:24:01.896149-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestRace","Output":" testing.runTests()\n"} 35 | {"Time":"2022-05-23T10:24:01.896152-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestRace","Output":" /opt/homebrew/Cellar/go/1.18.1/libexec/src/testing/testing.go:1837 +0x6c8\n"} 36 | {"Time":"2022-05-23T10:24:01.896155-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestRace","Output":" testing.(*M).Run()\n"} 37 | {"Time":"2022-05-23T10:24:01.896157-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestRace","Output":" /opt/homebrew/Cellar/go/1.18.1/libexec/src/testing/testing.go:1719 +0x878\n"} 38 | {"Time":"2022-05-23T10:24:01.89616-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestRace","Output":" main.main()\n"} 39 | {"Time":"2022-05-23T10:24:01.896163-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestRace","Output":" _testmain.go:47 +0x2fc\n"} 40 | {"Time":"2022-05-23T10:24:01.896166-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestRace","Output":"==================\n"} 41 | {"Time":"2022-05-23T10:24:01.896169-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestRace","Output":"3\n"} 42 | {"Time":"2022-05-23T10:24:01.896171-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestRace","Output":"3\n"} 43 | {"Time":"2022-05-23T10:24:01.896178-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestRace","Output":"5\n"} 44 | {"Time":"2022-05-23T10:24:01.896181-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestRace","Output":" testing.go:1312: race detected during execution of test\n"} 45 | {"Time":"2022-05-23T10:24:01.896199-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestRace","Output":"--- FAIL: TestRace (0.00s)\n"} 46 | {"Time":"2022-05-23T10:24:01.896221-04:00","Action":"fail","Package":"github.com/mfridman/debug-go/testing","Test":"TestRace","Elapsed":0} 47 | {"Time":"2022-05-23T10:24:01.896233-04:00","Action":"cont","Package":"github.com/mfridman/debug-go/testing"} 48 | {"Time":"2022-05-23T10:24:01.896243-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Output":"=== CONT \n"} 49 | {"Time":"2022-05-23T10:24:01.896248-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Output":" testing.go:1312: race detected during execution of test\n"} 50 | {"Time":"2022-05-23T10:24:01.896251-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Output":"FAIL\n"} 51 | {"Time":"2022-05-23T10:24:01.896704-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Output":"FAIL\tgithub.com/mfridman/debug-go/testing\t0.158s\n"} 52 | {"Time":"2022-05-23T10:24:01.896714-04:00","Action":"fail","Package":"github.com/mfridman/debug-go/testing","Elapsed":0.158} 53 | -------------------------------------------------------------------------------- /tests/testdata/race/test_06.jsonl: -------------------------------------------------------------------------------- 1 | {"Time":"2022-05-23T10:22:27.087816-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Output":"testing: warning: no tests to run\n"} 2 | {"Time":"2022-05-23T10:22:27.088274-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Output":"PASS\n"} 3 | {"Time":"2022-05-23T10:22:27.088645-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Output":"2\n"} 4 | {"Time":"2022-05-23T10:22:27.089042-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Output":"==================\n"} 5 | {"Time":"2022-05-23T10:22:27.089065-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Output":"WARNING: DATA RACE\n"} 6 | {"Time":"2022-05-23T10:22:27.08908-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Output":"Read at 0x00c0000b0178 by goroutine 7:\n"} 7 | {"Time":"2022-05-23T10:22:27.089102-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Output":" github.com/mfridman/debug-go/testing_test.TestMain.func1()\n"} 8 | {"Time":"2022-05-23T10:22:27.089119-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Output":" /Users/mfridman/src/github.com/mfridman/debug-go/testing/main_test.go:16 +0x3c\n"} 9 | {"Time":"2022-05-23T10:22:27.089138-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Output":"\n"} 10 | {"Time":"2022-05-23T10:22:27.089151-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Output":"Previous write at 0x00c0000b0178 by main goroutine:\n"} 11 | {"Time":"2022-05-23T10:22:27.089163-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Output":" github.com/mfridman/debug-go/testing_test.TestMain()\n"} 12 | {"Time":"2022-05-23T10:22:27.089175-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Output":" /Users/mfridman/src/github.com/mfridman/debug-go/testing/main_test.go:14 +0x98\n"} 13 | {"Time":"2022-05-23T10:22:27.089187-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Output":" main.main()\n"} 14 | {"Time":"2022-05-23T10:22:27.089198-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Output":" _testmain.go:47 +0x304\n"} 15 | {"Time":"2022-05-23T10:22:27.08921-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Output":"\n"} 16 | {"Time":"2022-05-23T10:22:27.089222-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Output":"Goroutine 7 (running) created at:\n"} 17 | {"Time":"2022-05-23T10:22:27.089256-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Output":" github.com/mfridman/debug-go/testing_test.TestMain()\n"} 18 | {"Time":"2022-05-23T10:22:27.089269-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Output":" /Users/mfridman/src/github.com/mfridman/debug-go/testing/main_test.go:15 +0x80\n"} 19 | {"Time":"2022-05-23T10:22:27.089282-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Output":" main.main()\n"} 20 | {"Time":"2022-05-23T10:22:27.089293-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Output":" _testmain.go:47 +0x304\n"} 21 | {"Time":"2022-05-23T10:22:27.089305-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Output":"==================\n"} 22 | {"Time":"2022-05-23T10:22:27.089317-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Output":"2\n"} 23 | {"Time":"2022-05-23T10:22:27.089338-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Output":"2\n"} 24 | {"Time":"2022-05-23T10:22:27.08935-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Output":"4\n"} 25 | {"Time":"2022-05-23T10:22:27.089364-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Output":"5\n"} 26 | {"Time":"2022-05-23T10:22:27.10081-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Output":"Found 1 data race(s)\n"} 27 | {"Time":"2022-05-23T10:22:27.101697-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Output":"FAIL\tgithub.com/mfridman/debug-go/testing\t0.173s\n"} 28 | {"Time":"2022-05-23T10:22:27.101757-04:00","Action":"fail","Package":"github.com/mfridman/debug-go/testing","Elapsed":0.173} 29 | -------------------------------------------------------------------------------- /tests/testdata/race/test_08.jsonl: -------------------------------------------------------------------------------- 1 | {"Time":"2022-05-23T10:28:13.239864-04:00","Action":"run","Package":"github.com/mfridman/debug-go/testing","Test":"TestRace"} 2 | {"Time":"2022-05-23T10:28:13.24012-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestRace","Output":"=== RUN TestRace\n"} 3 | {"Time":"2022-05-23T10:28:13.240135-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestRace","Output":"2\n"} 4 | {"Time":"2022-05-23T10:28:13.240149-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestRace","Output":"3\n"} 5 | {"Time":"2022-05-23T10:28:13.240422-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestRace","Output":"==================\n"} 6 | {"Time":"2022-05-23T10:28:13.240433-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestRace","Output":"WARNING: DATA RACE\n"} 7 | {"Time":"2022-05-23T10:28:13.240438-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestRace","Output":"Read at 0x00c0000a8f68 by goroutine 8:\n"} 8 | {"Time":"2022-05-23T10:28:13.240446-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestRace","Output":" github.com/mfridman/debug-go/pkg/racey.RaceFunc.func1()\n"} 9 | {"Time":"2022-05-23T10:28:13.240453-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestRace","Output":" /Users/mfridman/src/github.com/mfridman/debug-go/pkg/racey/racey.go:13 +0x3c\n"} 10 | {"Time":"2022-05-23T10:28:13.240468-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestRace","Output":"\n"} 11 | {"Time":"2022-05-23T10:28:13.240473-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestRace","Output":"Previous write at 0x00c0000a8f68 by goroutine 7:\n"} 12 | {"Time":"2022-05-23T10:28:13.240478-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestRace","Output":" github.com/mfridman/debug-go/pkg/racey.RaceFunc()\n"} 13 | {"Time":"2022-05-23T10:28:13.240484-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestRace","Output":" /Users/mfridman/src/github.com/mfridman/debug-go/pkg/racey/racey.go:11 +0x88\n"} 14 | {"Time":"2022-05-23T10:28:13.240489-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestRace","Output":" github.com/mfridman/debug-go/testing_test.TestRace()\n"} 15 | {"Time":"2022-05-23T10:28:13.240495-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestRace","Output":" /Users/mfridman/src/github.com/mfridman/debug-go/testing/main_test.go:52 +0x28\n"} 16 | {"Time":"2022-05-23T10:28:13.240502-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestRace","Output":" testing.tRunner()\n"} 17 | {"Time":"2022-05-23T10:28:13.240509-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestRace","Output":" /opt/homebrew/Cellar/go/1.18.1/libexec/src/testing/testing.go:1439 +0x18c\n"} 18 | {"Time":"2022-05-23T10:28:13.240515-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestRace","Output":" testing.(*T).Run.func1()\n"} 19 | {"Time":"2022-05-23T10:28:13.240519-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestRace","Output":" /opt/homebrew/Cellar/go/1.18.1/libexec/src/testing/testing.go:1486 +0x44\n"} 20 | {"Time":"2022-05-23T10:28:13.240524-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestRace","Output":"\n"} 21 | {"Time":"2022-05-23T10:28:13.240528-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestRace","Output":"Goroutine 8 (running) created at:\n"} 22 | {"Time":"2022-05-23T10:28:13.240533-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestRace","Output":" github.com/mfridman/debug-go/pkg/racey.RaceFunc()\n"} 23 | {"Time":"2022-05-23T10:28:13.240537-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestRace","Output":" /Users/mfridman/src/github.com/mfridman/debug-go/pkg/racey/racey.go:12 +0x70\n"} 24 | {"Time":"2022-05-23T10:28:13.240562-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestRace","Output":" github.com/mfridman/debug-go/testing_test.TestRace()\n"} 25 | {"Time":"2022-05-23T10:28:13.240566-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestRace","Output":" /Users/mfridman/src/github.com/mfridman/debug-go/testing/main_test.go:52 +0x28\n"} 26 | {"Time":"2022-05-23T10:28:13.240569-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestRace","Output":" testing.tRunner()\n"} 27 | {"Time":"2022-05-23T10:28:13.240572-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestRace","Output":" /opt/homebrew/Cellar/go/1.18.1/libexec/src/testing/testing.go:1439 +0x18c\n"} 28 | {"Time":"2022-05-23T10:28:13.240575-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestRace","Output":" testing.(*T).Run.func1()\n"} 29 | {"Time":"2022-05-23T10:28:13.240578-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestRace","Output":" /opt/homebrew/Cellar/go/1.18.1/libexec/src/testing/testing.go:1486 +0x44\n"} 30 | {"Time":"2022-05-23T10:28:13.240581-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestRace","Output":"\n"} 31 | {"Time":"2022-05-23T10:28:13.240584-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestRace","Output":"Goroutine 7 (running) created at:\n"} 32 | {"Time":"2022-05-23T10:28:13.240587-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestRace","Output":" testing.(*T).Run()\n"} 33 | {"Time":"2022-05-23T10:28:13.24059-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestRace","Output":" /opt/homebrew/Cellar/go/1.18.1/libexec/src/testing/testing.go:1486 +0x560\n"} 34 | {"Time":"2022-05-23T10:28:13.240593-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestRace","Output":" testing.runTests.func1()\n"} 35 | {"Time":"2022-05-23T10:28:13.240596-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestRace","Output":" /opt/homebrew/Cellar/go/1.18.1/libexec/src/testing/testing.go:1839 +0x94\n"} 36 | {"Time":"2022-05-23T10:28:13.240599-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestRace","Output":" testing.tRunner()\n"} 37 | {"Time":"2022-05-23T10:28:13.240602-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestRace","Output":" /opt/homebrew/Cellar/go/1.18.1/libexec/src/testing/testing.go:1439 +0x18c\n"} 38 | {"Time":"2022-05-23T10:28:13.240605-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestRace","Output":" testing.runTests()\n"} 39 | {"Time":"2022-05-23T10:28:13.240608-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestRace","Output":" /opt/homebrew/Cellar/go/1.18.1/libexec/src/testing/testing.go:1837 +0x6c8\n"} 40 | {"Time":"2022-05-23T10:28:13.240611-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestRace","Output":" testing.(*M).Run()\n"} 41 | {"Time":"2022-05-23T10:28:13.240614-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestRace","Output":" /opt/homebrew/Cellar/go/1.18.1/libexec/src/testing/testing.go:1719 +0x878\n"} 42 | {"Time":"2022-05-23T10:28:13.240617-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestRace","Output":" main.main()\n"} 43 | {"Time":"2022-05-23T10:28:13.24062-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestRace","Output":" _testmain.go:47 +0x2fc\n"} 44 | {"Time":"2022-05-23T10:28:13.240623-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestRace","Output":"==================\n"} 45 | {"Time":"2022-05-23T10:28:13.240626-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestRace","Output":"3\n"} 46 | {"Time":"2022-05-23T10:28:13.240635-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestRace","Output":"4\n"} 47 | {"Time":"2022-05-23T10:28:13.240638-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestRace","Output":"5\n"} 48 | {"Time":"2022-05-23T10:28:13.240641-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestRace","Output":" testing.go:1312: race detected during execution of test\n"} 49 | {"Time":"2022-05-23T10:28:13.240651-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Test":"TestRace","Output":"--- FAIL: TestRace (0.00s)\n"} 50 | {"Time":"2022-05-23T10:28:13.240655-04:00","Action":"fail","Package":"github.com/mfridman/debug-go/testing","Test":"TestRace","Elapsed":0} 51 | {"Time":"2022-05-23T10:28:13.240659-04:00","Action":"cont","Package":"github.com/mfridman/debug-go/testing"} 52 | {"Time":"2022-05-23T10:28:13.240664-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Output":"=== CONT \n"} 53 | {"Time":"2022-05-23T10:28:13.240667-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Output":" testing.go:1312: race detected during execution of test\n"} 54 | {"Time":"2022-05-23T10:28:13.24067-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Output":"FAIL\n"} 55 | {"Time":"2022-05-23T10:28:13.241273-04:00","Action":"output","Package":"github.com/mfridman/debug-go/testing","Output":"FAIL\tgithub.com/mfridman/debug-go/testing\t0.116s\n"} 56 | {"Time":"2022-05-23T10:28:13.241305-04:00","Action":"fail","Package":"github.com/mfridman/debug-go/testing","Elapsed":0.116} 57 | --------------------------------------------------------------------------------