Skip to content

Commit

Permalink
new: improve colored errors
Browse files Browse the repository at this point in the history
  • Loading branch information
lansfy committed Dec 29, 2024
1 parent 5b125b1 commit 1ed2d91
Show file tree
Hide file tree
Showing 10 changed files with 175 additions and 92 deletions.
9 changes: 2 additions & 7 deletions checker/response_body/response_body.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,10 @@ import (
"strings"

"github.com/lansfy/gonkex/checker"
"github.com/lansfy/gonkex/colorize"
"github.com/lansfy/gonkex/compare"
"github.com/lansfy/gonkex/models"
"github.com/lansfy/gonkex/xmlparsing"

"github.com/fatih/color"
)

type ResponseBodyChecker struct{}
Expand All @@ -25,11 +24,7 @@ func createWrongStatusError(statusCode int, known map[int]string) error {
for code := range known {
knownCodes = append(knownCodes, fmt.Sprintf("%d", code))
}
return fmt.Errorf(
"server responded with unexpected status:\n expected: %s\n actual: %s",
color.GreenString("%s", strings.Join(knownCodes, "/")),
color.RedString("%d", statusCode),
)
return colorize.NewNotEqualError("server responded with unexpected status:", "", "", strings.Join(knownCodes, " / "), statusCode)
}

