internal/cuetxtar: allow multiple golden files

This allows for:
- more organized output
- collating output for hierarchical subtests

Change-Id: I195b8d52de6f3abae2337e4bb80e2f5f86575f3f
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/7603
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
Reviewed-by: CUE cueckoo <cueckoo@gmail.com>
diff --git a/internal/cuetxtar/txtar.go b/internal/cuetxtar/txtar.go
index e2b8a07..ec2d126 100644
--- a/internal/cuetxtar/txtar.go
+++ b/internal/cuetxtar/txtar.go
@@ -18,6 +18,7 @@
 	"bufio"
 	"bytes"
 	"fmt"
+	"io"
 	"io/ioutil"
 	"os"
 	"path"
@@ -72,9 +73,9 @@
 	// Allow Test to be used as a T.
 	*testing.T
 
-	// Buffer is used to write the test results that will be compared to the
-	// golden file.
-	*bytes.Buffer
+	prefix   string
+	buf      *bytes.Buffer // the default buffer
+	outFiles []file
 
 	Archive *txtar.Archive
 
@@ -84,6 +85,19 @@
 	hasGold bool
 }
 
+func (t *Test) Write(b []byte) (n int, err error) {
+	if t.buf == nil {
+		t.buf = &bytes.Buffer{}
+		t.outFiles = append(t.outFiles, file{t.prefix, t.buf})
+	}
+	return t.buf.Write(b)
+}
+
+type file struct {
+	name string
+	buf  *bytes.Buffer
+}
+
 func (t *Test) HasTag(key string) bool {
 	prefix := []byte("#" + key)
 	s := bufio.NewScanner(bytes.NewReader(t.Archive.Comment))
@@ -137,8 +151,34 @@
 
 // Write file in a directory.
 func (t *Test) WriteFile(f *ast.File) {
+	// TODO: use FileWriter instead in separate CL.
 	fmt.Fprintln(t, "==", filepath.Base(f.Filename))
-	t.Write(formatNode(t.T, f))
+	_, _ = t.Write(formatNode(t.T, f))
+}
+
+// Writer returns a Writer with the given name.
+func (t *Test) Writer(name string) io.Writer {
+	switch name {
+	case "":
+		name = t.prefix
+	default:
+		name = path.Join(t.prefix, name)
+	}
+
+	for _, f := range t.outFiles {
+		if f.name == name {
+			return f.buf
+		}
+	}
+
+	w := &bytes.Buffer{}
+	t.outFiles = append(t.outFiles, file{name, w})
+
+	if name == t.prefix {
+		t.buf = w
+	}
+
+	return w
 }
 
 func formatNode(t *testing.T, n ast.Node) []byte {
@@ -222,22 +262,19 @@
 				t.Fatalf("error parsing txtar file: %v", err)
 			}
 
-			outFile := path.Join("out", x.Name)
-
-			var gold *txtar.File
-			for i, f := range a.Files {
-				if f.Name == outFile {
-					gold = &a.Files[i]
-				}
-			}
-
 			tc := &Test{
 				T:       t,
-				Buffer:  &bytes.Buffer{},
 				Archive: a,
 				Dir:     filepath.Dir(filepath.Join(dir, fullpath)),
 
-				hasGold: gold != nil,
+				prefix: path.Join("out", x.Name),
+			}
+
+			for _, f := range a.Files {
+				// TODO: not entirely correct.
+				if strings.HasPrefix(f.Name, tc.prefix) {
+					tc.hasGold = true
+				}
 			}
 
 			if tc.HasTag("skip") {
@@ -253,31 +290,42 @@
 
 			f(tc)
 
-			result := tc.Bytes()
-			if len(result) == 0 {
-				return
+			update := false
+			for _, sub := range tc.outFiles {
+				var gold *txtar.File
+				for i, f := range a.Files {
+					if f.Name == sub.name {
+						gold = &a.Files[i]
+					}
+				}
+
+				result := sub.buf.Bytes()
+
+				switch {
+				case gold == nil:
+					a.Files = append(a.Files, txtar.File{Name: sub.name})
+					gold = &a.Files[len(a.Files)-1]
+
+				case bytes.Equal(gold.Data, result):
+					continue
+				}
+
+				if x.Update || envUpdate != "" {
+					update = true
+					gold.Data = result
+					continue
+				}
+
+				t.Errorf("result for %s differs:\n%s",
+					sub.name,
+					cmp.Diff(string(gold.Data), string(result)))
 			}
 
-			if gold == nil {
-				a.Files = append(a.Files, txtar.File{Name: outFile})
-				gold = &a.Files[len(a.Files)-1]
-			}
-
-			if bytes.Equal(gold.Data, result) {
-				return
-			}
-
-			if !x.Update && envUpdate == "" {
-				t.Fatal(cmp.Diff(string(gold.Data), string(result)))
-			}
-
-			gold.Data = result
-
-			// Update and write file.
-
-			err = ioutil.WriteFile(fullpath, txtar.Format(a), 0644)
-			if err != nil {
-				t.Fatal(err)
+			if update {
+				err = ioutil.WriteFile(fullpath, txtar.Format(a), 0644)
+				if err != nil {
+					t.Fatal(err)
+				}
 			}
 		})