github.com/slayercat/go@v0.0.0-20170428012452-c51559813f61/src/cmd/asm/internal/asm/endtoend_test.go (about) 1 // Copyright 2015 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 asm 6 7 import ( 8 "bufio" 9 "bytes" 10 "fmt" 11 "io/ioutil" 12 "os" 13 "path/filepath" 14 "regexp" 15 "sort" 16 "strconv" 17 "strings" 18 "testing" 19 20 "cmd/asm/internal/lex" 21 "cmd/internal/obj" 22 ) 23 24 // An end-to-end test for the assembler: Do we print what we parse? 25 // Output is generated by, in effect, turning on -S and comparing the 26 // result against a golden file. 27 28 func testEndToEnd(t *testing.T, goarch, file string) { 29 input := filepath.Join("testdata", file+".s") 30 architecture, ctxt := setArch(goarch) 31 architecture.Init(ctxt) 32 lexer := lex.NewLexer(input) 33 parser := NewParser(ctxt, architecture, lexer) 34 pList := new(obj.Plist) 35 var ok bool 36 testOut = new(bytes.Buffer) // The assembler writes test output to this buffer. 37 ctxt.Bso = bufio.NewWriter(os.Stdout) 38 defer ctxt.Bso.Flush() 39 failed := false 40 ctxt.DiagFunc = func(format string, args ...interface{}) { 41 failed = true 42 t.Errorf(format, args...) 43 } 44 pList.Firstpc, ok = parser.Parse() 45 if !ok || failed { 46 t.Errorf("asm: %s assembly failed", goarch) 47 return 48 } 49 output := strings.Split(testOut.String(), "\n") 50 51 // Reconstruct expected output by independently "parsing" the input. 52 data, err := ioutil.ReadFile(input) 53 if err != nil { 54 t.Error(err) 55 return 56 } 57 lineno := 0 58 seq := 0 59 hexByLine := map[string]string{} 60 lines := strings.SplitAfter(string(data), "\n") 61 Diff: 62 for _, line := range lines { 63 lineno++ 64 65 // Ignore include of textflag.h. 66 if strings.HasPrefix(line, "#include ") { 67 continue 68 } 69 70 // The general form of a test input line is: 71 // // comment 72 // INST args [// printed form] [// hex encoding] 73 parts := strings.Split(line, "//") 74 printed := strings.TrimSpace(parts[0]) 75 if printed == "" || strings.HasSuffix(printed, ":") { // empty or label 76 continue 77 } 78 seq++ 79 80 var hexes string 81 switch len(parts) { 82 default: 83 t.Errorf("%s:%d: unable to understand comments: %s", input, lineno, line) 84 case 1: 85 // no comment 86 case 2: 87 // might be printed form or hex 88 note := strings.TrimSpace(parts[1]) 89 if isHexes(note) { 90 hexes = note 91 } else { 92 printed = note 93 } 94 case 3: 95 // printed form, then hex 96 printed = strings.TrimSpace(parts[1]) 97 hexes = strings.TrimSpace(parts[2]) 98 if !isHexes(hexes) { 99 t.Errorf("%s:%d: malformed hex instruction encoding: %s", input, lineno, line) 100 } 101 } 102 103 if hexes != "" { 104 hexByLine[fmt.Sprintf("%s:%d", input, lineno)] = hexes 105 } 106 107 // Canonicalize spacing in printed form. 108 // First field is opcode, then tab, then arguments separated by spaces. 109 // Canonicalize spaces after commas first. 110 // Comma to separate argument gets a space; comma within does not. 111 var buf []byte 112 nest := 0 113 for i := 0; i < len(printed); i++ { 114 c := printed[i] 115 switch c { 116 case '{', '[': 117 nest++ 118 case '}', ']': 119 nest-- 120 case ',': 121 buf = append(buf, ',') 122 if nest == 0 { 123 buf = append(buf, ' ') 124 } 125 for i+1 < len(printed) && (printed[i+1] == ' ' || printed[i+1] == '\t') { 126 i++ 127 } 128 continue 129 } 130 buf = append(buf, c) 131 } 132 133 f := strings.Fields(string(buf)) 134 135 // Turn relative (PC) into absolute (PC) automatically, 136 // so that most branch instructions don't need comments 137 // giving the absolute form. 138 if len(f) > 0 && strings.HasSuffix(printed, "(PC)") { 139 last := f[len(f)-1] 140 n, err := strconv.Atoi(last[:len(last)-len("(PC)")]) 141 if err == nil { 142 f[len(f)-1] = fmt.Sprintf("%d(PC)", seq+n) 143 } 144 } 145 146 if len(f) == 1 { 147 printed = f[0] 148 } else { 149 printed = f[0] + "\t" + strings.Join(f[1:], " ") 150 } 151 152 want := fmt.Sprintf("%05d (%s:%d)\t%s", seq, input, lineno, printed) 153 for len(output) > 0 && (output[0] < want || output[0] != want && len(output[0]) >= 5 && output[0][:5] == want[:5]) { 154 if len(output[0]) >= 5 && output[0][:5] == want[:5] { 155 t.Errorf("mismatched output:\nhave %s\nwant %s", output[0], want) 156 output = output[1:] 157 continue Diff 158 } 159 t.Errorf("unexpected output: %q", output[0]) 160 output = output[1:] 161 } 162 if len(output) > 0 && output[0] == want { 163 output = output[1:] 164 } else { 165 t.Errorf("missing output: %q", want) 166 } 167 } 168 for len(output) > 0 { 169 if output[0] == "" { 170 // spurious blank caused by Split on "\n" 171 output = output[1:] 172 continue 173 } 174 t.Errorf("unexpected output: %q", output[0]) 175 output = output[1:] 176 } 177 178 // Checked printing. 179 // Now check machine code layout. 180 181 top := pList.Firstpc 182 var text *obj.LSym 183 ok = true 184 ctxt.DiagFunc = func(format string, args ...interface{}) { 185 t.Errorf(format, args...) 186 ok = false 187 } 188 obj.Flushplist(ctxt, pList, nil) 189 190 for p := top; p != nil; p = p.Link { 191 if p.As == obj.ATEXT { 192 text = p.From.Sym 193 } 194 hexes := hexByLine[p.Line()] 195 if hexes == "" { 196 continue 197 } 198 delete(hexByLine, p.Line()) 199 if text == nil { 200 t.Errorf("%s: instruction outside TEXT", p) 201 } 202 size := int64(len(text.P)) - p.Pc 203 if p.Link != nil { 204 size = p.Link.Pc - p.Pc 205 } else if p.Isize != 0 { 206 size = int64(p.Isize) 207 } 208 var code []byte 209 if p.Pc < int64(len(text.P)) { 210 code = text.P[p.Pc:] 211 if size < int64(len(code)) { 212 code = code[:size] 213 } 214 } 215 codeHex := fmt.Sprintf("%x", code) 216 if codeHex == "" { 217 codeHex = "empty" 218 } 219 ok := false 220 for _, hex := range strings.Split(hexes, " or ") { 221 if codeHex == hex { 222 ok = true 223 break 224 } 225 } 226 if !ok { 227 t.Errorf("%s: have encoding %s, want %s", p, codeHex, hexes) 228 } 229 } 230 231 if len(hexByLine) > 0 { 232 var missing []string 233 for key := range hexByLine { 234 missing = append(missing, key) 235 } 236 sort.Strings(missing) 237 for _, line := range missing { 238 t.Errorf("%s: did not find instruction encoding", line) 239 } 240 } 241 242 } 243 244 func isHexes(s string) bool { 245 if s == "" { 246 return false 247 } 248 if s == "empty" { 249 return true 250 } 251 for _, f := range strings.Split(s, " or ") { 252 if f == "" || len(f)%2 != 0 || strings.TrimLeft(f, "0123456789abcdef") != "" { 253 return false 254 } 255 } 256 return true 257 } 258 259 // It would be nice if the error messages began with 260 // the standard file:line: prefix, 261 // but that's not where we are today. 262 // It might be at the beginning but it might be in the middle of the printed instruction. 263 var fileLineRE = regexp.MustCompile(`(?:^|\()(testdata[/\\][0-9a-z]+\.s:[0-9]+)(?:$|\))`) 264 265 // Same as in test/run.go 266 var ( 267 errRE = regexp.MustCompile(`// ERROR ?(.*)`) 268 errQuotesRE = regexp.MustCompile(`"([^"]*)"`) 269 ) 270 271 func testErrors(t *testing.T, goarch, file string) { 272 input := filepath.Join("testdata", file+".s") 273 architecture, ctxt := setArch(goarch) 274 lexer := lex.NewLexer(input) 275 parser := NewParser(ctxt, architecture, lexer) 276 pList := new(obj.Plist) 277 var ok bool 278 testOut = new(bytes.Buffer) // The assembler writes test output to this buffer. 279 ctxt.Bso = bufio.NewWriter(os.Stdout) 280 defer ctxt.Bso.Flush() 281 failed := false 282 var errBuf bytes.Buffer 283 ctxt.DiagFunc = func(format string, args ...interface{}) { 284 failed = true 285 s := fmt.Sprintf(format, args...) 286 if !strings.HasSuffix(s, "\n") { 287 s += "\n" 288 } 289 errBuf.WriteString(s) 290 } 291 pList.Firstpc, ok = parser.Parse() 292 obj.Flushplist(ctxt, pList, nil) 293 if ok && !failed { 294 t.Errorf("asm: %s had no errors", goarch) 295 } 296 297 errors := map[string]string{} 298 for _, line := range strings.Split(errBuf.String(), "\n") { 299 if line == "" || strings.HasPrefix(line, "\t") { 300 continue 301 } 302 m := fileLineRE.FindStringSubmatch(line) 303 if m == nil { 304 t.Errorf("unexpected error: %v", line) 305 continue 306 } 307 fileline := m[1] 308 if errors[fileline] != "" { 309 t.Errorf("multiple errors on %s:\n\t%s\n\t%s", fileline, errors[fileline], line) 310 continue 311 } 312 errors[fileline] = line 313 } 314 315 // Reconstruct expected errors by independently "parsing" the input. 316 data, err := ioutil.ReadFile(input) 317 if err != nil { 318 t.Error(err) 319 return 320 } 321 lineno := 0 322 lines := strings.Split(string(data), "\n") 323 for _, line := range lines { 324 lineno++ 325 326 fileline := fmt.Sprintf("%s:%d", input, lineno) 327 if m := errRE.FindStringSubmatch(line); m != nil { 328 all := m[1] 329 mm := errQuotesRE.FindAllStringSubmatch(all, -1) 330 if len(mm) != 1 { 331 t.Errorf("%s: invalid errorcheck line:\n%s", fileline, line) 332 } else if err := errors[fileline]; err == "" { 333 t.Errorf("%s: missing error, want %s", fileline, all) 334 } else if !strings.Contains(err, mm[0][1]) { 335 t.Errorf("%s: wrong error for %s:\n%s", fileline, all, err) 336 } 337 } else { 338 if errors[fileline] != "" { 339 t.Errorf("unexpected error on %s: %v", fileline, errors[fileline]) 340 } 341 } 342 delete(errors, fileline) 343 } 344 var extra []string 345 for key := range errors { 346 extra = append(extra, key) 347 } 348 sort.Strings(extra) 349 for _, fileline := range extra { 350 t.Errorf("unexpected error on %s: %v", fileline, errors[fileline]) 351 } 352 } 353 354 func Test386EndToEnd(t *testing.T) { 355 defer os.Setenv("GO386", os.Getenv("GO386")) 356 357 for _, go386 := range []string{"387", "sse"} { 358 os.Setenv("GO386", go386) 359 t.Logf("GO386=%v", os.Getenv("GO386")) 360 testEndToEnd(t, "386", "386") 361 } 362 } 363 364 func TestARMEndToEnd(t *testing.T) { 365 defer os.Setenv("GOARM", os.Getenv("GOARM")) 366 367 for _, goarm := range []string{"5", "6", "7"} { 368 os.Setenv("GOARM", goarm) 369 t.Logf("GOARM=%v", os.Getenv("GOARM")) 370 testEndToEnd(t, "arm", "arm") 371 } 372 } 373 374 func TestARM64EndToEnd(t *testing.T) { 375 testEndToEnd(t, "arm64", "arm64") 376 } 377 378 func TestAMD64EndToEnd(t *testing.T) { 379 testEndToEnd(t, "amd64", "amd64") 380 } 381 382 func TestAMD64Encoder(t *testing.T) { 383 testEndToEnd(t, "amd64", "amd64enc") 384 } 385 386 func TestAMD64Errors(t *testing.T) { 387 testErrors(t, "amd64", "amd64error") 388 } 389 390 func TestMIPSEndToEnd(t *testing.T) { 391 testEndToEnd(t, "mips", "mips") 392 testEndToEnd(t, "mips64", "mips64") 393 } 394 395 func TestPPC64EndToEnd(t *testing.T) { 396 testEndToEnd(t, "ppc64", "ppc64") 397 } 398 399 func TestS390XEndToEnd(t *testing.T) { 400 testEndToEnd(t, "s390x", "s390x") 401 }