func (c *ResponseBodyChecker) Check(t models.TestInterface, result *models.Result) ([]error, error) {
Expand Down
25 changes: 13 additions & 12 deletions checker/response_db/response_db.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ import (
"fmt"

"github.com/lansfy/gonkex/checker"
"github.com/lansfy/gonkex/colorize"
"github.com/lansfy/gonkex/compare"
"github.com/lansfy/gonkex/models"
"github.com/lansfy/gonkex/storage"

"github.com/fatih/color"
"github.com/kylelemons/godebug/pretty"
)

Expand Down Expand Up @@ -112,19 +112,20 @@ func toJSONArray(items []string, qual, testName string) ([]interface{}, error) {
}

func compareDbResponseLength(expected, actual []string, query interface{}) error {
var err error

if len(expected) != len(actual) {
err = fmt.Errorf(
"quantity of items in database do not match (-expected: %s +actual: %s)\n test query:\n%s\n result diff:\n%s",
color.CyanString("%v", len(expected)),
color.CyanString("%v", len(actual)),
color.CyanString("%v", query),
color.CyanString("%v", pretty.Compare(expected, actual)),
)
if len(expected) == len(actual) {
return nil
}

return err
return colorize.NewError(
colorize.None("quantity of items in database do not match (-expected: "),
colorize.Cyan(len(expected)),
colorize.None(" +actual: "),
colorize.Cyan(len(actual)),
colorize.None(")\n test query:\n"),
colorize.Cyan(query),
colorize.None("\n result diff:\n"),
colorize.Cyan(pretty.Compare(expected, actual)),
)
}

func newQuery(dbQuery string, db storage.StorageInterface) ([]string, error) {
Expand Down
28 changes: 13 additions & 15 deletions checker/response_header/response_header.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
package response_header

import (
"fmt"
"net/textproto"

"github.com/lansfy/gonkex/checker"
"github.com/lansfy/gonkex/colorize"
"github.com/lansfy/gonkex/models"

"github.com/fatih/color"
)

type ResponseHeaderChecker struct{}
Expand All @@ -28,9 +26,9 @@ func (c *ResponseHeaderChecker) Check(t models.TestInterface, result *models.Res
k = textproto.CanonicalMIMEHeaderKey(k)
actualValues, ok := result.ResponseHeaders[k]
if !ok {
errs = append(errs, fmt.Errorf(
"response does not include expected header %s",
color.CyanString(k),
errs = append(errs, colorize.NewError(
colorize.None("response does not include expected header "),
colorize.Cyan(k),
))
continue
}
Expand All @@ -45,17 +43,17 @@ func (c *ResponseHeaderChecker) Check(t models.TestInterface, result *models.Res
continue
}
if len(actualValues) == 1 {
errs = append(errs, fmt.Errorf(
"response header %s value does not match:\n expected: %s\n actual: %s",
color.CyanString(k),
color.GreenString("%s", v),
color.RedString("%v", actualValues[0]),
errs = append(errs, colorize.NewNotEqualError(
"response header ", k, " value does not match:",
v,
actualValues[0],
))
} else {
errs = append(errs, fmt.Errorf(
"response header %s value does not match expected %s",
color.CyanString(k),
color.GreenString("%s", v),
errs = append(errs, colorize.NewError(
colorize.None("response header "),
colorize.Cyan(k),
colorize.None(" value does not match expected "),
colorize.Green(v),
))
}
}
Expand Down
30 changes: 13 additions & 17 deletions checker/response_header/response_header_test.go
Original file line number Diff line number Diff line change
@@ -1,19 +1,15 @@
package response_header

import (
"errors"
"sort"
"testing"

"github.com/lansfy/gonkex/models"
"github.com/lansfy/gonkex/testloader/yaml_file"

"github.com/fatih/color"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestCheckShouldMatchSubset(t *testing.T) {
color.NoColor = true
test := &yaml_file.Test{
ResponseHeaders: map[int]map[string]string{
200: {
Expand All @@ -40,12 +36,11 @@ func TestCheckShouldMatchSubset(t *testing.T) {
checker := NewChecker()
errs, err := checker.Check(test, result)

assert.NoError(t, err, "Check must not result with an error")
assert.Empty(t, errs, "Check must succeed")
require.NoError(t, err, "Check must not result with an error")
require.Empty(t, errs, "Check must succeed")
}

func TestCheckWhenNotMatchedShouldReturnError(t *testing.T) {
color.NoColor = true
test := &yaml_file.Test{
ResponseHeaders: map[int]map[string]string{
200: {
Expand All @@ -67,18 +62,19 @@ func TestCheckWhenNotMatchedShouldReturnError(t *testing.T) {

checker := NewChecker()
errs, err := checker.Check(test, result)
require.NoError(t, err, "Check must not result with an error")

sort.Slice(errs, func(i, j int) bool {
return errs[i].Error() < errs[j].Error()
})
errText := []string{}
for _, err = range errs {
errText = append(errText, err.Error())
}

assert.NoError(t, err, "Check must not result with an error")
assert.Equal(
require.ElementsMatch(
t,
errs,
[]error{
errors.New("response does not include expected header Content-Type"),
errors.New("response header Accept value does not match:\n expected: text/html\n actual: application/json"),
errText,
[]string{
"response does not include expected header Content-Type",
"response header Accept value does not match:\n expected: text/html\n actual: application/json",
},
)
}
83 changes: 83 additions & 0 deletions colorize/error.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package colorize

import (
"fmt"
"strings"

"github.com/fatih/color"
)

type Color int

const (
ColorRed Color = iota
ColorCyan
ColorGreen
ColorNone
)

type Part struct {
attr Color
value string
}

func Red(v interface{}) *Part {
return &Part{ColorRed, fmt.Sprintf("%v", v)}
}

func Cyan(v interface{}) *Part {
return &Part{ColorCyan, fmt.Sprintf("%v", v)}
}

func Green(v interface{}) *Part {
return &Part{ColorGreen, fmt.Sprintf("%v", v)}
}

func None(v interface{}) *Part {
return &Part{ColorNone, fmt.Sprintf("%v", v)}
}

type Error struct {
parts []*Part
}

func (e *Error) Error() string {
buf := strings.Builder{}
for _, p := range e.parts {
buf.WriteString(p.value)
}
return buf.String()
}

func asIsString(format string, a ...interface{}) string {
return fmt.Sprintf(format, a...)
}

func (e *Error) ColorError() string {
buf := strings.Builder{}
for _, p := range e.parts {
if p.value == "" {
continue
}
f := asIsString
switch p.attr {
case ColorRed:
f = color.RedString
case ColorCyan:
f = color.CyanString
case ColorGreen:
f = color.GreenString
}

buf.WriteString(f(p.value))
}
return buf.String()
}

func NewError(parts ...*Part) error {
return &Error{parts}
}

func NewNotEqualError(before, entity, after string, expected, actual interface{}) error {
return NewError(None(before), Cyan(entity), None(after+"\n expected: "), Green(expected), None("\n actual: "), Red(actual))
}
39 changes: 17 additions & 22 deletions compare/compare.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import (
"regexp"
"strings"

"github.com/fatih/color"
"github.com/lansfy/gonkex/colorize"

"github.com/kylelemons/godebug/diff"
)

Expand Down Expand Up @@ -218,22 +219,22 @@ func leafMatchType(expected interface{}) leafsMatchType {
return pure
}

func diffStrings(a, b string) string {
func diffStrings(a, b string) []*colorize.Part {
chunks := diff.DiffChunks(strings.Split(a, "\n"), strings.Split(b, "\n"))

buf := strings.Builder{}
parts := []*colorize.Part{}
for _, c := range chunks {
for _, line := range c.Added {
buf.WriteString(color.RedString("+%s\n", line))
parts = append(parts, colorize.Red(fmt.Sprintf("+%s\n", line)))
}
for _, line := range c.Deleted {
buf.WriteString(color.RedString("-%s\n", line))
parts = append(parts, colorize.Red(fmt.Sprintf("-%s\n", line)))
}
for _, line := range c.Equal {
buf.WriteString(color.GreenString(" %s\n", line))
parts = append(parts, colorize.Green(fmt.Sprintf(" %s\n", line)))
}
}
return strings.TrimRight(buf.String(), "\n")
return parts
}

func makeValueCompareError(path, msg string, expected, actual interface{}) error {
Expand All @@ -243,24 +244,18 @@ func makeValueCompareError(path, msg string, expected, actual interface{}) error
return makeError(path, msg, expected, actual)
}

// special case for strings
changes := diffStrings(actualStr, expectedStr)
return fmt.Errorf(
"at path %s %s:\n diff (--- expected vs +++ actual):\n%s",
color.CyanString(path),
msg,
changes,
)
// special case for multi-line strings
parts := []*colorize.Part{
colorize.None("at path "),
colorize.Cyan(path),
colorize.None(" " + msg + ":\n diff (--- expected vs +++ actual):\n"),
}
parts = append(parts, diffStrings(actualStr, expectedStr)...)
return colorize.NewError(parts...)
}

func makeError(path, msg string, expected, actual interface{}) error {
return fmt.Errorf(
"at path %s %s:\n expected: %s\n actual: %s",
color.CyanString(path),
msg,
color.GreenString("%v", expected),
color.RedString("%v", actual),
)
return colorize.NewNotEqualError("at path ", path, " "+msg+":", expected, actual)
}

func convertToArray(array interface{}) []interface{} {
Expand Down
9 changes: 4 additions & 5 deletions compare/compare_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,16 @@ import (
"fmt"
"testing"

"github.com/fatih/color"
"github.com/stretchr/testify/assert"
)

func makeErrorString(path, msg string, expected, actual interface{}) string {
return fmt.Sprintf(
"at path %s %s:\n expected: %s\n actual: %s",
color.CyanString(path),
"at path %s %s:\n expected: %v\n actual: %v",
path,
msg,
color.GreenString("%v", expected),
color.RedString("%v", actual),
expected,
actual,
)
}

Expand Down
2 changes: 1 addition & 1 deletion output/terminal/template.txt
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ Response:

Errors:
{{ range $i, $e := .Errors }}
{{ inc $i }}) {{ $e.Error }}
{{ inc $i }}) {{ printError $e }}
{{ end }}
{{ else }}
Result: {{ success "OK" }}
Expand Down
Loading

0 comments on commit 1ed2d91

Please sign in to comment.