Skip to content

Commit

Permalink
Merge pull request #76 from dnephin/add-post-run-command
Browse files Browse the repository at this point in the history
Add post-run-command hook
  • Loading branch information
dnephin committed May 16, 2020
2 parents bbd252b + 8be9d46 commit 0a6ff94
Show file tree
Hide file tree
Showing 9 changed files with 221 additions and 6 deletions.
60 changes: 60 additions & 0 deletions contrib/notify/notify-macos.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package main

import (
"fmt"
"log"
"os"
"os/exec"
"strconv"
)

func main() {
total := envInt("TOTAL")
skipped := envInt("SKIPPED")
failed := envInt("FAILED")
errors := envInt("ERRORS")

emoji := "✅"
title := "Passed"
switch {
case errors > 0:
emoji = "⚠️"
title = "Errored"
case failed > 0:
emoji = "❌"
title = "Failed"
case skipped > 0:
title = "Passed with skipped"
}

subtitle := fmt.Sprintf("%d Tests Run", total)
if errors > 0 {
subtitle += fmt.Sprintf(", %d Errored", errors)
}
if failed > 0 {
subtitle += fmt.Sprintf(", %d Failed", failed)
}
if skipped > 0 {
subtitle += fmt.Sprintf(", %d Skipped", skipped)
}

args := []string{
"-title", emoji + " " + title,
"-group", "gotestsum",
"-subtitle", subtitle,
}
log.Printf("terminal-notifier %#v", args)
err := exec.Command("terminal-notifier", args...).Run()
if err != nil {
log.Fatalf("Failed to exec: %v", err)
}
}

func envInt(name string) int {
val := os.Getenv("TESTS_" + name)
n, err := strconv.Atoi(val)
if err != nil {
return 0
}
return n
}
28 changes: 28 additions & 0 deletions flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"path"
"strings"

"github.com/google/shlex"
"github.com/pkg/errors"
"gotest.tools/gotestsum/internal/junitxml"
"gotest.tools/gotestsum/testjson"
Expand Down Expand Up @@ -84,3 +85,30 @@ func (f *junitFieldFormatValue) Value() junitxml.FormatFunc {
}
return f.value
}

type commandValue struct {
original string
command []string
}

func (c *commandValue) String() string {
return c.original
}

func (c *commandValue) Set(raw string) error {
var err error
c.command, err = shlex.Split(raw)
c.original = raw
return err
}

func (c *commandValue) Type() string {
return "command"
}

