Skip to content

Commit 2d44e37

Browse files
dmullisdmullis
authored andcommitted
Upon regression failure, produce HTML for browser to surface differences
1 parent 999d655 commit 2d44e37

File tree

2 files changed

+206
-128
lines changed

2 files changed

+206
-128
lines changed

examples-regression_test.go

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
package goat
2+
3+
import (
4+
"bytes"
5+
"errors"
6+
"flag"
7+
//"fmt"
8+
"io"
9+
"io/ioutil"
10+
"os"
11+
"path/filepath"
12+
"strings"
13+
"testing"
14+
"text/template"
15+
)
16+
17+
const (
18+
examplesDir = "examples"
19+
)
20+
21+
var (
22+
write = flag.Bool("write",
23+
false, "write reference SVG output files")
24+
svgColorLightScheme = flag.String("svg-color-light-scheme", "#000000",
25+
`See help for cmd/goat`)
26+
svgColorDarkScheme = flag.String("svg-color-dark-scheme", "#FFFFFF",
27+
`See help for cmd/goat`)
28+
29+
// Begin the directory name with '_' to hide from git.
30+
svgDeltaDir = flag.String("svg-delta-dir", "_examples_new",
31+
`Directory to be filled with a delta-image file for each
32+
newly-generated SVG that does not match those in ` + examplesDir)
33+
)
34+
35+
func TestExamples(t *testing.T) {
36+
// XX This sweeps up ~every~ *.txt file in examples/
37+
txtPaths, err := filepath.Glob(filepath.Join(examplesDir, "*.txt"))
38+
if err != nil {
39+
t.Fatal(err)
40+
}
41+
42+
baseNames := make([]string, len(txtPaths))
43+
for i := range txtPaths {
44+
baseName, found := strings.CutPrefix(txtPaths[i], examplesDir+"/")
45+
if !found {
46+
panic("Could not cut prefix from pathname.")
47+
}
48+
baseNames[i] = baseName
49+
}
50+
51+
if *write {
52+
writeExamples(examplesDir, examplesDir, baseNames, *svgColorLightScheme, *svgColorDarkScheme)
53+
} else {
54+
t.Logf("Verifying equality of current SVG with examples/ references.\n")
55+
verifyExamples(t, examplesDir, baseNames)
56+
}
57+
}
58+
59+
60+
func writeExamples(inDir, outDir string, baseNames []string, lightColor, darkColor string) {
61+
for _, name := range baseNames {
62+
in := getIn(inDir + "/" + name)
63+
out := getOut(outDir + "/" + name)
64+
BuildAndWriteSVG(in, out, lightColor, darkColor)
65+
in.Close()
66+
out.Close()
67+
}
68+
}
69+
70+
func verifyExamples(t *testing.T, examplesDir string, baseNames []string) {
71+
var failures []string
72+
for _, name := range baseNames {
73+
in := getIn(examplesDir + "/" + name)
74+
buff := &bytes.Buffer{}
75+
BuildAndWriteSVG(in, buff, *svgColorLightScheme, *svgColorDarkScheme)
76+
in.Close()
77+
if nil != compareSVG(t, buff, examplesDir, name) {
78+
failures = append(failures, name)
79+
}
80+
81+
}
82+
if len(failures) > 0 {
83+
t.Logf(`Failed to verify contents of %d .svg files`,
84+
len(failures))
85+
err := os.Mkdir(*svgDeltaDir, 0770)
86+
if err != nil {
87+
t.Fatalf(`
88+
Aborting: "%v"`, err)
89+
}
90+
writeExamples(examplesDir, *svgDeltaDir, failures, "#000088", "#88CCFF")
91+
writeDeltaHTML(t, "../" + examplesDir, *svgDeltaDir, failures)
92+
t.FailNow()
93+
}
94+
}
95+
96+
func compareSVG(t *testing.T, buff *bytes.Buffer, examplesDir string, baseName string) error {
97+
fileName := examplesDir + "/" + baseName
98+
golden, err := getOutString(fileName)
99+
if err != nil {
100+
t.Log(err)
101+
}
102+
if newStr := buff.String(); newStr != golden {
103+
// Skip complaint if the modification timestamp of the .txt file
104+
// source is fresher than that of the .svg?
105+
// => NO, Any .txt difference might be an editing mistake.
106+
107+
t.Logf("Content mismatch for %s. Length was %d, expected %d",
108+
toSVGFilename(fileName), buff.Len(), len(golden))
109+
for i:=0; i<min(len(golden), len(newStr)); i++ {
110+
if newStr[i] != golden[i] {
111+
t.Logf("Differing runes at offset %d: new='%#v' reference='%#v'\n",
112+
i, newStr[i], golden[i])
113+
break
114+
}
115+
}
116+
t.Logf("Generated contents do not match existing %s",
117+
toSVGFilename(fileName))
118+
return errors.New("Generated contents do not match existing")
119+
} else {
120+
if testing.Verbose() {
121+
t.Logf("Existing and generated contents match %s\n",
122+
toSVGFilename(fileName))
123+
}
124+
}
125+
return nil
126+
}
127+
128+
// See https://developer.mozilla.org/en-US/docs/Web/CSS/blend-mode#example_using_difference
129+
func writeDeltaHTML(t *testing.T, examplesDir, deltaDir string, baseNames []string) {
130+
t.Logf("Writing new SVG and HTML delta files into %s/", deltaDir)
131+
132+
tmpl := template.Must(template.New("_ignored_").Parse(`
133+
<style type="text/css">
134+
.blended-images {
135+
height: 100%; /* XX How to make equal to pixel bounds of the SVGs? */
136+
background-size: contain, contain;
137+
background-repeat: no-repeat;
138+
background-blend-mode: difference;
139+
background-image: url('{{.ExamplesDir}}/{{.SvgBaseName}}'), url('{{.DeltaDir}}/{{.SvgBaseName}}');
140+
}
141+
</style>
142+
143+
<div style="background-color: grey;">
144+
<div class="blended-images"></div>
145+
</div>
146+
`))
147+
for _, name := range baseNames {
148+
htmlOutName := stripSuffix(name) + ".html"
149+
t.Logf("\t%s", htmlOutName)
150+
htmlOutFile, err := os.Create(deltaDir + "/" + htmlOutName)
151+
err = tmpl.Execute(htmlOutFile, map[string]string{
152+
"ExamplesDir": examplesDir,
153+
"DeltaDir": ".",
154+
"SvgBaseName": toSVGFilename(name),
155+
})
156+
htmlOutFile.Close()
157+
if err != nil {
158+
panic(err)
159+
}
160+
}
161+
}
162+
163+
func getIn(txtFilename string) io.ReadCloser {
164+
in, err := os.Open(txtFilename)
165+
if err != nil {
166+
panic(err)
167+
}
168+
return in
169+
}
170+
171+
func getOutExport(pathPrefix, txtBaseName string) io.WriteCloser {
172+
svgBaseName := toSVGFilename(txtBaseName)
173+
out, err := os.Create(pathPrefix + svgBaseName)
174+
if err != nil {
175+
panic(err)
176+
}
177+
return out
178+
}
179+
180+
func getOut(txtFilename string) io.WriteCloser {
181+
out, err := os.Create(toSVGFilename(txtFilename))
182+
if err != nil {
183+
panic(err)
184+
}
185+
return out
186+
}
187+
188+
func getOutString(txtFilename string) (string, error) {
189+
b, err := ioutil.ReadFile(toSVGFilename(txtFilename))
190+
if err != nil {
191+
// XX Simply panic rather than return an error?
192+
return "", err
193+
}
194+
// XX Why are there RETURN characters in contents of the .SVG files?
195+
b = bytes.ReplaceAll(b, []byte("\r\n"), []byte("\n"))
196+
return string(b), nil
197+
}
198+
199+
func toSVGFilename(txtFilename string) string {
200+
return strings.TrimSuffix(txtFilename, filepath.Ext(txtFilename)) + ".svg"
201+
}
202+
203+
func stripSuffix(basename string) string {
204+
return strings.Split(basename,".")[0]
205+
}

