2021-10-18 22:38:16 +03:00
|
|
|
// Package difftest implement test based on serialized string output
|
|
|
|
//
|
|
|
|
// User provides a function that gets a input string. It returns a output string
|
|
|
|
// based on the input somehow and a output path to file with content to compare it
|
|
|
|
// to or to write to if in write mode.
|
|
|
|
// If there is a difference test will fail with a diff.
|
|
|
|
//
|
|
|
|
// Test inputs are read from files matching Pattern from Path.
|
|
|
|
//
|
|
|
|
// Note that output path can be the same as input which useful if the function
|
|
|
|
// implements some kind of transcript that includes both input and output.
|
2021-10-10 17:32:39 +03:00
|
|
|
package difftest
|
|
|
|
|
|
|
|
import (
|
|
|
|
"io/ioutil"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
|
|
|
"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
|
|
|
|
}
|
|
|
|
|
2021-10-18 22:38:16 +03:00
|
|
|
func testDeepEqual(t tf, color bool, printfFn func(format string, args ...interface{}), expected string, actual string) {
|
2021-10-10 17:32:39 +03:00
|
|
|
t.Helper()
|
|
|
|
|
2021-10-18 22:38:16 +03:00
|
|
|
diff := difflib.UnifiedDiff{
|
|
|
|
A: difflib.SplitLines(expected),
|
|
|
|
B: difflib.SplitLines(actual),
|
|
|
|
FromFile: "expected",
|
|
|
|
ToFile: "actual",
|
|
|
|
Context: 3,
|
|
|
|
}
|
|
|
|
uDiff, err := difflib.GetUnifiedDiffString(diff)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("%s", err)
|
|
|
|
}
|
|
|
|
if uDiff == "" {
|
|
|
|
return
|
|
|
|
}
|
2021-10-10 17:32:39 +03:00
|
|
|
|
2021-10-18 22:38:16 +03:00
|
|
|
if color {
|
|
|
|
lines := strings.Split(uDiff, "\n")
|
|
|
|
var coloredLines []string
|
|
|
|
// diff looks like this:
|
|
|
|
// --- expected
|
|
|
|
// +++ actual
|
|
|
|
// @@ -5,7 +5,7 @@
|
|
|
|
// - a
|
|
|
|
// + b
|
|
|
|
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
|
2021-10-10 17:32:39 +03:00
|
|
|
}
|
2021-10-18 22:38:16 +03:00
|
|
|
coloredLines = append(coloredLines, l)
|
2021-10-10 17:32:39 +03:00
|
|
|
}
|
|
|
|
}
|
2021-10-18 22:38:16 +03:00
|
|
|
uDiff = strings.Join(coloredLines, "\n")
|
2021-10-10 17:32:39 +03:00
|
|
|
}
|
2021-10-18 22:38:16 +03:00
|
|
|
|
|
|
|
printfFn("%s", "\n"+uDiff)
|
2021-10-10 17:32:39 +03:00
|
|
|
}
|
|
|
|
|
2021-10-18 22:38:16 +03:00
|
|
|
func ErrorEx(t tf, color bool, expected string, actual string) {
|
2021-10-10 17:32:39 +03:00
|
|
|
t.Helper()
|
|
|
|
testDeepEqual(t, color, t.Errorf, expected, actual)
|
|
|
|
}
|
|
|
|
|
2021-10-18 22:38:16 +03:00
|
|
|
func Error(t tf, expected string, actual string) {
|
2021-10-10 17:32:39 +03:00
|
|
|
t.Helper()
|
|
|
|
testDeepEqual(t, false, t.Errorf, expected, actual)
|
|
|
|
}
|
|
|
|
|
2021-10-18 22:38:16 +03:00
|
|
|
func FatalEx(t tf, color bool, expected string, actual string) {
|
2021-10-10 17:32:39 +03:00
|
|
|
t.Helper()
|
|
|
|
testDeepEqual(t, color, t.Fatalf, expected, actual)
|
|
|
|
}
|
|
|
|
|
2021-10-18 22:38:16 +03:00
|
|
|
func Fatal(t tf, expected string, actual string) {
|
2021-10-10 17:32:39 +03:00
|
|
|
t.Helper()
|
|
|
|
testDeepEqual(t, false, t.Fatalf, expected, actual)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestWithOptions(t *testing.T, opts Options) {
|
|
|
|
t.Helper()
|
|
|
|
|
2021-10-30 02:26:00 +03:00
|
|
|
t.Helper()
|
|
|
|
|
|
|
|
// done in 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 {
|
2021-10-10 17:32:39 +03:00
|
|
|
t.Helper()
|
|
|
|
|
2021-10-30 02:26:00 +03:00
|
|
|
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
|
|
|
|
}
|
2021-10-10 17:32:39 +03:00
|
|
|
|
2021-10-30 02:26:00 +03:00
|
|
|
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)
|
2021-10-10 17:32:39 +03:00
|
|
|
if err != nil {
|
2021-10-30 02:26:00 +03:00
|
|
|
t.Fatal(err)
|
2021-10-10 17:32:39 +03:00
|
|
|
}
|
2021-10-30 02:26:00 +03:00
|
|
|
|
|
|
|
outputPath, output, err := opts.Fn(t, p, string(input))
|
2021-10-10 17:32:39 +03:00
|
|
|
if err != nil {
|
2021-10-30 02:26:00 +03:00
|
|
|
t.Fatal(err)
|
2021-10-10 17:32:39 +03:00
|
|
|
}
|
|
|
|
|
2021-10-30 02:26:00 +03:00
|
|
|
expectedOutput, expectedOutputErr := ioutil.ReadFile(outputPath)
|
|
|
|
if opts.WriteOutput {
|
|
|
|
if expectedOutputErr == nil && string(expectedOutput) == output {
|
|
|
|
return
|
2021-10-10 17:32:39 +03:00
|
|
|
}
|
|
|
|
|
2021-10-30 02:26:00 +03:00
|
|
|
if err := ioutil.WriteFile(outputPath, []byte(output), 0644); err != nil { //nolint:gosec
|
2021-10-10 17:32:39 +03:00
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2021-10-30 02:26:00 +03:00
|
|
|
return
|
|
|
|
}
|
2021-10-10 17:32:39 +03:00
|
|
|
|
2021-10-30 02:26:00 +03:00
|
|
|
if expectedOutputErr != nil {
|
|
|
|
t.Fatal(expectedOutputErr)
|
|
|
|
}
|
2021-10-10 22:19:33 +03:00
|
|
|
|
2021-10-30 02:26:00 +03:00
|
|
|
ErrorEx(t, opts.ColorDiff, string(expectedOutput), output)
|
|
|
|
})
|
|
|
|
}
|
2021-10-10 17:32:39 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
func Test(t *testing.T, pattern string, fn Fn) {
|
|
|
|
t.Helper()
|
|
|
|
|
|
|
|
TestWithOptions(t, Options{
|
|
|
|
Path: "testdata",
|
|
|
|
Pattern: pattern,
|
|
|
|
Fn: fn,
|
|
|
|
})
|
|
|
|
}
|