github.com/kdevb0x/go@v0.0.0-20180115030120-39687051e9e7/src/cmd/internal/test2json/test2json_test.go (about) 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 "io/ioutil" 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 := ioutil.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 := ioutil.WriteFile(js, buf.Bytes(), 0666); err != nil { 50 t.Fatal(err) 51 } 52 return 53 } 54 55 want, err := ioutil.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 // Write 2 bytes at a time on even boundaries. 76 t.Run("even2", func(t *testing.T) { 77 buf.Reset() 78 c = NewConverter(&buf, "", 0) 79 in = append([]byte{}, orig...) 80 for i := 0; i < len(in); i += 2 { 81 if i+2 <= len(in) { 82 writeAndKill(c, in[i:i+2]) 83 } else { 84 writeAndKill(c, in[i:]) 85 } 86 } 87 c.Close() 88 diffJSON(t, buf.Bytes(), want) 89 }) 90 91 // Write 2 bytes at a time on odd boundaries. 92 t.Run("odd2", func(t *testing.T) { 93 buf.Reset() 94 c = NewConverter(&buf, "", 0) 95 in = append([]byte{}, orig...) 96 if len(in) > 0 { 97 writeAndKill(c, in[:1]) 98 } 99 for i := 1; i < len(in); i += 2 { 100 if i+2 <= len(in) { 101 writeAndKill(c, in[i:i+2]) 102 } else { 103 writeAndKill(c, in[i:]) 104 } 105 } 106 c.Close() 107 diffJSON(t, buf.Bytes(), want) 108 }) 109 110 // Test with very small output buffers, to check that 111 // UTF8 sequences are not broken up. 112 for b := 5; b <= 8; b++ { 113 t.Run(fmt.Sprintf("tiny%d", b), func(t *testing.T) { 114 oldIn := inBuffer 115 oldOut := outBuffer 116 defer func() { 117 inBuffer = oldIn 118 outBuffer = oldOut 119 }() 120 inBuffer = 64 121 outBuffer = b 122 buf.Reset() 123 c = NewConverter(&buf, "", 0) 124 in = append([]byte{}, orig...) 125 writeAndKill(c, in) 126 c.Close() 127 diffJSON(t, buf.Bytes(), want) 128 }) 129 } 130 }) 131 } 132 } 133 134 // writeAndKill writes b to w and then fills b with Zs. 135 // The filling makes sure that if w is holding onto b for 136 // future use, that future use will have obviously wrong data. 137 func writeAndKill(w io.Writer, b []byte) { 138 w.Write(b) 139 for i := range b { 140 b[i] = 'Z' 141 } 142 } 143 144 // diffJSON diffs the stream we have against the stream we want 145 // and fails the test with a useful message if they don't match. 146 func diffJSON(t *testing.T, have, want []byte) { 147 t.Helper() 148 type event map[string]interface{} 149 150 // Parse into events, one per line. 151 parseEvents := func(b []byte) ([]event, []string) { 152 t.Helper() 153 var events []event 154 var lines []string 155 for _, line := range bytes.SplitAfter(b, []byte("\n")) { 156 if len(line) > 0 { 157 line = bytes.TrimSpace(line) 158 var e event 159 err := json.Unmarshal(line, &e) 160 if err != nil { 161 t.Errorf("unmarshal %s: %v", b, err) 162 continue 163 } 164 events = append(events, e) 165 lines = append(lines, string(line)) 166 } 167 } 168 return events, lines 169 } 170 haveEvents, haveLines := parseEvents(have) 171 wantEvents, wantLines := parseEvents(want) 172 if t.Failed() { 173 return 174 } 175 176 // Make sure the events we have match the events we want. 177 // At each step we're matching haveEvents[i] against wantEvents[j]. 178 // i and j can move independently due to choices about exactly 179 // how to break up text in "output" events. 180 i := 0 181 j := 0 182 183 // Fail reports a failure at the current i,j and stops the test. 184 // It shows the events around the current positions, 185 // with the current positions marked. 186 fail := func() { 187 var buf bytes.Buffer 188 show := func(i int, lines []string) { 189 for k := -2; k < 5; k++ { 190 marker := "" 191 if k == 0 { 192 marker = "» " 193 } 194 if 0 <= i+k && i+k < len(lines) { 195 fmt.Fprintf(&buf, "\t%s%s\n", marker, lines[i+k]) 196 } 197 } 198 if i >= len(lines) { 199 // show marker after end of input 200 fmt.Fprintf(&buf, "\t» \n") 201 } 202 } 203 fmt.Fprintf(&buf, "have:\n") 204 show(i, haveLines) 205 fmt.Fprintf(&buf, "want:\n") 206 show(j, wantLines) 207 t.Fatal(buf.String()) 208 } 209 210 var outputTest string // current "Test" key in "output" events 211 var wantOutput, haveOutput string // collected "Output" of those events 212 213 // getTest returns the "Test" setting, or "" if it is missing. 214 getTest := func(e event) string { 215 s, _ := e["Test"].(string) 216 return s 217 } 218 219 // checkOutput collects output from the haveEvents for the current outputTest 220 // and then checks that the collected output matches the wanted output. 221 checkOutput := func() { 222 for i < len(haveEvents) && haveEvents[i]["Action"] == "output" && getTest(haveEvents[i]) == outputTest { 223 haveOutput += haveEvents[i]["Output"].(string) 224 i++ 225 } 226 if haveOutput != wantOutput { 227 t.Errorf("output mismatch for Test=%q:\nhave %q\nwant %q", outputTest, haveOutput, wantOutput) 228 fail() 229 } 230 haveOutput = "" 231 wantOutput = "" 232 } 233 234 // Walk through wantEvents matching against haveEvents. 235 for j = range wantEvents { 236 e := wantEvents[j] 237 if e["Action"] == "output" && getTest(e) == outputTest { 238 wantOutput += e["Output"].(string) 239 continue 240 } 241 checkOutput() 242 if e["Action"] == "output" { 243 outputTest = getTest(e) 244 wantOutput += e["Output"].(string) 245 continue 246 } 247 if i >= len(haveEvents) { 248 t.Errorf("early end of event stream: missing event") 249 fail() 250 } 251 if !reflect.DeepEqual(haveEvents[i], e) { 252 t.Errorf("events out of sync") 253 fail() 254 } 255 i++ 256 } 257 checkOutput() 258 if i < len(haveEvents) { 259 t.Errorf("extra events in stream") 260 fail() 261 } 262 } 263 264 func TestTrimUTF8(t *testing.T) { 265 s := "hello α ☺ 😂 world" // α is 2-byte, ☺ is 3-byte, 😂 is 4-byte 266 b := []byte(s) 267 for i := 0; i < len(s); i++ { 268 j := trimUTF8(b[:i]) 269 u := string([]rune(s[:j])) + string([]rune(s[j:])) 270 if u != s { 271 t.Errorf("trimUTF8(%q) = %d (-%d), not at boundary (split: %q %q)", s[:i], j, i-j, s[:j], s[j:]) 272 } 273 if utf8.FullRune(b[j:i]) { 274 t.Errorf("trimUTF8(%q) = %d (-%d), too early (missed: %q)", s[:j], j, i-j, s[j:i]) 275 } 276 } 277 }