mirror of
https://github.com/autc04/Retro68.git
synced 2024-10-20 20:24:15 +00:00
278 lines
6.9 KiB
Go
278 lines
6.9 KiB
Go
// Copyright 2017 The Go Authors. All rights reserved.
|
||
// Use of this source code is governed by a BSD-style
|
||
// license that can be found in the LICENSE file.
|
||
|
||
package test2json
|
||
|
||
import (
|
||
"bytes"
|
||
"encoding/json"
|
||
"flag"
|
||
"fmt"
|
||
"io"
|
||
"io/ioutil"
|
||
"path/filepath"
|
||
"reflect"
|
||
"strings"
|
||
"testing"
|
||
"unicode/utf8"
|
||
)
|
||
|
||
var update = flag.Bool("update", false, "rewrite testdata/*.json files")
|
||
|
||
func TestGolden(t *testing.T) {
|
||
files, err := filepath.Glob("testdata/*.test")
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
for _, file := range files {
|
||
name := strings.TrimSuffix(filepath.Base(file), ".test")
|
||
t.Run(name, func(t *testing.T) {
|
||
orig, err := ioutil.ReadFile(file)
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
|
||
// Test one line written to c at a time.
|
||
// Assume that's the most likely to be handled correctly.
|
||
var buf bytes.Buffer
|
||
c := NewConverter(&buf, "", 0)
|
||
in := append([]byte{}, orig...)
|
||
for _, line := range bytes.SplitAfter(in, []byte("\n")) {
|
||
writeAndKill(c, line)
|
||
}
|
||
c.Close()
|
||
|
||
if *update {
|
||
js := strings.TrimSuffix(file, ".test") + ".json"
|
||
t.Logf("rewriting %s", js)
|
||
if err := ioutil.WriteFile(js, buf.Bytes(), 0666); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
return
|
||
}
|
||
|
||
want, err := ioutil.ReadFile(strings.TrimSuffix(file, ".test") + ".json")
|
||
if err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
diffJSON(t, buf.Bytes(), want)
|
||
if t.Failed() {
|
||
// If the line-at-a-time conversion fails, no point testing boundary conditions.
|
||
return
|
||
}
|
||
|
||
// Write entire input in bulk.
|
||
t.Run("bulk", func(t *testing.T) {
|
||
buf.Reset()
|
||
c = NewConverter(&buf, "", 0)
|
||
in = append([]byte{}, orig...)
|
||
writeAndKill(c, in)
|
||
c.Close()
|
||
diffJSON(t, buf.Bytes(), want)
|
||
})
|
||
|
||
// Write 2 bytes at a time on even boundaries.
|
||
t.Run("even2", func(t *testing.T) {
|
||
buf.Reset()
|
||
c = NewConverter(&buf, "", 0)
|
||
in = append([]byte{}, orig...)
|
||
for i := 0; i < len(in); i += 2 {
|
||
if i+2 <= len(in) {
|
||
writeAndKill(c, in[i:i+2])
|
||
} else {
|
||
writeAndKill(c, in[i:])
|
||
}
|
||
}
|
||
c.Close()
|
||
diffJSON(t, buf.Bytes(), want)
|
||
})
|
||
|
||
// Write 2 bytes at a time on odd boundaries.
|
||
t.Run("odd2", func(t *testing.T) {
|
||
buf.Reset()
|
||
c = NewConverter(&buf, "", 0)
|
||
in = append([]byte{}, orig...)
|
||
if len(in) > 0 {
|
||
writeAndKill(c, in[:1])
|
||
}
|
||
for i := 1; i < len(in); i += 2 {
|
||
if i+2 <= len(in) {
|
||
writeAndKill(c, in[i:i+2])
|
||
} else {
|
||
writeAndKill(c, in[i:])
|
||
}
|
||
}
|
||
c.Close()
|
||
diffJSON(t, buf.Bytes(), want)
|
||
})
|
||
|
||
// Test with very small output buffers, to check that
|
||
// UTF8 sequences are not broken up.
|
||
for b := 5; b <= 8; b++ {
|
||
t.Run(fmt.Sprintf("tiny%d", b), func(t *testing.T) {
|
||
oldIn := inBuffer
|
||
oldOut := outBuffer
|
||
defer func() {
|
||
inBuffer = oldIn
|
||
outBuffer = oldOut
|
||
}()
|
||
inBuffer = 64
|
||
outBuffer = b
|
||
buf.Reset()
|
||
c = NewConverter(&buf, "", 0)
|
||
in = append([]byte{}, orig...)
|
||
writeAndKill(c, in)
|
||
c.Close()
|
||
diffJSON(t, buf.Bytes(), want)
|
||
})
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
// writeAndKill writes b to w and then fills b with Zs.
|
||
// The filling makes sure that if w is holding onto b for
|
||
// future use, that future use will have obviously wrong data.
|
||
func writeAndKill(w io.Writer, b []byte) {
|
||
w.Write(b)
|
||
for i := range b {
|
||
b[i] = 'Z'
|
||
}
|
||
}
|
||
|
||
// diffJSON diffs the stream we have against the stream we want
|
||
// and fails the test with a useful message if they don't match.
|
||
func diffJSON(t *testing.T, have, want []byte) {
|
||
t.Helper()
|
||
type event map[string]interface{}
|
||
|
||
// Parse into events, one per line.
|
||
parseEvents := func(b []byte) ([]event, []string) {
|
||
t.Helper()
|
||
var events []event
|
||
var lines []string
|
||
for _, line := range bytes.SplitAfter(b, []byte("\n")) {
|
||
if len(line) > 0 {
|
||
line = bytes.TrimSpace(line)
|
||
var e event
|
||
err := json.Unmarshal(line, &e)
|
||
if err != nil {
|
||
t.Errorf("unmarshal %s: %v", b, err)
|
||
continue
|
||
}
|
||
events = append(events, e)
|
||
lines = append(lines, string(line))
|
||
}
|
||
}
|
||
return events, lines
|
||
}
|
||
haveEvents, haveLines := parseEvents(have)
|
||
wantEvents, wantLines := parseEvents(want)
|
||
if t.Failed() {
|
||
return
|
||
}
|
||
|
||
// Make sure the events we have match the events we want.
|
||
// At each step we're matching haveEvents[i] against wantEvents[j].
|
||
// i and j can move independently due to choices about exactly
|
||
// how to break up text in "output" events.
|
||
i := 0
|
||
j := 0
|
||
|
||
// Fail reports a failure at the current i,j and stops the test.
|
||
// It shows the events around the current positions,
|
||
// with the current positions marked.
|
||
fail := func() {
|
||
var buf bytes.Buffer
|
||
show := func(i int, lines []string) {
|
||
for k := -2; k < 5; k++ {
|
||
marker := ""
|
||
if k == 0 {
|
||
marker = "» "
|
||
}
|
||
if 0 <= i+k && i+k < len(lines) {
|
||
fmt.Fprintf(&buf, "\t%s%s\n", marker, lines[i+k])
|
||
}
|
||
}
|
||
if i >= len(lines) {
|
||
// show marker after end of input
|
||
fmt.Fprintf(&buf, "\t» \n")
|
||
}
|
||
}
|
||
fmt.Fprintf(&buf, "have:\n")
|
||
show(i, haveLines)
|
||
fmt.Fprintf(&buf, "want:\n")
|
||
show(j, wantLines)
|
||
t.Fatal(buf.String())
|
||
}
|
||
|
||
var outputTest string // current "Test" key in "output" events
|
||
var wantOutput, haveOutput string // collected "Output" of those events
|
||
|
||
// getTest returns the "Test" setting, or "" if it is missing.
|
||
getTest := func(e event) string {
|
||
s, _ := e["Test"].(string)
|
||
return s
|
||
}
|
||
|
||
// checkOutput collects output from the haveEvents for the current outputTest
|
||
// and then checks that the collected output matches the wanted output.
|
||
checkOutput := func() {
|
||
for i < len(haveEvents) && haveEvents[i]["Action"] == "output" && getTest(haveEvents[i]) == outputTest {
|
||
haveOutput += haveEvents[i]["Output"].(string)
|
||
i++
|
||
}
|
||
if haveOutput != wantOutput {
|
||
t.Errorf("output mismatch for Test=%q:\nhave %q\nwant %q", outputTest, haveOutput, wantOutput)
|
||
fail()
|
||
}
|
||
haveOutput = ""
|
||
wantOutput = ""
|
||
}
|
||
|
||
// Walk through wantEvents matching against haveEvents.
|
||
for j = range wantEvents {
|
||
e := wantEvents[j]
|
||
if e["Action"] == "output" && getTest(e) == outputTest {
|
||
wantOutput += e["Output"].(string)
|
||
continue
|
||
}
|
||
checkOutput()
|
||
if e["Action"] == "output" {
|
||
outputTest = getTest(e)
|
||
wantOutput += e["Output"].(string)
|
||
continue
|
||
}
|
||
if i >= len(haveEvents) {
|
||
t.Errorf("early end of event stream: missing event")
|
||
fail()
|
||
}
|
||
if !reflect.DeepEqual(haveEvents[i], e) {
|
||
t.Errorf("events out of sync")
|
||
fail()
|
||
}
|
||
i++
|
||
}
|
||
checkOutput()
|
||
if i < len(haveEvents) {
|
||
t.Errorf("extra events in stream")
|
||
fail()
|
||
}
|
||
}
|
||
|
||
func TestTrimUTF8(t *testing.T) {
|
||
s := "hello α ☺ 😂 world" // α is 2-byte, ☺ is 3-byte, 😂 is 4-byte
|
||
b := []byte(s)
|
||
for i := 0; i < len(s); i++ {
|
||
j := trimUTF8(b[:i])
|
||
u := string([]rune(s[:j])) + string([]rune(s[j:]))
|
||
if u != s {
|
||
t.Errorf("trimUTF8(%q) = %d (-%d), not at boundary (split: %q %q)", s[:i], j, i-j, s[:j], s[j:])
|
||
}
|
||
if utf8.FullRune(b[j:i]) {
|
||
t.Errorf("trimUTF8(%q) = %d (-%d), too early (missed: %q)", s[:j], j, i-j, s[j:i])
|
||
}
|
||
}
|
||
}
|