1
1
mirror of https://github.com/wader/fq.git synced 2024-12-23 13:22:58 +03:00

test: Refactor out diff testing into own package

This commit is contained in:
Mattias Wadman 2021-10-10 16:32:39 +02:00
parent 4cee498e06
commit f0233bb811
6 changed files with 220 additions and 186 deletions

View File

@ -1,45 +0,0 @@
package deepequal
import (
"fmt"
"reflect"
"github.com/pmezard/go-difflib/difflib"
)
type tf interface {
Errorf(format string, args ...interface{})
Fatalf(format string, args ...interface{})
}
func testDeepEqual(fn func(format string, args ...interface{}), name string, expected interface{}, actual interface{}) {
expectedStr := fmt.Sprintf("%v", expected)
actualStr := fmt.Sprintf("%v", actual)
if !reflect.DeepEqual(expected, actual) {
diff := difflib.UnifiedDiff{
A: difflib.SplitLines(expectedStr),
B: difflib.SplitLines(actualStr),
FromFile: "expected",
ToFile: "actual",
Context: 3,
}
uDiff, err := difflib.GetUnifiedDiffString(diff)
if err != nil {
panic(err)
}
fn(`
%s diff:
%s
`,
name, uDiff)
}
}
func Error(t tf, name string, expected interface{}, actual interface{}) {
testDeepEqual(t.Errorf, name, expected, actual)
}
func Fatal(t tf, name string, expected interface{}, actual interface{}) {
testDeepEqual(t.Fatalf, name, expected, actual)
}

View File

@ -1,40 +0,0 @@
package deepequal_test
import (
"fmt"
"testing"
"github.com/wader/fq/internal/deepequal"
)
type tfFn func(format string, args ...interface{})
func (fn tfFn) Errorf(format string, args ...interface{}) {
fn(format, args...)
}
func (fn tfFn) Fatalf(format string, args ...interface{}) {
fn(format, args...)
}
func TestError(t *testing.T) {
deepequal.Error(
tfFn(func(format string, args ...interface{}) {
expected := `
name diff:
--- expected
+++ actual
@@ -1 +1 @@
-aaaaaaaaa
+aaaaaabba
`
actual := fmt.Sprintf(format, args...)
if expected != actual {
t.Errorf("expected %q, got %q", expected, actual)
}
}),
"name",
"aaaaaaaaa", "aaaaaabba",
)
}

View File