examples_test.go

Lines changed: 1 addition & 128 deletions
Original file line numberDiff line numberDiff line change
@@ -2,31 +2,17 @@ package goat
22

33
import (
44
"bytes"
5-
"errors"
6-
"flag"
7-
"fmt"
85
"io"
9-
"io/ioutil"
106
"os"
117
"path/filepath"
12-
"strings"
138
"testing"
149
)
1510

16-
var (
17-
write = flag.Bool("write",
18-
false, "write reference SVG output files")
19-
svgColorLightScheme = flag.String("svg-color-light-scheme", "#000000",
20-
`See help for cmd/goat`)
21-
svgColorDarkScheme = flag.String("svg-color-dark-scheme", "#FFFFFF",
22-
`See help for cmd/goat`)
23-
)
24-
2511
// XX TXT source file suite is limited to a single file -- "circuits.txt"
2612
func TestExampleStableOutput(t *testing.T) {
2713
var previous string
2814
for i := 0; i < 3; i++ {
29-
in, err := os.Open(filepath.Join(basePath, "circuits.txt"))
15+
in, err := os.Open(filepath.Join(examplesDir, "circuits.txt"))
3016
if err != nil {
3117
t.Fatal(err)
3218
}
@@ -41,86 +27,6 @@ func TestExampleStableOutput(t *testing.T) {
4127
}
4228
}
4329

44-
func TestExamples(t *testing.T) {
45-
filenames, err := filepath.Glob(filepath.Join(basePath, "*.txt"))
46-
if err != nil {
47-
t.Fatal(err)
48-
}
49-
50-
if *write {
51-
writeExamples(t, filenames)
52-
} else {
53-
t.Logf("Verifying equality of current SVG with examples/ references.\n")
54-
verifyExamples(t, filenames)
55-
}
56-
}
57-
58-
59-
func writeExamples(t *testing.T, filenames []string) {
60-
for _, name := range filenames {
61-
in := getIn(name)
62-
out := getOut(name)
63-
BuildAndWriteSVG(in, out, *svgColorLightScheme, *svgColorDarkScheme)
64-
in.Close()
65-
out.Close()
66-
}
67-
}
68-
69-
func verifyExamples(t *testing.T, filenames []string) {
70-
var failures []string
71-
for _, name := range filenames {
72-
in := getIn(name)
73-
buff := &bytes.Buffer{}
74-
BuildAndWriteSVG(in, buff, *svgColorLightScheme, *svgColorDarkScheme)
75-
in.Close()
76-
if nil != compareSVG(t, buff, name) {
77-
failures = append(failures, name)
78-
}
79-
80-
}
81-
if len(failures) > 0 {
82-
t.Logf(`Failed to verify contents of %d .svg files
83-
Failing files:`,
84-
len(failures))
85-
for _, name := range failures {
86-
svgFile := toSVGFilename(name)
87-
fmt.Printf("\t\t%s\n", svgFile)
88-
}
89-
t.FailNow()
90-
}
91-
}
92-
93-
func compareSVG(t *testing.T, buff *bytes.Buffer, fileName string) error {
94-
golden, err := getOutString(fileName)
95-
if err != nil {
96-
t.Log(err)
97-
}
98-
if newStr := buff.String(); newStr != golden {
99-
// Skip complaint if the modification timestamp of the .txt file
100-
// source is fresher than that of the .svg?
101-
// => NO, Any .txt difference might be an editing mistake.
102-
103-
t.Logf("Content mismatch for %s. Length was %d, expected %d",
104-
toSVGFilename(fileName), buff.Len(), len(golden))
105-
for i:=0; i<min(len(golden), len(newStr)); i++ {
106-
if newStr[i] != golden[i] {
107-
t.Logf("Differing runes at offset %d: new='%#v' reference='%#v'\n",
108-
i, newStr[i], golden[i])
109-
break
110-
}
111-
}
112-
t.Logf("Generated contents do not match existing %s",
113-
toSVGFilename(fileName))
114-
return errors.New("Generated contents do not match existing")
115-
} else {
116-
if testing.Verbose() {
117-
t.Logf("Existing and generated contents match %s\n",
118-
toSVGFilename(fileName))
119-
}
120-
}
121-
return nil
122-
}
123-
12430
func BenchmarkComplicated(b *testing.B) {
12531
in := getIn(filepath.FromSlash("examples/complicated.txt"))
12632
b.ResetTimer()
@@ -129,36 +35,3 @@ func BenchmarkComplicated(b *testing.B) {
12935
}
13036
in.Close()
13137
}
132-
133-
const basePath string = "examples"
134-
135-
func getIn(txtFilename string) io.ReadCloser {
136-
in, err := os.Open(txtFilename)
137-
if err != nil {
138-
panic(err)
139-
}
140-
return in
141-
}
142-
143-
func getOut(txtFilename string) io.WriteCloser {
144-
out, err := os.Create(toSVGFilename(txtFilename))
145-
if err != nil {
146-
panic(err)
147-
}
148-
return out
149-
}
150-
151-
func getOutString(txtFilename string) (string, error) {
152-
b, err := ioutil.ReadFile(toSVGFilename(txtFilename))
153-
if err != nil {
154-
// XX Simply panic rather than return an error?
155-
return "", err
156-
}
157-
// XX Why are there RETURN characters in contents of the .SVG files?
158-
b = bytes.ReplaceAll(b, []byte("\r\n"), []byte("\n"))
159-
return string(b), nil
160-
}
161-
162-
func toSVGFilename(txtFilename string) string {
163-
return strings.TrimSuffix(txtFilename, filepath.Ext(txtFilename)) + ".svg"
164-
}

0 commit comments

Comments
 (0)