diff --git a/gotestdox.go b/gotestdox.go index b905ed6..659c7f7 100644 --- a/gotestdox.go +++ b/gotestdox.go @@ -1,14 +1,10 @@ package gotestdox import ( - "bufio" - "encoding/json" "fmt" - "io" "os" - "os/exec" - "sort" - "strings" + + "github.com/bitfield/gotestdox/pkg/gotestdox" "github.com/fatih/color" "github.com/mattn/go-isatty" @@ -34,12 +30,20 @@ See https://github.com/bitfield/gotestdox for more information.` // Main runs the command-line interface for gotestdox. The exit status for the // binary is 0 if the tests passed, or 1 if the tests failed, or there was some // error. +// +// # Colour +// +// If the program is attached to an interactive terminal, as determined by +// [github.com/mattn/go-isatty], and the NO_COLOR environment variable is not +// set, check marks will be shown in green and x's in red. func Main() int { if len(os.Args) > 1 && os.Args[1] == "-h" { fmt.Println(Usage) return 0 } - td := NewTestDoxer() + td := gotestdox.NewTestDoxer() + td.Fail = color.RedString(td.Fail) + td.Pass = color.GreenString(td.Pass) if isatty.IsTerminal(os.Stdin.Fd()) { td.ExecGoTest(os.Args[1:]) } else { @@ -50,210 +54,3 @@ func Main() int { } return 0 } - -// TestDoxer holds the state and config associated with a particular invocation -// of 'go test'. -type TestDoxer struct { - Stdin io.Reader - Stdout, Stderr io.Writer - OK bool -} - -// NewTestDoxer returns a [*TestDoxer] configured with the default I/O streams: -// [os.Stdin], [os.Stdout], and [os.Stderr]. -func NewTestDoxer() *TestDoxer { - return &TestDoxer{ - Stdin: os.Stdin, - Stdout: os.Stdout, - Stderr: os.Stderr, - } -} - -// ExecGoTest runs the 'go test -json' command, with any extra args supplied by -// the user, and consumes its output. Any errors are reported to td's Stderr -// stream, including the full command line that was run. If all tests passed, -// td.OK will be true. If there was a test failure, or 'go test' returned some -// error, then td.OK will be false. -func (td *TestDoxer) ExecGoTest(userArgs []string) { - args := []string{"test", "-json"} - args = append(args, userArgs...) - cmd := exec.Command("go", args...) - goTestOutput, err := cmd.StdoutPipe() - if err != nil { - fmt.Fprintln(td.Stderr, cmd.Args, err) - return - } - cmd.Stderr = td.Stderr - if err := cmd.Start(); err != nil { - fmt.Fprintln(td.Stderr, cmd.Args, err) - return - } - td.Stdin = goTestOutput - td.Filter() - if err := cmd.Wait(); err != nil { - td.OK = false - fmt.Fprintln(td.Stderr, cmd.Args, err) - return - } -} - -// Filter reads from td's Stdin stream, line by line, processing JSON records -// emitted by 'go test -json'. -// -// For each Go package it sees records about, it will print the full name of -// the package to td.Stdout, followed by a line giving the pass/fail status and -// the prettified name of each test, sorted alphabetically. -// -// If all tests passed, td.OK will be true at the end. If not, or if there was -// a parsing error, it will be false. Errors will be reported to td.Stderr. -func (td *TestDoxer) Filter() { - td.OK = true - results := map[string][]Event{} - outputs := map[string][]string{} - scanner := bufio.NewScanner(td.Stdin) - for scanner.Scan() { - event, err := ParseJSON(scanner.Text()) - if err != nil { - td.OK = false - fmt.Fprintln(td.Stderr, err) - return - } - switch { - case event.IsPackageResult(): - fmt.Fprintf(td.Stdout, "%s:\n", event.Package) - tests := results[event.Package] - sort.Slice(tests, func(i, j int) bool { - return tests[i].Sentence < tests[j].Sentence - }) - for _, r := range tests { - fmt.Fprintln(td.Stdout, r.String()) - if r.Action == ActionFail { - for _, line := range outputs[r.Test] { - fmt.Fprint(td.Stdout, line) - } - } - } - fmt.Fprintln(td.Stdout) - case event.IsOutput(): - outputs[event.Test] = append(outputs[event.Test], event.Output) - case event.IsTestResult(), event.IsFuzzFail(): - event.Sentence = Prettify(event.Test) - results[event.Package] = append(results[event.Package], event) - if event.Action == ActionFail { - td.OK = false - } - } - } -} - -// ParseJSON takes a string representing a single JSON test record as emitted -// by 'go test -json', and attempts to parse it into an [Event], returning any -// parsing error encountered. -func ParseJSON(line string) (Event, error) { - event := Event{} - err := json.Unmarshal([]byte(line), &event) - if err != nil { - return Event{}, fmt.Errorf("parsing JSON: %w\ninput: %s", err, line) - } - return event, nil -} - -const ( - ActionPass = "pass" - ActionFail = "fail" -) - -// Event represents a Go test event as recorded by the 'go test -json' command. -// It does not attempt to unmarshal all the data, only those fields it needs to -// know about. It is based on the (unexported) 'event' struct used by Go's -// [cmd/internal/test2json] package. -type Event struct { - Action string - Package string - Test string - Sentence string - Output string - Elapsed float64 -} - -// String formats a test Event for display. The prettified test name will be -// prefixed by a ✔ if the test passed, or an x if it failed. -// -// The sentence generated by [Prettify] from the name of the test will be -// shown, followed by the elapsed time in parentheses, to 2 decimal places. -// -// # Colour -// -// If the program is attached to an interactive terminal, as determined by -// [github.com/mattn/go-isatty], and the NO_COLOR environment variable is not -// set, check marks will be shown in green and x's in red. -func (e Event) String() string { - status := color.RedString("x") - if e.Action == ActionPass { - status = color.GreenString("✔") - } - return fmt.Sprintf(" %s %s (%.2fs)", status, e.Sentence, e.Elapsed) -} - -// IsTestResult determines whether or not the test event is one that we are -// interested in (namely, a pass or fail event on a test). Events on non-tests -// (for example, examples) are ignored, and all events on tests other than pass -// or fail events (for example, run or pause events) are also ignored. -func (e Event) IsTestResult() bool { - // Skip events on benchmarks, examples, and fuzz tests - if strings.HasPrefix(e.Test, "Benchmark") { - return false - } - if strings.HasPrefix(e.Test, "Example") { - return false - } - if strings.HasPrefix(e.Test, "Fuzz") { - return false - } - if e.Test == "" { - return false - } - if e.Action == ActionPass || e.Action == ActionFail { - return true - } - return false -} - -func (e Event) IsFuzzFail() bool { - if !strings.HasPrefix(e.Test, "Fuzz") { - return false - } - if e.Action != ActionFail { - return false - } - return true -} - -// IsPackageResult determines whether or not the test event is a package pass -// or fail event. That is, whether it indicates the passing or failing of a -// package as a whole, rather than some individual test within the package. -func (e Event) IsPackageResult() bool { - if e.Test != "" { - return false - } - if e.Action == ActionPass || e.Action == ActionFail { - return true - } - return false -} - -// IsOutput determines whether or not the event is a test output (for example -// from [testing.T.Error]), excluding status messages automatically generated -// by 'go test' such as "--- FAIL: ..." or "=== RUN / PAUSE / CONT". -func (e Event) IsOutput() bool { - if e.Action != "output" { - return false - } - if strings.HasPrefix(e.Output, "---") { - return false - } - if strings.HasPrefix(e.Output, "===") { - return false - } - return true -} diff --git a/gotestdox_test.go b/gotestdox_test.go index 0231ee0..92f9527 100644 --- a/gotestdox_test.go +++ b/gotestdox_test.go @@ -1,16 +1,10 @@ package gotestdox_test import ( - "fmt" - "io" - "log" "os" - "strings" "testing" "github.com/bitfield/gotestdox" - "github.com/fatih/color" - "github.com/google/go-cmp/cmp" "github.com/rogpeppe/go-internal/testscript" ) @@ -26,252 +20,3 @@ func TestGotestdoxProducesCorrectOutputWhen(t *testing.T) { Dir: "testdata/script", }) } - -func TestParseJSON_ReturnsValidDataForValidJSON(t *testing.T) { - t.Parallel() - input := `{"Time":"2022-02-28T15:53:43.532326Z","Action":"pass","Package":"github.com/bitfield/script","Test":"TestFindFilesInNonexistentPathReturnsError","Elapsed":0.12}` - want := gotestdox.Event{ - Action: "pass", - Package: "github.com/bitfield/script", - Test: "TestFindFilesInNonexistentPathReturnsError", - Elapsed: 0.12, - } - - got, err := gotestdox.ParseJSON(input) - if err != nil { - t.Fatal(err) - } - if !cmp.Equal(want, got) { - t.Error(cmp.Diff(want, got)) - } -} - -func TestParseJSON_ErrorsOnInvalidJSON(t *testing.T) { - t.Parallel() - input := `invalid` - _, err := gotestdox.ParseJSON(input) - if err == nil { - t.Error("want error") - } -} - -func TestEventString_FormatsPassAndFailEventsDifferently(t *testing.T) { - t.Parallel() - pass := gotestdox.Event{ - Action: "pass", - Test: "TestFooDoesX", - }.String() - fail := gotestdox.Event{ - Action: "fail", - Test: "TestFooDoesX", - }.String() - if pass == fail { - t.Errorf("both pass and fail events formatted as %q", pass) - } -} - -func TestIsFuzzFail_IsTrueForFuzzFailEvents(t *testing.T) { - t.Parallel() - event := gotestdox.Event{ - Action: "fail", - Test: "FuzzBar", - } - if !event.IsFuzzFail() { - t.Errorf("false for %q event on %q", event.Action, event.Test) - } -} - -func TestIsFuzzFail_IsFalseForNonFuzzFailEvents(t *testing.T) { - t.Parallel() - tcs := []gotestdox.Event{ - { - Action: "pass", - Test: "FuzzBar", - }, - { - Action: "fail", - Test: "TestFooDoesX", - }, - } - for _, event := range tcs { - if event.IsFuzzFail() { - t.Errorf("true for %q event on %q", event.Action, event.Test) - } - } -} - -func TestIsTestResult_IsTrueForTestPassOrFailEvents(t *testing.T) { - t.Parallel() - tcs := []gotestdox.Event{ - { - Action: "pass", - Test: "TestFooDoesX", - }, - { - Action: "fail", - Test: "TestFooDoesX", - }, - } - for _, event := range tcs { - if !event.IsTestResult() { - t.Errorf("false for %q event on %q", event.Action, event.Test) - } - } -} - -func TestIsTestResult_IsFalseForNonTestPassFailEvents(t *testing.T) { - t.Parallel() - tcs := []gotestdox.Event{ - { - Action: "pass", - Test: "ExampleFooDoesX", - }, - { - Action: "fail", - Test: "BenchmarkFooDoesX", - }, - { - Action: "pass", - Test: "", - }, - { - Action: "fail", - Test: "", - }, - { - Action: "pass", - Test: "FuzzBar", - }, - { - Action: "run", - Test: "TestFooDoesX", - }, - } - for _, event := range tcs { - if event.IsTestResult() { - t.Errorf("true for %q event on %q", event.Action, event.Test) - } - } -} - -func TestIsPackageResult_IsTrueForPackageResultEvents(t *testing.T) { - t.Parallel() - tcs := []gotestdox.Event{ - { - Action: "pass", - Test: "", - }, - { - Action: "fail", - Test: "", - }, - } - for _, event := range tcs { - if !event.IsPackageResult() { - t.Errorf("false for package result event %#v", event) - } - } -} - -func TestIsPackageResult_IsFalseForNonPackageResultEvents(t *testing.T) { - t.Parallel() - tcs := []gotestdox.Event{ - { - Action: "pass", - Test: "TestSomething", - }, - { - Action: "fail", - Test: "TestSomething", - }, - { - Action: "output", - Test: "", - }, - } - for _, event := range tcs { - if event.IsPackageResult() { - t.Errorf("true for non package result event %#v", event) - } - } -} - -func TestNewTestDoxer_ReturnsTestdoxerWithStandardIOStreams(t *testing.T) { - t.Parallel() - td := gotestdox.NewTestDoxer() - if td.Stdin != os.Stdin { - t.Error("want stdin os.Stdin") - } - if td.Stdout != os.Stdout { - t.Error("want stdout os.Stdout") - } - if td.Stderr != os.Stderr { - t.Error("want stderr os.Stderr") - } -} - -func TestExecGoTest_SetsOKToFalseWhenCommandErrors(t *testing.T) { - t.Parallel() - td := gotestdox.TestDoxer{ - Stdout: io.Discard, - Stderr: io.Discard, - } - td.ExecGoTest([]string{"bogus"}) - if td.OK { - t.Error("want not ok") - } -} - -func ExampleTestDoxer_Filter() { - input := `{"Action":"pass","Package":"demo","Test":"TestItWorks"} - {"Action":"pass","Package":"demo","Elapsed":0}` - td := gotestdox.NewTestDoxer() - td.Stdin = strings.NewReader(input) - color.NoColor = true - td.Filter() - // Output: - // demo: - // ✔ It works (0.00s) -} - -func ExampleEvent_String() { - event := gotestdox.Event{ - Action: "pass", - Sentence: "It works", - } - color.NoColor = true - fmt.Println(event.String()) - // Output: - // ✔ It works (0.00s) -} - -func ExampleEvent_IsTestResult_true() { - event := gotestdox.Event{ - Action: "pass", - Test: "TestItWorks", - } - fmt.Println(event.IsTestResult()) - // Output: - // true -} - -func ExampleEvent_IsTestResult_false() { - event := gotestdox.Event{ - Action: "fail", - Test: "ExampleEventsShouldBeIgnored", - } - fmt.Println(event.IsTestResult()) - // Output: - // false -} - -func ExampleParseJSON() { - input := `{"Action":"pass","Package":"demo","Test":"TestItWorks","Output":"","Elapsed":0.2}` - event, err := gotestdox.ParseJSON(input) - if err != nil { - log.Fatal(err) - } - fmt.Printf("%#v\n", event) - // Output: - // gotestdox.Event{Action:"pass", Package:"demo", Test:"TestItWorks", Sentence:"", Output:"", Elapsed:0.2} -} diff --git a/fuzz_test.go b/pkg/gotestdox/fuzz_test.go similarity index 92% rename from fuzz_test.go rename to pkg/gotestdox/fuzz_test.go index b954cd7..567b31a 100644 --- a/fuzz_test.go +++ b/pkg/gotestdox/fuzz_test.go @@ -7,7 +7,7 @@ import ( "testing" "unicode" - "github.com/bitfield/gotestdox" + "github.com/bitfield/gotestdox/pkg/gotestdox" ) func FuzzPrettify(f *testing.F) { diff --git a/pkg/gotestdox/gotestdox.go b/pkg/gotestdox/gotestdox.go new file mode 100644 index 0000000..27edb24 --- /dev/null +++ b/pkg/gotestdox/gotestdox.go @@ -0,0 +1,219 @@ +package gotestdox + +import ( + "bufio" + "encoding/json" + "fmt" + "io" + "os" + "os/exec" + "sort" + "strings" +) + +// TestDoxer holds the state and config associated with a particular invocation +// of 'go test'. +type TestDoxer struct { + Stdin io.Reader + Stdout, Stderr io.Writer + OK bool + Fail string + Pass string +} + +// NewTestDoxer returns a [*TestDoxer] configured with the default I/O streams: +// [os.Stdin], [os.Stdout], and [os.Stderr]. +func NewTestDoxer() *TestDoxer { + return &TestDoxer{ + Stdin: os.Stdin, + Stdout: os.Stdout, + Stderr: os.Stderr, + Fail: "x", + Pass: "✔", + } +} + +// ExecGoTest runs the 'go test -json' command, with any extra args supplied by +// the user, and consumes its output. Any errors are reported to td's Stderr +// stream, including the full command line that was run. If all tests passed, +// td.OK will be true. If there was a test failure, or 'go test' returned some +// error, then td.OK will be false. +func (td *TestDoxer) ExecGoTest(userArgs []string) { + args := []string{"test", "-json"} + args = append(args, userArgs...) + cmd := exec.Command("go", args...) + goTestOutput, err := cmd.StdoutPipe() + if err != nil { + fmt.Fprintln(td.Stderr, cmd.Args, err) + return + } + cmd.Stderr = td.Stderr + if err := cmd.Start(); err != nil { + fmt.Fprintln(td.Stderr, cmd.Args, err) + return + } + td.Stdin = goTestOutput + td.Filter() + if err := cmd.Wait(); err != nil { + td.OK = false + fmt.Fprintln(td.Stderr, cmd.Args, err) + return + } +} + +// Filter reads from td's Stdin stream, line by line, processing JSON records +// emitted by 'go test -json'. +// +// For each Go package it sees records about, it will print the full name of +// the package to td.Stdout, followed by a line giving the pass/fail status and +// the prettified name of each test, sorted alphabetically. +// +// If all tests passed, td.OK will be true at the end. If not, or if there was +// a parsing error, it will be false. Errors will be reported to td.Stderr. +func (td *TestDoxer) Filter() { + td.OK = true + results := map[string][]Event{} + outputs := map[string][]string{} + scanner := bufio.NewScanner(td.Stdin) + for scanner.Scan() { + event, err := td.ParseJSON(scanner.Text()) + if err != nil { + td.OK = false + fmt.Fprintln(td.Stderr, err) + return + } + switch { + case event.IsPackageResult(): + fmt.Fprintf(td.Stdout, "%s:\n", event.Package) + tests := results[event.Package] + sort.Slice(tests, func(i, j int) bool { + return tests[i].Sentence < tests[j].Sentence + }) + for _, r := range tests { + fmt.Fprintln(td.Stdout, r.String()) + if r.Action == ActionFail { + for _, line := range outputs[r.Test] { + fmt.Fprint(td.Stdout, line) + } + } + } + fmt.Fprintln(td.Stdout) + case event.IsOutput(): + outputs[event.Test] = append(outputs[event.Test], event.Output) + case event.IsTestResult(), event.IsFuzzFail(): + event.Sentence = Prettify(event.Test) + results[event.Package] = append(results[event.Package], event) + if event.Action == ActionFail { + td.OK = false + } + } + } +} + +// ParseJSON takes a string representing a single JSON test record as emitted +// by 'go test -json', and attempts to parse it into an [Event], returning any +// parsing error encountered. +func (td *TestDoxer) ParseJSON(line string) (Event, error) { + event := Event{} + err := json.Unmarshal([]byte(line), &event) + if err != nil { + return Event{}, fmt.Errorf("parsing JSON: %w\ninput: %s", err, line) + } + event.Status = td.Fail + if event.Action == ActionPass { + event.Status = td.Pass + } + return event, nil +} + +const ( + ActionPass = "pass" + ActionFail = "fail" +) + +// Event represents a Go test event as recorded by the 'go test -json' command. +// It does not attempt to unmarshal all the data, only those fields it needs to +// know about. It is based on the (unexported) 'event' struct used by Go's +// [cmd/internal/test2json] package. +type Event struct { + Action string + Package string + Test string + Sentence string + Output string + Elapsed float64 + + Status string `json:"omitempty"` +} + +// String formats a test Event for display. The prettified test name will be +// prefixed by a ✔ if the test passed, or an x if it failed. +// +// The sentence generated by [Prettify] from the name of the test will be +// shown, followed by the elapsed time in parentheses, to 2 decimal places. +func (e Event) String() string { + return fmt.Sprintf(" %s %s (%.2fs)", e.Status, e.Sentence, e.Elapsed) +} + +// IsTestResult determines whether or not the test event is one that we are +// interested in (namely, a pass or fail event on a test). Events on non-tests +// (for example, examples) are ignored, and all events on tests other than pass +// or fail events (for example, run or pause events) are also ignored. +func (e Event) IsTestResult() bool { + // Skip events on benchmarks, examples, and fuzz tests + if strings.HasPrefix(e.Test, "Benchmark") { + return false + } + if strings.HasPrefix(e.Test, "Example") { + return false + } + if strings.HasPrefix(e.Test, "Fuzz") { + return false + } + if e.Test == "" { + return false + } + if e.Action == ActionPass || e.Action == ActionFail { + return true + } + return false +} + +func (e Event) IsFuzzFail() bool { + if !strings.HasPrefix(e.Test, "Fuzz") { + return false + } + if e.Action != ActionFail { + return false + } + return true +} + +// IsPackageResult determines whether or not the test event is a package pass +// or fail event. That is, whether it indicates the passing or failing of a +// package as a whole, rather than some individual test within the package. +func (e Event) IsPackageResult() bool { + if e.Test != "" { + return false + } + if e.Action == ActionPass || e.Action == ActionFail { + return true + } + return false +} + +// IsOutput determines whether or not the event is a test output (for example +// from [testing.T.Error]), excluding status messages automatically generated +// by 'go test' such as "--- FAIL: ..." or "=== RUN / PAUSE / CONT". +func (e Event) IsOutput() bool { + if e.Action != "output" { + return false + } + if strings.HasPrefix(e.Output, "---") { + return false + } + if strings.HasPrefix(e.Output, "===") { + return false + } + return true +} diff --git a/pkg/gotestdox/gotestdox_test.go b/pkg/gotestdox/gotestdox_test.go new file mode 100644 index 0000000..5cc84af --- /dev/null +++ b/pkg/gotestdox/gotestdox_test.go @@ -0,0 +1,268 @@ +package gotestdox_test + +import ( + "fmt" + "io" + "log" + "os" + "strings" + "testing" + + "github.com/bitfield/gotestdox/pkg/gotestdox" + + "github.com/google/go-cmp/cmp" +) + +func TestParseJSON_ReturnsValidDataForValidJSON(t *testing.T) { + t.Parallel() + input := `{"Time":"2022-02-28T15:53:43.532326Z","Action":"pass","Package":"github.com/bitfield/script","Test":"TestFindFilesInNonexistentPathReturnsError","Elapsed":0.12}` + want := gotestdox.Event{ + Action: "pass", + Package: "github.com/bitfield/script", + Test: "TestFindFilesInNonexistentPathReturnsError", + Elapsed: 0.12, + Status: "✔", + } + + td := gotestdox.NewTestDoxer() + got, err := td.ParseJSON(input) + if err != nil { + t.Fatal(err) + } + if !cmp.Equal(want, got) { + t.Error(cmp.Diff(want, got)) + } +} + +func TestParseJSON_ErrorsOnInvalidJSON(t *testing.T) { + t.Parallel() + input := `invalid` + td := gotestdox.NewTestDoxer() + _, err := td.ParseJSON(input) + if err == nil { + t.Error("want error") + } +} + +func TestEventString_FormatsPassAndFailEventsDifferently(t *testing.T) { + t.Parallel() + pass := gotestdox.Event{ + Action: "pass", + Status: "✔", + Test: "TestFooDoesX", + }.String() + fail := gotestdox.Event{ + Action: "fail", + Status: "x", + Test: "TestFooDoesX", + }.String() + if pass == fail { + t.Errorf("both pass and fail events formatted as %q", pass) + } +} + +func TestIsFuzzFail_IsTrueForFuzzFailEvents(t *testing.T) { + t.Parallel() + event := gotestdox.Event{ + Action: "fail", + Test: "FuzzBar", + } + if !event.IsFuzzFail() { + t.Errorf("false for %q event on %q", event.Action, event.Test) + } +} + +func TestIsFuzzFail_IsFalseForNonFuzzFailEvents(t *testing.T) { + t.Parallel() + tcs := []gotestdox.Event{ + { + Action: "pass", + Test: "FuzzBar", + }, + { + Action: "fail", + Test: "TestFooDoesX", + }, + } + for _, event := range tcs { + if event.IsFuzzFail() { + t.Errorf("true for %q event on %q", event.Action, event.Test) + } + } +} + +func TestIsTestResult_IsTrueForTestPassOrFailEvents(t *testing.T) { + t.Parallel() + tcs := []gotestdox.Event{ + { + Action: "pass", + Test: "TestFooDoesX", + }, + { + Action: "fail", + Test: "TestFooDoesX", + }, + } + for _, event := range tcs { + if !event.IsTestResult() { + t.Errorf("false for %q event on %q", event.Action, event.Test) + } + } +} + +func TestIsTestResult_IsFalseForNonTestPassFailEvents(t *testing.T) { + t.Parallel() + tcs := []gotestdox.Event{ + { + Action: "pass", + Test: "ExampleFooDoesX", + }, + { + Action: "fail", + Test: "BenchmarkFooDoesX", + }, + { + Action: "pass", + Test: "", + }, + { + Action: "fail", + Test: "", + }, + { + Action: "pass", + Test: "FuzzBar", + }, + { + Action: "run", + Test: "TestFooDoesX", + }, + } + for _, event := range tcs { + if event.IsTestResult() { + t.Errorf("true for %q event on %q", event.Action, event.Test) + } + } +} + +func TestIsPackageResult_IsTrueForPackageResultEvents(t *testing.T) { + t.Parallel() + tcs := []gotestdox.Event{ + { + Action: "pass", + Test: "", + }, + { + Action: "fail", + Test: "", + }, + } + for _, event := range tcs { + if !event.IsPackageResult() { + t.Errorf("false for package result event %#v", event) + } + } +} + +func TestIsPackageResult_IsFalseForNonPackageResultEvents(t *testing.T) { + t.Parallel() + tcs := []gotestdox.Event{ + { + Action: "pass", + Test: "TestSomething", + }, + { + Action: "fail", + Test: "TestSomething", + }, + { + Action: "output", + Test: "", + }, + } + for _, event := range tcs { + if event.IsPackageResult() { + t.Errorf("true for non package result event %#v", event) + } + } +} + +func TestNewTestDoxer_ReturnsTestdoxerWithStandardIOStreams(t *testing.T) { + t.Parallel() + td := gotestdox.NewTestDoxer() + if td.Stdin != os.Stdin { + t.Error("want stdin os.Stdin") + } + if td.Stdout != os.Stdout { + t.Error("want stdout os.Stdout") + } + if td.Stderr != os.Stderr { + t.Error("want stderr os.Stderr") + } +} + +func TestExecGoTest_SetsOKToFalseWhenCommandErrors(t *testing.T) { + t.Parallel() + td := gotestdox.TestDoxer{ + Stdout: io.Discard, + Stderr: io.Discard, + } + td.ExecGoTest([]string{"bogus"}) + if td.OK { + t.Error("want not ok") + } +} + +func ExampleTestDoxer_Filter() { + input := `{"Action":"pass","Package":"demo","Test":"TestItWorks"} + {"Action":"pass","Package":"demo","Elapsed":0}` + td := gotestdox.NewTestDoxer() + td.Stdin = strings.NewReader(input) + td.Filter() + // Output: + // demo: + // ✔ It works (0.00s) +} + +func ExampleEvent_String() { + event := gotestdox.Event{ + Action: "pass", + Status: "✔", + Sentence: "It works", + } + fmt.Println(event.String()) + // Output: + // ✔ It works (0.00s) +} + +func ExampleEvent_IsTestResult_true() { + event := gotestdox.Event{ + Action: "pass", + Test: "TestItWorks", + } + fmt.Println(event.IsTestResult()) + // Output: + // true +} + +func ExampleEvent_IsTestResult_false() { + event := gotestdox.Event{ + Action: "fail", + Test: "ExampleEventsShouldBeIgnored", + } + fmt.Println(event.IsTestResult()) + // Output: + // false +} + +func ExampleTestDoxer_ParseJSON() { + input := `{"Action":"pass","Package":"demo","Test":"TestItWorks","Output":"","Elapsed":0.2}` + td := gotestdox.NewTestDoxer() + event, err := td.ParseJSON(input) + if err != nil { + log.Fatal(err) + } + fmt.Printf("%#v\n", event) + // Output: + // gotestdox.Event{Action:"pass", Package:"demo", Test:"TestItWorks", Sentence:"", Output:"", Elapsed:0.2, Status:"✔"} +} diff --git a/prettifier.go b/pkg/gotestdox/prettifier.go similarity index 100% rename from prettifier.go rename to pkg/gotestdox/prettifier.go diff --git a/prettifier_test.go b/pkg/gotestdox/prettifier_test.go similarity index 99% rename from prettifier_test.go rename to pkg/gotestdox/prettifier_test.go index 305988e..c6dad16 100644 --- a/prettifier_test.go +++ b/pkg/gotestdox/prettifier_test.go @@ -4,7 +4,7 @@ import ( "fmt" "testing" - "github.com/bitfield/gotestdox" + "github.com/bitfield/gotestdox/pkg/gotestdox" "github.com/google/go-cmp/cmp" ) diff --git a/testdata/fuzz/FuzzPrettify/02d4bc9f4e567048ebe719db6d94d22ed0788a56e3bdf4aa1fa3a546b5a681a4 b/pkg/gotestdox/testdata/fuzz/FuzzPrettify/02d4bc9f4e567048ebe719db6d94d22ed0788a56e3bdf4aa1fa3a546b5a681a4 similarity index 100% rename from testdata/fuzz/FuzzPrettify/02d4bc9f4e567048ebe719db6d94d22ed0788a56e3bdf4aa1fa3a546b5a681a4 rename to pkg/gotestdox/testdata/fuzz/FuzzPrettify/02d4bc9f4e567048ebe719db6d94d22ed0788a56e3bdf4aa1fa3a546b5a681a4 diff --git a/testdata/fuzz/FuzzPrettify/05bda4c43f0606152eb8661c3f07af35c23ea151e4c0eba13616e681baa0b76b b/pkg/gotestdox/testdata/fuzz/FuzzPrettify/05bda4c43f0606152eb8661c3f07af35c23ea151e4c0eba13616e681baa0b76b similarity index 100% rename from testdata/fuzz/FuzzPrettify/05bda4c43f0606152eb8661c3f07af35c23ea151e4c0eba13616e681baa0b76b rename to pkg/gotestdox/testdata/fuzz/FuzzPrettify/05bda4c43f0606152eb8661c3f07af35c23ea151e4c0eba13616e681baa0b76b diff --git a/testdata/fuzz/FuzzPrettify/1d9c428aec8af9b4c3d6f8f87bb3fd4cd88776a6518bf232ca8ece05de180c4f b/pkg/gotestdox/testdata/fuzz/FuzzPrettify/1d9c428aec8af9b4c3d6f8f87bb3fd4cd88776a6518bf232ca8ece05de180c4f similarity index 100% rename from testdata/fuzz/FuzzPrettify/1d9c428aec8af9b4c3d6f8f87bb3fd4cd88776a6518bf232ca8ece05de180c4f rename to pkg/gotestdox/testdata/fuzz/FuzzPrettify/1d9c428aec8af9b4c3d6f8f87bb3fd4cd88776a6518bf232ca8ece05de180c4f diff --git a/testdata/fuzz/FuzzPrettify/2759ad33bc85f335199a678f5b2afd245760bc670e03abcfedd22440262e7f20 b/pkg/gotestdox/testdata/fuzz/FuzzPrettify/2759ad33bc85f335199a678f5b2afd245760bc670e03abcfedd22440262e7f20 similarity index 100% rename from testdata/fuzz/FuzzPrettify/2759ad33bc85f335199a678f5b2afd245760bc670e03abcfedd22440262e7f20 rename to pkg/gotestdox/testdata/fuzz/FuzzPrettify/2759ad33bc85f335199a678f5b2afd245760bc670e03abcfedd22440262e7f20 diff --git a/testdata/fuzz/FuzzPrettify/383824675cc2c177e198f74ede3c8ca29be00e2b6797a034a7a5839a4cd9af1c b/pkg/gotestdox/testdata/fuzz/FuzzPrettify/383824675cc2c177e198f74ede3c8ca29be00e2b6797a034a7a5839a4cd9af1c similarity index 100% rename from testdata/fuzz/FuzzPrettify/383824675cc2c177e198f74ede3c8ca29be00e2b6797a034a7a5839a4cd9af1c rename to pkg/gotestdox/testdata/fuzz/FuzzPrettify/383824675cc2c177e198f74ede3c8ca29be00e2b6797a034a7a5839a4cd9af1c diff --git a/testdata/fuzz/FuzzPrettify/5838cdfae7b16cde2707c04599b62223e7bade8dafdd8a1c53b8f881e8f79d99 b/pkg/gotestdox/testdata/fuzz/FuzzPrettify/5838cdfae7b16cde2707c04599b62223e7bade8dafdd8a1c53b8f881e8f79d99 similarity index 100% rename from testdata/fuzz/FuzzPrettify/5838cdfae7b16cde2707c04599b62223e7bade8dafdd8a1c53b8f881e8f79d99 rename to pkg/gotestdox/testdata/fuzz/FuzzPrettify/5838cdfae7b16cde2707c04599b62223e7bade8dafdd8a1c53b8f881e8f79d99 diff --git a/testdata/fuzz/FuzzPrettify/68ed403ba2cdc61b143313312fd67edbd7e8bc6481570c7d1d7b6901793166ce b/pkg/gotestdox/testdata/fuzz/FuzzPrettify/68ed403ba2cdc61b143313312fd67edbd7e8bc6481570c7d1d7b6901793166ce similarity index 100% rename from testdata/fuzz/FuzzPrettify/68ed403ba2cdc61b143313312fd67edbd7e8bc6481570c7d1d7b6901793166ce rename to pkg/gotestdox/testdata/fuzz/FuzzPrettify/68ed403ba2cdc61b143313312fd67edbd7e8bc6481570c7d1d7b6901793166ce diff --git a/testdata/fuzz/FuzzPrettify/7287749262c764198e0bfd313015202b1503cb011ed58da513853e0fb2e68601 b/pkg/gotestdox/testdata/fuzz/FuzzPrettify/7287749262c764198e0bfd313015202b1503cb011ed58da513853e0fb2e68601 similarity index 100% rename from testdata/fuzz/FuzzPrettify/7287749262c764198e0bfd313015202b1503cb011ed58da513853e0fb2e68601 rename to pkg/gotestdox/testdata/fuzz/FuzzPrettify/7287749262c764198e0bfd313015202b1503cb011ed58da513853e0fb2e68601 diff --git a/testdata/fuzz/FuzzPrettify/931b61b895a37b9f6d196cc73123abb83cdd7bc05e469ad7d4b76bd1ff16db60 b/pkg/gotestdox/testdata/fuzz/FuzzPrettify/931b61b895a37b9f6d196cc73123abb83cdd7bc05e469ad7d4b76bd1ff16db60 similarity index 100% rename from testdata/fuzz/FuzzPrettify/931b61b895a37b9f6d196cc73123abb83cdd7bc05e469ad7d4b76bd1ff16db60 rename to pkg/gotestdox/testdata/fuzz/FuzzPrettify/931b61b895a37b9f6d196cc73123abb83cdd7bc05e469ad7d4b76bd1ff16db60 diff --git a/testdata/fuzz/FuzzPrettify/9eca0c15d9a07d7821d43cbb4e08d4b84ca610d679b1ee89a0f0a9cdf46f5b79 b/pkg/gotestdox/testdata/fuzz/FuzzPrettify/9eca0c15d9a07d7821d43cbb4e08d4b84ca610d679b1ee89a0f0a9cdf46f5b79 similarity index 100% rename from testdata/fuzz/FuzzPrettify/9eca0c15d9a07d7821d43cbb4e08d4b84ca610d679b1ee89a0f0a9cdf46f5b79 rename to pkg/gotestdox/testdata/fuzz/FuzzPrettify/9eca0c15d9a07d7821d43cbb4e08d4b84ca610d679b1ee89a0f0a9cdf46f5b79 diff --git a/testdata/fuzz/FuzzPrettify/aa12d4e4166bf7ad00d59c3ec5e9ef473494bd2fde7d93b48136abbc77a5acef b/pkg/gotestdox/testdata/fuzz/FuzzPrettify/aa12d4e4166bf7ad00d59c3ec5e9ef473494bd2fde7d93b48136abbc77a5acef similarity index 100% rename from testdata/fuzz/FuzzPrettify/aa12d4e4166bf7ad00d59c3ec5e9ef473494bd2fde7d93b48136abbc77a5acef rename to pkg/gotestdox/testdata/fuzz/FuzzPrettify/aa12d4e4166bf7ad00d59c3ec5e9ef473494bd2fde7d93b48136abbc77a5acef diff --git a/testdata/fuzz/FuzzPrettify/d72586fff5c01205 b/pkg/gotestdox/testdata/fuzz/FuzzPrettify/d72586fff5c01205 similarity index 100% rename from testdata/fuzz/FuzzPrettify/d72586fff5c01205 rename to pkg/gotestdox/testdata/fuzz/FuzzPrettify/d72586fff5c01205 diff --git a/testdata/fuzz/FuzzPrettify/f5d26b6486179756258edcb8fde23e655aeae6f7ce1d0d22380609e5055cd6de b/pkg/gotestdox/testdata/fuzz/FuzzPrettify/f5d26b6486179756258edcb8fde23e655aeae6f7ce1d0d22380609e5055cd6de similarity index 100% rename from testdata/fuzz/FuzzPrettify/f5d26b6486179756258edcb8fde23e655aeae6f7ce1d0d22380609e5055cd6de rename to pkg/gotestdox/testdata/fuzz/FuzzPrettify/f5d26b6486179756258edcb8fde23e655aeae6f7ce1d0d22380609e5055cd6de