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:
parent
4cee498e06
commit
f0233bb811
@ -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)
|
||||
}
|
@ -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",
|
||||
)
|
||||
}
|
172
internal/difftest/difftest.go
Normal file
172
internal/difftest/difftest.go
Normal 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,
|
||||
})
|
||||
}
|
@ -62,6 +62,7 @@ type CaseRun struct {
|
||||
Readlines []CaseReadline
|
||||
ReadlinesPos int
|
||||
ReadlineEnv []string
|
||||
WasRun bool
|
||||
}
|
||||
|
||||
func (cr *CaseRun) Line() int { return cr.LineNr }
|
||||
@ -236,23 +237,41 @@ 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.ActualExitCode != 0 {
|
||||
fmt.Fprintf(sb, "exitcode: %d\n", p.ActualExitCode)
|
||||
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.ActualStderrBuf.Len() > 0 {
|
||||
fmt.Fprint(sb, "stderr:\n")
|
||||
fmt.Fprint(sb, p.ActualStderrBuf.String())
|
||||
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)
|
||||
|
@ -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))
|
||||
|
||||
}
|
@ -4,56 +4,27 @@ 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 testDecodedTestCaseRun(t *testing.T, registry *registry.Registry, cr *script.CaseRun) {
|
||||
i, err := interp.New(cr, registry)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = i.Main(context.Background(), cr.Stdout(), "dev")
|
||||
if err != nil {
|
||||
if ex, ok := err.(interp.Exiter); ok { //nolint:errorlint
|
||||
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) {
|
||||
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))
|
||||
|
||||
cs = append(cs, c)
|
||||
c.Path = path
|
||||
|
||||
for _, p := range c.Parts {
|
||||
@ -62,27 +33,24 @@ func TestPath(t *testing.T, registry *registry.Registry) {
|
||||
continue
|
||||
}
|
||||
|
||||
t.Run(strconv.Itoa(cr.LineNr)+":"+cr.Command, func(t *testing.T) {
|
||||
testDecodedTestCaseRun(t, registry, cr)
|
||||
c.WasRun = true
|
||||
t.Run(strconv.Itoa(cr.LineNr)+"/"+cr.Command, func(t *testing.T) {
|
||||
cr.WasRun = true
|
||||
|
||||
i, err := interp.New(cr, registry)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = i.Main(context.Background(), cr.Stdout(), "dev")
|
||||
if err != nil {
|
||||
if ex, ok := err.(interp.Exiter); ok { //nolint:errorlint
|
||||
cr.ActualExitCode = ex.ExitCode()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user