Source file src/cmd/internal/test2json/test2json_test.go

     1  // Copyright 2017 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package test2json
     6  
     7  import (
     8  	"bytes"
     9  	"encoding/json"
    10  	"flag"
    11  	"fmt"
    12  	"io"
    13  	"os"
    14  	"path/filepath"
    15  	"reflect"
    16  	"strings"
    17  	"testing"
    18  	"unicode/utf8"
    19  )
    20  
    21  var update = flag.Bool("update", false, "rewrite testdata/*.json files")
    22  
    23  func TestGolden(t *testing.T) {
    24  	files, err := filepath.Glob("testdata/*.test")
    25  	if err != nil {
    26  		t.Fatal(err)
    27  	}
    28  	for _, file := range files {
    29  		name := strings.TrimSuffix(filepath.Base(file), ".test")
    30  		t.Run(name, func(t *testing.T) {
    31  			orig, err := os.ReadFile(file)
    32  			if err != nil {
    33  				t.Fatal(err)
    34  			}
    35  
    36  			// Test one line written to c at a time.
    37  			// Assume that's the most likely to be handled correctly.
    38  			var buf bytes.Buffer
    39  			c := NewConverter(&buf, "", 0)
    40  			in := append([]byte{}, orig...)
    41  			for _, line := range bytes.SplitAfter(in, []byte("\n")) {
    42  				writeAndKill(c, line)
    43  			}
    44  			c.Close()
    45  
    46  			if *update {
    47  				js := strings.TrimSuffix(file, ".test") + ".json"
    48  				t.Logf("rewriting %s", js)
    49  				if err := os.WriteFile(js, buf.Bytes(), 0666); err != nil {
    50  					t.Fatal(err)
    51  				}
    52  				return
    53  			}
    54  
    55  			want, err := os.ReadFile(strings.TrimSuffix(file, ".test") + ".json")
    56  			if err != nil {
    57  				t.Fatal(err)
    58  			}
    59  			diffJSON(t, buf.Bytes(), want)
    60  			if t.Failed() {
    61  				// If the line-at-a-time conversion fails, no point testing boundary conditions.
    62  				return
    63  			}
    64  
    65  			// Write entire input in bulk.
    66  			t.Run("bulk", func(t *testing.T) {
    67  				buf.Reset()
    68  				c = NewConverter(&buf, "", 0)
    69  				in = append([]byte{}, orig...)
    70  				writeAndKill(c, in)
    71  				c.Close()
    72  				diffJSON(t, buf.Bytes(), want)
    73  			})
    74  
    75  			// In bulk again with \r\n.
    76  			t.Run("crlf", func(t *testing.T) {
    77  				buf.Reset()
    78  				c = NewConverter(&buf, "", 0)
    79  				in = bytes.ReplaceAll(orig, []byte("\n"), []byte("\r\n"))
    80  				writeAndKill(c, in)
    81  				c.Close()
    82  				diffJSON(t, bytes.ReplaceAll(buf.Bytes(), []byte(`\r\n`), []byte(`\n`)), want)
    83  			})
    84  
    85  			// Write 2 bytes at a time on even boundaries.
    86  			t.Run("even2", func(t *testing.T) {
    87  				buf.Reset()
    88  				c = NewConverter(&buf, "", 0)
    89  				in = append([]byte{}, orig...)
    90  				for i := 0; i < len(in); i += 2 {
    91  					if i+2 <= len(in) {
    92  						writeAndKill(c, in[i:i+2])
    93  					} else {
    94  						writeAndKill(c, in[i:])
    95  					}
    96  				}
    97  				c.Close()
    98  				diffJSON(t, buf.Bytes(), want)
    99  			})
   100  
   101  			// Write 2 bytes at a time on odd boundaries.
   102  			t.Run("odd2", func(t *testing.T) {
   103  				buf.Reset()
   104  				c = NewConverter(&buf, "", 0)
   105  				in = append([]byte{}, orig...)
   106  				if len(in) > 0 {
   107  					writeAndKill(c, in[:1])
   108  				}
   109  				for i := 1; i < len(in); i += 2 {
   110  					if i+2 <= len(in) {
   111  						writeAndKill(c, in[i:i+2])
   112  					} else {
   113  						writeAndKill(c, in[i:])
   114  					}
   115  				}
   116  				c.Close()
   117  				diffJSON(t, buf.Bytes(), want)
   118  			})
   119  
   120  			// Test with very small output buffers, to check that
   121  			// UTF8 sequences are not broken up.
   122  			for b := 5; b <= 8; b++ {
   123  				t.Run(fmt.Sprintf("tiny%d", b), func(t *testing.T) {
   124  					oldIn := inBuffer
   125  					oldOut := outBuffer
   126  					defer func() {
   127  						inBuffer = oldIn
   128  						outBuffer = oldOut
   129  					}()
   130  					inBuffer = 64
   131  					outBuffer = b
   132  					buf.Reset()
   133  					c = NewConverter(&buf, "", 0)
   134  					in = append([]byte{}, orig...)
   135  					writeAndKill(c, in)
   136  					c.Close()
   137  					diffJSON(t, buf.Bytes(), want)
   138  				})
   139  			}
   140  		})
   141  	}
   142  }
   143  
   144  // writeAndKill writes b to w and then fills b with Zs.
   145  // The filling makes sure that if w is holding onto b for
   146  // future use, that future use will have obviously wrong data.
   147  func writeAndKill(w io.Writer, b []byte) {
   148  	w.Write(b)
   149  	for i := range b {
   150  		b[i] = 'Z'
   151  	}
   152  }
   153  
   154  // diffJSON diffs the stream we have against the stream we want
   155  // and fails the test with a useful message if they don't match.
   156  func diffJSON(t *testing.T, have, want []byte) {
   157  	t.Helper()
   158  	type event map[string]any
   159  
   160  	// Parse into events, one per line.
   161  	parseEvents := func(b []byte) ([]event, []string) {
   162  		t.Helper()
   163  		var events []event
   164  		var lines []string
   165  		for _, line := range bytes.SplitAfter(b, []byte("\n")) {
   166  			if len(line) > 0 {
   167  				line = bytes.TrimSpace(line)
   168  				var e event
   169  				err := json.Unmarshal(line, &e)
   170  				if err != nil {
   171  					t.Errorf("unmarshal %s: %v", b, err)
   172  					continue
   173  				}
   174  				events = append(events, e)
   175  				lines = append(lines, string(line))
   176  			}
   177  		}
   178  		return events, lines
   179  	}
   180  	haveEvents, haveLines := parseEvents(have)
   181  	wantEvents, wantLines := parseEvents(want)
   182  	if t.Failed() {
   183  		return
   184  	}
   185  
   186  	// Make sure the events we have match the events we want.
   187  	// At each step we're matching haveEvents[i] against wantEvents[j].
   188  	// i and j can move independently due to choices about exactly
   189  	// how to break up text in "output" events.
   190  	i := 0
   191  	j := 0
   192  
   193  	// Fail reports a failure at the current i,j and stops the test.
   194  	// It shows the events around the current positions,
   195  	// with the current positions marked.
   196  	fail := func() {
   197  		var buf bytes.Buffer
   198  		show := func(i int, lines []string) {
   199  			for k := -2; k < 5; k++ {
   200  				marker := ""
   201  				if k == 0 {
   202  					marker = "» "
   203  				}
   204  				if 0 <= i+k && i+k < len(lines) {
   205  					fmt.Fprintf(&buf, "\t%s%s\n", marker, lines[i+k])
   206  				}
   207  			}
   208  			if i >= len(lines) {
   209  				// show marker after end of input
   210  				fmt.Fprintf(&buf, "\t» \n")
   211  			}
   212  		}
   213  		fmt.Fprintf(&buf, "have:\n")
   214  		show(i, haveLines)
   215  		fmt.Fprintf(&buf, "want:\n")
   216  		show(j, wantLines)
   217  		t.Fatal(buf.String())
   218  	}
   219  
   220  	var outputTest string             // current "Test" key in "output" events
   221  	var wantOutput, haveOutput string // collected "Output" of those events
   222  
   223  	// getTest returns the "Test" setting, or "" if it is missing.
   224  	getTest := func(e event) string {
   225  		s, _ := e["Test"].(string)
   226  		return s
   227  	}
   228  
   229  	// checkOutput collects output from the haveEvents for the current outputTest
   230  	// and then checks that the collected output matches the wanted output.
   231  	checkOutput := func() {
   232  		for i < len(haveEvents) && haveEvents[i]["Action"] == "output" && getTest(haveEvents[i]) == outputTest {
   233  			haveOutput += haveEvents[i]["Output"].(string)
   234  			i++
   235  		}
   236  		if haveOutput != wantOutput {
   237  			t.Errorf("output mismatch for Test=%q:\nhave %q\nwant %q", outputTest, haveOutput, wantOutput)
   238  			fail()
   239  		}
   240  		haveOutput = ""
   241  		wantOutput = ""
   242  	}
   243  
   244  	// Walk through wantEvents matching against haveEvents.
   245  	for j = range wantEvents {
   246  		e := wantEvents[j]
   247  		if e["Action"] == "output" && getTest(e) == outputTest {
   248  			wantOutput += e["Output"].(string)
   249  			continue
   250  		}
   251  		checkOutput()
   252  		if e["Action"] == "output" {
   253  			outputTest = getTest(e)
   254  			wantOutput += e["Output"].(string)
   255  			continue
   256  		}
   257  		if i >= len(haveEvents) {
   258  			t.Errorf("early end of event stream: missing event")
   259  			fail()
   260  		}
   261  		if !reflect.DeepEqual(haveEvents[i], e) {
   262  			t.Errorf("events out of sync")
   263  			fail()
   264  		}
   265  		i++
   266  	}
   267  	checkOutput()
   268  	if i < len(haveEvents) {
   269  		t.Errorf("extra events in stream")
   270  		fail()
   271  	}
   272  }
   273  
   274  func TestTrimUTF8(t *testing.T) {
   275  	s := "hello α ☺ 😂 world" // α is 2-byte, ☺ is 3-byte, 😂 is 4-byte
   276  	b := []byte(s)
   277  	for i := 0; i < len(s); i++ {
   278  		j := trimUTF8(b[:i])
   279  		u := string([]rune(s[:j])) + string([]rune(s[j:]))
   280  		if u != s {
   281  			t.Errorf("trimUTF8(%q) = %d (-%d), not at boundary (split: %q %q)", s[:i], j, i-j, s[:j], s[j:])
   282  		}
   283  		if utf8.FullRune(b[j:i]) {
   284  			t.Errorf("trimUTF8(%q) = %d (-%d), too early (missed: %q)", s[:j], j, i-j, s[j:i])
   285  		}
   286  	}
   287  }
   288  

View as plain text