wip
This commit is contained in:
34
internal/gen/gen.go
Normal file
34
internal/gen/gen.go
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
package gen
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/mmcloughlin/avo/internal/inst"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Interface interface {
|
||||||
|
Generate(io.Writer, []*inst.Instruction) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type Func func(io.Writer, []*inst.Instruction) error
|
||||||
|
|
||||||
|
func (f Func) Generate(w io.Writer, is []*inst.Instruction) error {
|
||||||
|
return f(w, is)
|
||||||
|
}
|
||||||
|
|
||||||
|
type printer struct {
|
||||||
|
w io.Writer
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *printer) printf(format string, args ...interface{}) {
|
||||||
|
if p.err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, p.err = fmt.Fprintf(p.w, format, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *printer) Err() error {
|
||||||
|
return p.err
|
||||||
|
}
|
||||||
127
internal/gen/loadertest.go
Normal file
127
internal/gen/loadertest.go
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
package gen
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/mmcloughlin/avo/internal/inst"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LoaderTest struct{}
|
||||||
|
|
||||||
|
func (l LoaderTest) Generate(w io.Writer, is []*inst.Instruction) error {
|
||||||
|
p := &printer{w: w}
|
||||||
|
|
||||||
|
p.printf("TEXT loadertest(SB), 0, $0\n")
|
||||||
|
|
||||||
|
for _, i := range is {
|
||||||
|
p.printf("\t// %s %s\n", i.Opcode, i.Summary)
|
||||||
|
for _, f := range i.Forms {
|
||||||
|
as := args(f.Operands)
|
||||||
|
p.printf("\t// %#v\n", f.Operands)
|
||||||
|
if as == nil {
|
||||||
|
p.printf("\t// skip\n")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
p.printf("\t%s\t%s\n", i.Opcode, strings.Join(as, ", "))
|
||||||
|
}
|
||||||
|
p.printf("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
p.printf("\tRET\n")
|
||||||
|
|
||||||
|
return p.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
func args(ops []inst.Operand) []string {
|
||||||
|
as := make([]string, len(ops))
|
||||||
|
for i, op := range ops {
|
||||||
|
a := arg(op.Type)
|
||||||
|
if a == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
as[i] = a
|
||||||
|
}
|
||||||
|
return as
|
||||||
|
}
|
||||||
|
|
||||||
|
// arg generates an argument for an operand of the given type.
|
||||||
|
func arg(t string) string {
|
||||||
|
m := map[string]string{
|
||||||
|
// <xs:enumeration value="1" />
|
||||||
|
// <xs:enumeration value="3" />
|
||||||
|
// <xs:enumeration value="imm4" />
|
||||||
|
// <xs:enumeration value="imm8" />
|
||||||
|
// <xs:enumeration value="imm16" />
|
||||||
|
// <xs:enumeration value="imm32" />
|
||||||
|
// <xs:enumeration value="imm64" />
|
||||||
|
// <xs:enumeration value="al" />
|
||||||
|
// <xs:enumeration value="cl" />
|
||||||
|
// <xs:enumeration value="r8" />
|
||||||
|
// <xs:enumeration value="r8l" />
|
||||||
|
// <xs:enumeration value="ax" />
|
||||||
|
// <xs:enumeration value="r16" />
|
||||||
|
// <xs:enumeration value="r16l" />
|
||||||
|
// <xs:enumeration value="eax" />
|
||||||
|
// <xs:enumeration value="r32" />
|
||||||
|
// <xs:enumeration value="r32l" />
|
||||||
|
// <xs:enumeration value="rax" />
|
||||||
|
// <xs:enumeration value="r64" />
|
||||||
|
"r64": "R8",
|
||||||
|
// <xs:enumeration value="mm" />
|
||||||
|
// <xs:enumeration value="xmm0" />
|
||||||
|
// <xs:enumeration value="xmm" />
|
||||||
|
// <xs:enumeration value="xmm{k}" />
|
||||||
|
// <xs:enumeration value="xmm{k}{z}" />
|
||||||
|
// <xs:enumeration value="ymm" />
|
||||||
|
// <xs:enumeration value="ymm{k}" />
|
||||||
|
// <xs:enumeration value="ymm{k}{z}" />
|
||||||
|
// <xs:enumeration value="zmm" />
|
||||||
|
// <xs:enumeration value="zmm{k}" />
|
||||||
|
// <xs:enumeration value="zmm{k}{z}" />
|
||||||
|
// <xs:enumeration value="k" />
|
||||||
|
// <xs:enumeration value="k{k}" />
|
||||||
|
// <xs:enumeration value="moffs32" />
|
||||||
|
// <xs:enumeration value="moffs64" />
|
||||||
|
// <xs:enumeration value="m" />
|
||||||
|
// <xs:enumeration value="m8" />
|
||||||
|
// <xs:enumeration value="m16" />
|
||||||
|
// <xs:enumeration value="m16{k}{z}" />
|
||||||
|
// <xs:enumeration value="m32" />
|
||||||
|
// <xs:enumeration value="m32{k}" />
|
||||||
|
// <xs:enumeration value="m32{k}{z}" />
|
||||||
|
// <xs:enumeration value="m64" />
|
||||||
|
// <xs:enumeration value="m64{k}" />
|
||||||
|
// <xs:enumeration value="m64{k}{z}" />
|
||||||
|
// <xs:enumeration value="m128" />
|
||||||
|
// <xs:enumeration value="m128{k}{z}" />
|
||||||
|
// <xs:enumeration value="m256" />
|
||||||
|
// <xs:enumeration value="m256{k}{z}" />
|
||||||
|
// <xs:enumeration value="m512" />
|
||||||
|
// <xs:enumeration value="m512{k}{z}" />
|
||||||
|
// <xs:enumeration value="m64/m32bcst" />
|
||||||
|
// <xs:enumeration value="m128/m32bcst" />
|
||||||
|
// <xs:enumeration value="m256/m32bcst" />
|
||||||
|
// <xs:enumeration value="m512/m32bcst" />
|
||||||
|
// <xs:enumeration value="m128/m64bcst" />
|
||||||
|
// <xs:enumeration value="m256/m64bcst" />
|
||||||
|
// <xs:enumeration value="m512/m64bcst" />
|
||||||
|
// <xs:enumeration value="vm32x" />
|
||||||
|
// <xs:enumeration value="vm32x{k}" />
|
||||||
|
// <xs:enumeration value="vm64x" />
|
||||||
|
// <xs:enumeration value="vm64x{k}" />
|
||||||
|
// <xs:enumeration value="vm32y" />
|
||||||
|
// <xs:enumeration value="vm32y{k}" />
|
||||||
|
// <xs:enumeration value="vm64y" />
|
||||||
|
// <xs:enumeration value="vm64y{k}" />
|
||||||
|
// <xs:enumeration value="vm32z" />
|
||||||
|
// <xs:enumeration value="vm32z{k}" />
|
||||||
|
// <xs:enumeration value="vm64z" />
|
||||||
|
// <xs:enumeration value="vm64z{k}" />
|
||||||
|
// <xs:enumeration value="rel8" />
|
||||||
|
// <xs:enumeration value="rel32" />
|
||||||
|
// <xs:enumeration value="{er}" />
|
||||||
|
// <xs:enumeration value="{sae}" />
|
||||||
|
}
|
||||||
|
return m[t]
|
||||||
|
}
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
package inst
|
package inst
|
||||||
|
|
||||||
type Instruction struct {
|
type Instruction struct {
|
||||||
Opcode string
|
Opcode string
|
||||||
Forms []Form
|
Summary string
|
||||||
|
Forms []Form
|
||||||
}
|
}
|
||||||
|
|
||||||
type Form struct {
|
type Form struct {
|
||||||
@@ -22,3 +23,33 @@ const (
|
|||||||
W Action = 0x2
|
W Action = 0x2
|
||||||
RW Action = R | W
|
RW Action = R | W
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func ActionFromReadWrite(r, w bool) Action {
|
||||||
|
var a Action
|
||||||
|
if r {
|
||||||
|
a |= R
|
||||||
|
}
|
||||||
|
if w {
|
||||||
|
a |= W
|
||||||
|
}
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a Action) Read() bool {
|
||||||
|
return (a & R) != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a Action) Write() bool {
|
||||||
|
return (a & W) != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a Action) String() string {
|
||||||
|
s := ""
|
||||||
|
if a.Read() {
|
||||||
|
s += "r"
|
||||||
|
}
|
||||||
|
if a.Write() {
|
||||||
|
s += "w"
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|||||||
20
internal/load/ifind.sh
Executable file
20
internal/load/ifind.sh
Executable file
@@ -0,0 +1,20 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
inst=$1
|
||||||
|
|
||||||
|
eval $(go env)
|
||||||
|
|
||||||
|
section() {
|
||||||
|
echo '------------------------------------------'
|
||||||
|
echo $1
|
||||||
|
echo '------------------------------------------'
|
||||||
|
}
|
||||||
|
|
||||||
|
section 'stdlib cmd/'
|
||||||
|
grep -iR ${inst} ${GOROOT}/src/cmd/{asm,internal/obj/x86}
|
||||||
|
|
||||||
|
section 'x/arch/x86 repo'
|
||||||
|
grep -iR ${inst} ${GOPATH}/src/golang.org/x/arch/x86/
|
||||||
|
|
||||||
|
section '*_amd64.s files in stdlib'
|
||||||
|
find ${GOROOT}/src -name '*_amd64.s' | xargs grep -i ${inst}
|
||||||
181
internal/load/load.go
Normal file
181
internal/load/load.go
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
package load
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/mmcloughlin/avo/internal/inst"
|
||||||
|
"github.com/mmcloughlin/avo/internal/opcodescsv"
|
||||||
|
"github.com/mmcloughlin/avo/internal/opcodesxml"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultCSVName = "x86.v0.2.csv"
|
||||||
|
defaultOpcodesXMLName = "x86_64.xml"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Loader struct {
|
||||||
|
X86CSVPath string
|
||||||
|
OpcodesXMLPath string
|
||||||
|
|
||||||
|
alias map[opcodescsv.Alias]string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLoaderFromDataDir(dir string) *Loader {
|
||||||
|
return &Loader{
|
||||||
|
X86CSVPath: filepath.Join(dir, defaultCSVName),
|
||||||
|
OpcodesXMLPath: filepath.Join(dir, defaultOpcodesXMLName),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Loader) Load() ([]*inst.Instruction, error) {
|
||||||
|
if err := l.init(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load Opcodes XML file.
|
||||||
|
iset, err := opcodesxml.ReadFile(l.OpcodesXMLPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load opcodes XML data, grouped by Go opcode.
|
||||||
|
im := map[string]*inst.Instruction{}
|
||||||
|
for _, i := range iset.Instructions {
|
||||||
|
for _, f := range i.Forms {
|
||||||
|
if !l.include(f) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
opcode := l.goname(f)
|
||||||
|
|
||||||
|
if im[opcode] == nil {
|
||||||
|
im[opcode] = &inst.Instruction{
|
||||||
|
Opcode: opcode,
|
||||||
|
Summary: i.Summary,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
im[opcode].Forms = append(im[opcode].Forms, l.form(f))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert to a slice to return.
|
||||||
|
is := make([]*inst.Instruction, 0, len(im))
|
||||||
|
for _, i := range im {
|
||||||
|
is = append(is, i)
|
||||||
|
}
|
||||||
|
|
||||||
|
return is, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Loader) init() error {
|
||||||
|
icsv, err := opcodescsv.ReadFile(l.X86CSVPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
l.alias, err = opcodescsv.BuildAliasMap(icsv)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// include decides whether to include the instruction form in the avo listing.
|
||||||
|
// This discards some opcodes that are not supported in Go.
|
||||||
|
func (l Loader) include(f opcodesxml.Form) bool {
|
||||||
|
// Exclude certain ISAs simply not present in Go.
|
||||||
|
for _, isa := range f.ISA {
|
||||||
|
switch isa.ID {
|
||||||
|
case "TBM", "CLZERO", "MONITORX", "FEMMS":
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// x86 csv contains a number of CMOV* instructions which are actually not valid
|
||||||
|
// Go instructions. The valid Go forms should have different opcodes from GNU.
|
||||||
|
// Therefore a decent "heuristic" is CMOV* instructions that do not have
|
||||||
|
// aliases.
|
||||||
|
if strings.HasPrefix(f.GASName, "cmov") && l.lookupAlias(f) == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Some specific exclusions.
|
||||||
|
switch f.GASName {
|
||||||
|
case "callq":
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l Loader) lookupAlias(f opcodesxml.Form) string {
|
||||||
|
a := opcodescsv.Alias{Opcode: f.GASName, DataSize: datasize(f)}
|
||||||
|
return l.alias[a]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l Loader) goname(f opcodesxml.Form) string {
|
||||||
|
// Use go opcode from Opcodes XML where available.
|
||||||
|
if f.GoName != "" {
|
||||||
|
return f.GoName
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return alias if available.
|
||||||
|
if a := l.lookupAlias(f); a != "" {
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
n := strings.ToUpper(f.GASName)
|
||||||
|
|
||||||
|
// Some need data sizes added to them.
|
||||||
|
// TODO(mbm): is there a better way of determining which ones these are?
|
||||||
|
s := datasize(f)
|
||||||
|
suffix := map[int]string{16: "W", 32: "L", 64: "Q"}
|
||||||
|
switch n {
|
||||||
|
case "RDRAND", "RDSEED":
|
||||||
|
n += suffix[s]
|
||||||
|
}
|
||||||
|
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l Loader) form(f opcodesxml.Form) inst.Form {
|
||||||
|
return inst.Form{
|
||||||
|
Operands: operands(f.Operands),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// operands maps Opcodes XML operands to avo format.
|
||||||
|
func operands(ops []opcodesxml.Operand) []inst.Operand {
|
||||||
|
r := make([]inst.Operand, 0, len(ops))
|
||||||
|
for _, op := range ops {
|
||||||
|
r = append(r, operand(op))
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// operand maps an Opcodes XML operand to avo format.
|
||||||
|
func operand(op opcodesxml.Operand) inst.Operand {
|
||||||
|
return inst.Operand{
|
||||||
|
Type: op.Type,
|
||||||
|
Action: inst.ActionFromReadWrite(op.Input, op.Output),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// datasize (intelligently) guesses the datasize of an instruction form.
|
||||||
|
func datasize(f opcodesxml.Form) int {
|
||||||
|
for _, op := range f.Operands {
|
||||||
|
if !op.Output {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for s := 8; s <= 64; s *= 2 {
|
||||||
|
if strings.HasSuffix(op.Type, strconv.Itoa(s)) {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
31
internal/load/load_test.go
Normal file
31
internal/load/load_test.go
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
package load_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/mmcloughlin/avo/internal/gen"
|
||||||
|
"github.com/mmcloughlin/avo/internal/inst"
|
||||||
|
"github.com/mmcloughlin/avo/internal/load"
|
||||||
|
"github.com/mmcloughlin/avo/internal/test"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Load(t *testing.T) []*inst.Instruction {
|
||||||
|
t.Helper()
|
||||||
|
l := load.NewLoaderFromDataDir("testdata")
|
||||||
|
is, err := l.Load()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
return is
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAssembles(t *testing.T) {
|
||||||
|
is := Load(t)
|
||||||
|
|
||||||
|
g := &gen.LoaderTest{}
|
||||||
|
var buf bytes.Buffer
|
||||||
|
g.Generate(&buf, is)
|
||||||
|
|
||||||
|
test.Assembles(t, buf.Bytes())
|
||||||
|
}
|
||||||
1
internal/load/testdata
Symbolic link
1
internal/load/testdata
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
../data
|
||||||
38
internal/opcodescsv/analysis.go
Normal file
38
internal/opcodescsv/analysis.go
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
package opcodescsv
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/arch/x86/x86csv"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Alias struct {
|
||||||
|
Opcode string
|
||||||
|
DataSize int
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuildAliasMap constructs a map from AT&T/GNU/Intel to Go syntax.
|
||||||
|
func BuildAliasMap(is []*x86csv.Inst) (map[Alias]string, error) {
|
||||||
|
m := map[Alias]string{}
|
||||||
|
for _, i := range is {
|
||||||
|
s, err := datasize(i.DataSize)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, alt := range []string{i.IntelOpcode(), i.GNUOpcode()} {
|
||||||
|
if strings.ToUpper(alt) != i.GoOpcode() {
|
||||||
|
m[Alias{Opcode: strings.ToLower(alt), DataSize: s}] = i.GoOpcode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func datasize(s string) (int, error) {
|
||||||
|
if s == "" {
|
||||||
|
return 0, nil
|
||||||
|
}
|
||||||
|
return strconv.Atoi(s)
|
||||||
|
}
|
||||||
19
internal/opcodescsv/io.go
Normal file
19
internal/opcodescsv/io.go
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
package opcodescsv
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"golang.org/x/arch/x86/x86csv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ReadFile reads the given x86 CSV file.
|
||||||
|
func ReadFile(filename string) ([]*x86csv.Inst, error) {
|
||||||
|
f, err := os.Open(filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
r := x86csv.NewReader(f)
|
||||||
|
return r.ReadAll()
|
||||||
|
}
|
||||||
69
internal/test/utils.go
Normal file
69
internal/test/utils.go
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
package test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Assembles asserts that the given assembly code passes the go assembler.
|
||||||
|
func Assembles(t *testing.T, asm []byte) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
dir, clean := TempDir(t)
|
||||||
|
defer clean()
|
||||||
|
|
||||||
|
asmfilename := filepath.Join(dir, "asm.s")
|
||||||
|
if err := ioutil.WriteFile(asmfilename, asm, 0600); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
objfilename := filepath.Join(dir, "asm.o")
|
||||||
|
|
||||||
|
goexec(t, "tool", "asm", "-e", "-o", objfilename, asmfilename)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TempDir creates a temp directory. Returns the path to the directory and a
|
||||||
|
// cleanup function.
|
||||||
|
func TempDir(t *testing.T) (string, func()) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
dir, err := ioutil.TempDir("", "avo")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return dir, func() {
|
||||||
|
if err := os.RemoveAll(dir); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// gobin returns a best guess path to the "go" binary.
|
||||||
|
func gobin() string {
|
||||||
|
var exeSuffix string
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
exeSuffix = ".exe"
|
||||||
|
}
|
||||||
|
path := filepath.Join(runtime.GOROOT(), "bin", "go"+exeSuffix)
|
||||||
|
if _, err := os.Stat(path); err == nil {
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
return "go"
|
||||||
|
}
|
||||||
|
|
||||||
|
// goexec runs a "go" command and checks the output.
|
||||||
|
func goexec(t *testing.T, arg ...string) {
|
||||||
|
t.Helper()
|
||||||
|
cmd := exec.Command(gobin(), arg...)
|
||||||
|
t.Logf("exec: %s", cmd.Args)
|
||||||
|
b, err := cmd.CombinedOutput()
|
||||||
|
t.Logf("output:\n%s\n", string(b))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user