212 lines
6.7 KiB
Go
212 lines
6.7 KiB
Go
package baseline
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
|
|
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/collections"
|
|
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/core"
|
|
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/repo"
|
|
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/stringutil"
|
|
"github.com/peter-evans/patience"
|
|
)
|
|
|
|
type Options struct {
|
|
Subfolder string
|
|
IsSubmodule bool
|
|
IsSubmoduleAccepted bool
|
|
DiffFixupOld func(string) string
|
|
SkipDiffWithOld bool
|
|
}
|
|
|
|
const NoContent = "<no content>"
|
|
|
|
func Run(t *testing.T, fileName string, actual string, opts Options) {
|
|
origSubfolder := opts.Subfolder
|
|
|
|
{
|
|
subfolder := opts.Subfolder
|
|
if opts.IsSubmodule {
|
|
subfolder = filepath.Join("submodule", subfolder)
|
|
}
|
|
|
|
localPath := filepath.Join(localRoot, subfolder, fileName)
|
|
referencePath := filepath.Join(referenceRoot, subfolder, fileName)
|
|
|
|
writeComparison(t, actual, localPath, referencePath, false)
|
|
}
|
|
|
|
if !opts.IsSubmodule || opts.SkipDiffWithOld {
|
|
// Not a submodule, no diffs.
|
|
return
|
|
}
|
|
|
|
submoduleReference := filepath.Join(submoduleReferenceRoot, fileName)
|
|
submoduleExpected := readFileOrNoContent(submoduleReference)
|
|
|
|
const (
|
|
submoduleFolder = "submodule"
|
|
submoduleAcceptedFolder = "submoduleAccepted"
|
|
)
|
|
|
|
diffFileName := fileName + ".diff"
|
|
isSubmoduleAccepted := opts.IsSubmoduleAccepted || submoduleAcceptedFileNames().Has(origSubfolder+"/"+diffFileName)
|
|
|
|
outRoot := core.IfElse(isSubmoduleAccepted, submoduleAcceptedFolder, submoduleFolder)
|
|
unusedOutRoot := core.IfElse(isSubmoduleAccepted, submoduleFolder, submoduleAcceptedFolder)
|
|
|
|
{
|
|
localPath := filepath.Join(localRoot, outRoot, origSubfolder, diffFileName)
|
|
referencePath := filepath.Join(referenceRoot, outRoot, origSubfolder, diffFileName)
|
|
|
|
diff := getBaselineDiff(t, actual, submoduleExpected, fileName, opts.DiffFixupOld)
|
|
writeComparison(t, diff, localPath, referencePath, false)
|
|
}
|
|
|
|
// Delete the other diff file if it exists
|
|
{
|
|
localPath := filepath.Join(localRoot, unusedOutRoot, origSubfolder, diffFileName)
|
|
referencePath := filepath.Join(referenceRoot, unusedOutRoot, origSubfolder, diffFileName)
|
|
writeComparison(t, NoContent, localPath, referencePath, false)
|
|
}
|
|
}
|
|
|
|
var submoduleAcceptedFileNames = sync.OnceValue(func() *collections.Set[string] {
|
|
var set collections.Set[string]
|
|
|
|
submoduleAccepted := filepath.Join(repo.TestDataPath, "submoduleAccepted.txt")
|
|
if content, err := os.ReadFile(submoduleAccepted); err == nil {
|
|
for line := range strings.SplitSeq(string(content), "\n") {
|
|
line = strings.TrimSpace(line)
|
|
if line == "" || line[0] == '#' {
|
|
continue
|
|
}
|
|
set.Add(line)
|
|
}
|
|
} else {
|
|
panic(fmt.Sprintf("failed to read submodule accepted file: %v", err))
|
|
}
|
|
|
|
return &set
|
|
})
|
|
|
|
func readFileOrNoContent(fileName string) string {
|
|
content, err := os.ReadFile(fileName)
|
|
if err != nil {
|
|
return NoContent
|
|
}
|
|
return string(content)
|
|
}
|
|
|
|
func DiffText(oldName string, newName string, expected string, actual string) string {
|
|
lines := patience.Diff(stringutil.SplitLines(expected), stringutil.SplitLines(actual))
|
|
return patience.UnifiedDiffTextWithOptions(lines, patience.UnifiedDiffOptions{
|
|
Precontext: 3,
|
|
Postcontext: 3,
|
|
SrcHeader: oldName,
|
|
DstHeader: newName,
|
|
})
|
|
}
|
|
|
|
func getBaselineDiff(t *testing.T, actual string, expected string, fileName string, fixupOld func(string) string) string {
|
|
if fixupOld != nil {
|
|
expected = fixupOld(expected)
|
|
}
|
|
if actual == expected {
|
|
return NoContent
|
|
}
|
|
s := DiffText("old."+fileName, "new."+fileName, expected, actual)
|
|
|
|
// Remove line numbers from unified diff headers; this avoids adding/deleting
|
|
// lines in our baselines from causing knock-on header changes later in the diff.
|
|
|
|
aCurLine := 1
|
|
bCurLine := 1
|
|
s = fixUnifiedDiff.ReplaceAllStringFunc(s, func(match string) string {
|
|
var aLine, aLineCount, bLine, bLineCount int
|
|
if _, err := fmt.Sscanf(match, "@@ -%d,%d +%d,%d @@", &aLine, &aLineCount, &bLine, &bLineCount); err != nil {
|
|
panic(fmt.Sprintf("failed to parse unified diff header: %v", err))
|
|
}
|
|
aDiff := aLine - aCurLine
|
|
bDiff := bLine - bCurLine
|
|
aCurLine = aLine
|
|
bCurLine = bLine
|
|
|
|
// Keep surrounded by @@, to make GitHub's grammar happy.
|
|
// https://github.com/textmate/diff.tmbundle/blob/0593bb775eab1824af97ef2172fd38822abd97d7/Syntaxes/Diff.plist#L68
|
|
return fmt.Sprintf("@@= skipped -%d, +%d lines =@@", aDiff, bDiff)
|
|
})
|
|
|
|
return s
|
|
}
|
|
|
|
var fixUnifiedDiff = regexp.MustCompile(`@@ -\d+,\d+ \+\d+,\d+ @@`)
|
|
|
|
func RunAgainstSubmodule(t *testing.T, fileName string, actual string, opts Options) {
|
|
local := filepath.Join(localRoot, opts.Subfolder, fileName)
|
|
reference := filepath.Join(submoduleReferenceRoot, opts.Subfolder, fileName)
|
|
writeComparison(t, actual, local, reference, true)
|
|
}
|
|
|
|
func writeComparison(t *testing.T, actualContent string, local, reference string, comparingAgainstSubmodule bool) {
|
|
if actualContent == "" {
|
|
panic("the generated content was \"\". Return 'baseline.NoContent' if no baselining is required.")
|
|
}
|
|
|
|
if err := os.MkdirAll(filepath.Dir(local), 0o755); err != nil {
|
|
t.Error(fmt.Errorf("failed to create directories for the local baseline file %s: %w", local, err))
|
|
return
|
|
}
|
|
|
|
if _, err := os.Stat(local); err == nil {
|
|
if err := os.Remove(local); err != nil {
|
|
t.Error(fmt.Errorf("failed to remove the local baseline file %s: %w", local, err))
|
|
return
|
|
}
|
|
}
|
|
|
|
expected := NoContent
|
|
foundExpected := false
|
|
if content, err := os.ReadFile(reference); err == nil {
|
|
expected = string(content)
|
|
foundExpected = true
|
|
}
|
|
|
|
if expected != actualContent || actualContent == NoContent && foundExpected {
|
|
if actualContent == NoContent {
|
|
if err := os.WriteFile(local+".delete", []byte{}, 0o644); err != nil {
|
|
t.Error(fmt.Errorf("failed to write the local baseline file %s: %w", local+".delete", err))
|
|
return
|
|
}
|
|
} else {
|
|
if err := os.WriteFile(local, []byte(actualContent), 0o644); err != nil {
|
|
t.Error(fmt.Errorf("failed to write the local baseline file %s: %w", local, err))
|
|
return
|
|
}
|
|
}
|
|
|
|
if _, err := os.Stat(reference); err != nil {
|
|
if comparingAgainstSubmodule {
|
|
t.Errorf("the baseline file %s does not exist in the TypeScript submodule", reference)
|
|
} else {
|
|
t.Errorf("new baseline created at %s.", local)
|
|
}
|
|
} else if comparingAgainstSubmodule {
|
|
t.Errorf("the baseline file %s does not match the reference in the TypeScript submodule", reference)
|
|
} else {
|
|
t.Errorf("the baseline file %s has changed. (Run `hereby baseline-accept` if the new baseline is correct.)", reference)
|
|
}
|
|
}
|
|
}
|
|
|
|
var (
|
|
localRoot = filepath.Join(repo.TestDataPath, "baselines", "local")
|
|
referenceRoot = filepath.Join(repo.TestDataPath, "baselines", "reference")
|
|
submoduleReferenceRoot = filepath.Join(repo.TypeScriptSubmodulePath, "tests", "baselines", "reference")
|
|
)
|