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  }