func (c *commandValue) Value() []string {
if c == nil {
return nil
}
return c.command
}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module gotest.tools/gotestsum
require (
github.com/fatih/color v1.9.0
github.com/google/go-cmp v0.3.0
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510
github.com/jonboulle/clockwork v0.1.0
github.com/mattn/go-colorable v0.1.6 // indirect
github.com/pkg/errors v0.9.1
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s=
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
Expand Down
29 changes: 26 additions & 3 deletions handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"
"io"
"os"
"os/exec"

"github.com/pkg/errors"
"gotest.tools/gotestsum/internal/junitxml"
Expand Down Expand Up @@ -48,14 +49,14 @@ func (h *eventHandler) Close() error {

var _ testjson.EventHandler = &eventHandler{}

func newEventHandler(opts *options, stdout io.Writer, stderr io.Writer) (*eventHandler, error) {
formatter := testjson.NewEventFormatter(stdout, opts.format)
func newEventHandler(opts *options) (*eventHandler, error) {
formatter := testjson.NewEventFormatter(opts.stdout, opts.format)
if formatter == nil {
return nil, errors.Errorf("unknown format %s", opts.format)
}
handler := &eventHandler{
formatter: formatter,
err: stderr,
err: opts.stderr,
}
var err error
if opts.jsonFile != "" {
Expand Down Expand Up @@ -86,3 +87,25 @@ func writeJUnitFile(opts *options, execution *testjson.Execution) error {
FormatTestCaseClassname: opts.junitTestCaseClassnameFormat.Value(),
})
}

func postRunHook(opts *options, execution *testjson.Execution) error {
command := opts.postRunHookCmd.Value()
if len(command) == 0 {
return nil
}

cmd := exec.Command(command[0], command[1:]...)
cmd.Stdout = opts.stdout
cmd.Stderr = opts.stderr
cmd.Env = append(
os.Environ(),
"GOTESTSUM_JSONFILE="+opts.jsonFile,
"GOTESTSUM_JUNITFILE="+opts.junitFile,
fmt.Sprintf("TESTS_TOTAL=%d", execution.Total()),
fmt.Sprintf("TESTS_FAILED=%d", len(execution.Failed())),
fmt.Sprintf("TESTS_SKIPPED=%d", len(execution.Skipped())),
fmt.Sprintf("TESTS_ERRORS=%d", len(execution.Errors())),
)
// TODO: send a more detailed report to stdin?
return cmd.Run()
}
48 changes: 48 additions & 0 deletions handler_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package main

import (
"bytes"
"os"
"strings"
"testing"

"gotest.tools/gotestsum/testjson"
"gotest.tools/v3/assert"
"gotest.tools/v3/env"
"gotest.tools/v3/golden"
)

func TestPostRunHook(t *testing.T) {
command := &commandValue{}
err := command.Set("go run ./testdata/postrunhook/main.go")
assert.NilError(t, err)

buf := new(bytes.Buffer)
opts := &options{
postRunHookCmd: command,
jsonFile: "events.json",
junitFile: "junit.xml",
stdout: buf,
}

defer env.Patch(t, "GOTESTSUM_FORMAT", "short")()

exec := newExecFromTestData(t)
err = postRunHook(opts, exec)
assert.NilError(t, err)
golden.Assert(t, buf.String(), "post-run-hook-expected")
}

func newExecFromTestData(t *testing.T) *testjson.Execution {
t.Helper()
f, err := os.Open("testjson/testdata/go-test-json.out")
assert.NilError(t, err)
defer f.Close() // nolint: errcheck

exec, err := testjson.ScanTestOutput(testjson.ScanConfig{
Stdout: f,
Stderr: strings.NewReader(""),
})
assert.NilError(t, err)
return exec
}
18 changes: 15 additions & 3 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@ func setupFlags(name string) (*pflag.FlagSet, *options) {
noSummary: newNoSummaryValue(),
junitTestCaseClassnameFormat: &junitFieldFormatValue{},
junitTestSuiteNameFormat: &junitFieldFormatValue{},
postRunHookCmd: &commandValue{},
stdout: os.Stdout,
stderr: os.Stderr,
}
flags := pflag.NewFlagSet(name, pflag.ContinueOnError)
flags.SetInterspersed(false)
Expand Down Expand Up @@ -109,6 +112,8 @@ Formats:
"format the testsuite name field as: "+junitFieldFormatValues)
flags.Var(opts.junitTestCaseClassnameFormat, "junitfile-testcase-classname",
"format the testcase classname field as: "+junitFieldFormatValues)
flags.Var(opts.postRunHookCmd, "post-run-command",
"command to run after the tests have completed")
flags.BoolVar(&opts.version, "version", false, "show version and exit")
return flags, opts
}
Expand All @@ -127,11 +132,16 @@ type options struct {
rawCommand bool
jsonFile string
junitFile string
postRunHookCmd *commandValue
noColor bool
noSummary *noSummaryValue
junitTestSuiteNameFormat *junitFieldFormatValue
junitTestCaseClassnameFormat *junitFieldFormatValue
version bool

// shims for testing
stdout io.Writer
stderr io.Writer
}

func setupLogging(opts *options) {
Expand All @@ -151,8 +161,7 @@ func run(opts *options) error {
}
defer goTestProc.cancel()

out := os.Stdout
handler, err := newEventHandler(opts, out, os.Stderr)
handler, err := newEventHandler(opts)
if err != nil {
return err
}
Expand All @@ -165,10 +174,13 @@ func run(opts *options) error {
if err != nil {
return err
}
testjson.PrintSummary(out, exec, opts.noSummary.value)
testjson.PrintSummary(opts.stdout, exec, opts.noSummary.value)
if err := writeJUnitFile(opts, exec); err != nil {
return err
}
if err := postRunHook(opts, exec); err != nil {
return err
}
return goTestProc.cmd.Wait()
}

Expand Down
7 changes: 7 additions & 0 deletions testdata/post-run-hook-expected
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
GOTESTSUM_FORMAT=short
GOTESTSUM_JSONFILE=events.json
GOTESTSUM_JUNITFILE=junit.xml
TESTS_ERRORS=0
TESTS_FAILED=5
TESTS_SKIPPED=4
TESTS_TOTAL=46
34 changes: 34 additions & 0 deletions testdata/postrunhook/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package main

import (
"errors"
"fmt"
"os"
"sort"
"strings"
)

func main() {
if err := run(); err != nil {
fmt.Fprintln(os.Stderr, err.Error())
os.Exit(1)
}
}

func run() error {
environ := os.Environ()
sort.Strings(environ)
for _, v := range environ {
for _, prefix := range []string{"TESTS_", "GOTESTSUM_"} {
if strings.HasPrefix(v, prefix) {
fmt.Println(v)
}
}
}

err := os.Getenv("TEST_STUB_ERROR")
if err != "" {
return errors.New(err)
}
return nil
}

0 comments on commit 0a6ff94

Please sign in to comment.