@ -0,0 +1,172 @@
package difftest
import (
"fmt"
"io/ioutil"
"log"
"os"
"path/filepath"
"reflect"
"strings"
"testing"
"github.com/pmezard/go-difflib/difflib"
)
type tf interface {
Helper()
Errorf(format string, args ...interface{})
Fatalf(format string, args ...interface{})
}
const green = "\x1b[32m"
const red = "\x1b[31m"
const reset = "\x1b[0m"
type Fn func(t *testing.T, path string, input string) (string, string, error)
type Options struct {
Path string
Pattern string
ColorDiff bool
WriteOutput bool
Fn Fn
}
func testDeepEqual(t tf, color bool, fn func(format string, args ...interface{}), expected interface{}, actual interface{}) {
t.Helper()
expectedStr := fmt.Sprintf("%v", expected)
actualStr := fmt.Sprintf("%v", actual)
if !reflect.DeepEqual(expected, actual) {
diff := difflib.UnifiedDiff{
A: difflib.SplitLines(expectedStr),
B: difflib.SplitLines(actualStr),
FromFile: "expected",
ToFile: "actual",
Context: 3,
}
uDiff, err := difflib.GetUnifiedDiffString(diff)
if color {
lines := strings.Split(uDiff, "\n")
var coloredLines []string
seenAt := false
for _, l := range lines {
if len(l) == 0 {
continue
}
switch {
case seenAt && l[0] == '+':
coloredLines = append(coloredLines, green+l+reset)
case seenAt && l[0] == '-':
coloredLines = append(coloredLines, red+l+reset)
default:
if l[0] == '@' {
seenAt = true
}
coloredLines = append(coloredLines, l)
}
}
uDiff = strings.Join(coloredLines, "\n")
}
if err != nil {
panic(err)
}
fn("\n" + uDiff)
}
}
func ErrorEx(t tf, color bool, expected interface{}, actual interface{}) {
t.Helper()
testDeepEqual(t, color, t.Errorf, expected, actual)
}
func Error(t tf, expected interface{}, actual interface{}) {
t.Helper()
testDeepEqual(t, false, t.Errorf, expected, actual)
}
func FatalEx(t tf, color bool, expected interface{}, actual interface{}) {
t.Helper()
testDeepEqual(t, color, t.Fatalf, expected, actual)
}
func Fatal(t tf, expected interface{}, actual interface{}) {
t.Helper()
testDeepEqual(t, false, t.Fatalf, expected, actual)
}
func TestWithOptions(t *testing.T, opts Options) {
t.Helper()
func() {
t.Helper()
// done i two steps as it seems hard to mark some functions inside filepath.Walk as test helpers
var paths []string
if err := filepath.Walk(opts.Path, func(path string, info os.FileInfo, err error) error {
t.Helper()
if err != nil {
return err
}
match, err := filepath.Match(filepath.Join(filepath.Dir(path), opts.Pattern), path)
if err != nil {
return err
} else if !match {
return nil
}
paths = append(paths, path)
return nil
}); err != nil {
t.Fatal(err)
}
for _, p := range paths {
t.Run(p, func(t *testing.T) {
t.Helper()
input, err := ioutil.ReadFile(p)
if err != nil {
t.Fatal(err)
}
outputPath, output, err := opts.Fn(t, p, string(input))
if err != nil {
t.Fatal(err)
}
expectedOutput, expectedOutputErr := ioutil.ReadFile(outputPath)
if opts.WriteOutput {
if expectedOutputErr == nil && string(expectedOutput) == output {
log.Println("skip")
return
}
if err := ioutil.WriteFile(outputPath, []byte(output), 0644); err != nil { //nolint:gosec
t.Fatal(err)
}
return
}
if expectedOutputErr != nil {
t.Fatal(expectedOutputErr)
}
ErrorEx(t, opts.ColorDiff, string(expectedOutput), output)
})
}
}()
}
func Test(t *testing.T, pattern string, fn Fn) {
t.Helper()
TestWithOptions(t, Options{
Path: "testdata",
Pattern: pattern,
Fn: fn,
})
}

View File

@ -62,6 +62,7 @@ type CaseRun struct {
Readlines []CaseReadline
ReadlinesPos int
ReadlineEnv []string
WasRun bool
}
func (cr *CaseRun) Line() int { return cr.LineNr }
@ -236,24 +237,42 @@ func (c *Case) ToActual() string {
fmt.Fprintf(sb, "#%s\n", p.comment)
case *CaseRun:
fmt.Fprintf(sb, "$%s\n", p.Command)
s := p.ActualStdoutBuf.String()
var s string
if p.WasRun {
s = p.ActualStdoutBuf.String()
} else {
s = p.ToExpectedStdout()
}
if s != "" {
fmt.Fprint(sb, s)
if !strings.HasSuffix(s, "\n") {
fmt.Fprint(sb, "\\\n")
}
}
if p.WasRun {
if p.ActualExitCode != 0 {
fmt.Fprintf(sb, "exitcode: %d\n", p.ActualExitCode)
}
} else {
if p.ExpectedExitCode != 0 {
fmt.Fprintf(sb, "exitcode: %d\n", p.ExpectedExitCode)
}
}
if p.StdinInitial != "" {
fmt.Fprint(sb, "stdin:\n")
fmt.Fprint(sb, p.StdinInitial)
}
if p.WasRun {
if p.ActualStderrBuf.Len() > 0 {
fmt.Fprint(sb, "stderr:\n")
fmt.Fprint(sb, p.ActualStderrBuf.String())
}
} else {
if p.ExpectedStderr != "" {
fmt.Fprint(sb, "stderr:\n")
fmt.Fprint(sb, p.ExpectedStderr)
}
}
case *caseFile:
fmt.Fprintf(sb, "%s:\n", p.name)
sb.Write(p.data)

View File

@ -1,40 +0,0 @@
package script_test
import (
"log"
"regexp"
"testing"
"github.com/wader/fq/internal/deepequal"
"github.com/wader/fq/internal/script"
)
func TestSectionParser(t *testing.T) {
actualSections := script.SectionParser(
regexp.MustCompile(`^(?:(a:)|(b:))$`),
`
a:
c
c
b:
a:
c
a:
`[1:])
expectedSections := []script.Section{
{LineNr: 1, Name: "a:", Value: "c\nc\n"},
{LineNr: 4, Name: "b:", Value: ""},
{LineNr: 5, Name: "a:", Value: "c\n"},
{LineNr: 7, Name: "a:", Value: ""},
}
deepequal.Error(t, "sections", expectedSections, actualSections)
}
func TestUnescape(t *testing.T) {
s := script.Unescape(`asd\n\r\t \0b11110000 asd \0xffcb sdfd `)
log.Printf("s: %v\n", []byte(s))
}

View File

@ -4,19 +4,38 @@ import (
"context"
"io/ioutil"
"os"
"path/filepath"
"strconv"
"testing"
"github.com/wader/fq/format/registry"
"github.com/wader/fq/internal/deepequal"
"github.com/wader/fq/internal/difftest"
"github.com/wader/fq/internal/script"
"github.com/wader/fq/pkg/interp"
)
var writeActual = os.Getenv("WRITE_ACTUAL") != ""
func TestPath(t *testing.T, registry *registry.Registry) {
difftest.TestWithOptions(t, difftest.Options{
Path: ".",
Pattern: "*.fqtest",
ColorDiff: os.Getenv("TEST_COLOR") != "",
WriteOutput: os.Getenv("WRITE_ACTUAL") != "",
Fn: func(t *testing.T, path, input string) (string, string, error) {
b, err := ioutil.ReadFile(path)
if err != nil {
t.Fatal(err)
}
c := script.ParseCases(string(b))
c.Path = path
for _, p := range c.Parts {
cr, ok := p.(*script.CaseRun)
if !ok {
continue
}
t.Run(strconv.Itoa(cr.LineNr)+"/"+cr.Command, func(t *testing.T) {
cr.WasRun = true
func testDecodedTestCaseRun(t *testing.T, registry *registry.Registry, cr *script.CaseRun) {
i, err := interp.New(cr, registry)
if err != nil {
t.Fatal(err)
@ -28,61 +47,10 @@ func testDecodedTestCaseRun(t *testing.T, registry *registry.Registry, cr *scrip
cr.ActualExitCode = ex.ExitCode()
}
}
if writeActual {
return
}
deepequal.Error(t, "exitcode", cr.ExpectedExitCode, cr.ActualExitCode)
deepequal.Error(t, "stdout", cr.ToExpectedStdout(), cr.ActualStdoutBuf.String())
deepequal.Error(t, "stderr", cr.ToExpectedStderr(), cr.ActualStderrBuf.String())
}
func TestPath(t *testing.T, registry *registry.Registry) {
cs := []*script.Case{}
err := filepath.Walk(".", func(path string, info os.FileInfo, err error) error {
if filepath.Ext(path) != ".fqtest" {
return nil
}
t.Run(path, func(t *testing.T) {
b, err := ioutil.ReadFile(path)
if err != nil {
t.Fatal(err)
}
c := script.ParseCases(string(b))
cs = append(cs, c)
c.Path = path
for _, p := range c.Parts {
cr, ok := p.(*script.CaseRun)
if !ok {
continue
}
t.Run(strconv.Itoa(cr.LineNr)+":"+cr.Command, func(t *testing.T) {
testDecodedTestCaseRun(t, registry, cr)
c.WasRun = true
})
}
})
return nil
return path, c.ToActual(), nil
},
})
if err != nil {
t.Fatal(err)
}
if writeActual {
for _, c := range cs {
if !c.WasRun {
continue
}
if err := ioutil.WriteFile(c.Path, []byte(c.ToActual()), 0644); err != nil { //nolint:gosec
t.Error(err)
}
}
}
}