5adc314817
Changelog: https://golangci-lint.run/product/changelog/#1610 Removes `exportloopref` (no longer needed), replaces it with `copyloopvar` which is basically the opposite. Also adds: - `durationcheck` - `gocheckcompilerdirectives` - `reassign` - `mirror` - `tenv` --------- Co-authored-by: Marius van der Wijden <m.vanderwijden@live.de>
686 lines
17 KiB
Go
686 lines
17 KiB
Go
// Copyright 2021 The go-ethereum Authors
|
|
// This file is part of go-ethereum.
|
|
//
|
|
// go-ethereum is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// go-ethereum is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
package main
|
|
|
|
import (
|
|
"bufio"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/ethereum/go-ethereum/cmd/evm/internal/t8ntool"
|
|
"github.com/ethereum/go-ethereum/internal/cmdtest"
|
|
"github.com/ethereum/go-ethereum/internal/reexec"
|
|
)
|
|
|
|
func TestMain(m *testing.M) {
|
|
// Run the app if we've been exec'd as "ethkey-test" in runEthkey.
|
|
reexec.Register("evm-test", func() {
|
|
if err := app.Run(os.Args); err != nil {
|
|
fmt.Fprintln(os.Stderr, err)
|
|
os.Exit(1)
|
|
}
|
|
os.Exit(0)
|
|
})
|
|
// check if we have been reexec'd
|
|
if reexec.Init() {
|
|
return
|
|
}
|
|
os.Exit(m.Run())
|
|
}
|
|
|
|
type testT8n struct {
|
|
*cmdtest.TestCmd
|
|
}
|
|
|
|
type t8nInput struct {
|
|
inAlloc string
|
|
inTxs string
|
|
inEnv string
|
|
stFork string
|
|
stReward string
|
|
}
|
|
|
|
func (args *t8nInput) get(base string) []string {
|
|
var out []string
|
|
if opt := args.inAlloc; opt != "" {
|
|
out = append(out, "--input.alloc")
|
|
out = append(out, fmt.Sprintf("%v/%v", base, opt))
|
|
}
|
|
if opt := args.inTxs; opt != "" {
|
|
out = append(out, "--input.txs")
|
|
out = append(out, fmt.Sprintf("%v/%v", base, opt))
|
|
}
|
|
if opt := args.inEnv; opt != "" {
|
|
out = append(out, "--input.env")
|
|
out = append(out, fmt.Sprintf("%v/%v", base, opt))
|
|
}
|
|
if opt := args.stFork; opt != "" {
|
|
out = append(out, "--state.fork", opt)
|
|
}
|
|
if opt := args.stReward; opt != "" {
|
|
out = append(out, "--state.reward", opt)
|
|
}
|
|
return out
|
|
}
|
|
|
|
type t8nOutput struct {
|
|
alloc bool
|
|
result bool
|
|
body bool
|
|
}
|
|
|
|
func (args *t8nOutput) get() (out []string) {
|
|
if args.body {
|
|
out = append(out, "--output.body", "stdout")
|
|
} else {
|
|
out = append(out, "--output.body", "") // empty means ignore
|
|
}
|
|
if args.result {
|
|
out = append(out, "--output.result", "stdout")
|
|
} else {
|
|
out = append(out, "--output.result", "")
|
|
}
|
|
if args.alloc {
|
|
out = append(out, "--output.alloc", "stdout")
|
|
} else {
|
|
out = append(out, "--output.alloc", "")
|
|
}
|
|
return out
|
|
}
|
|
|
|
func TestT8n(t *testing.T) {
|
|
t.Parallel()
|
|
tt := new(testT8n)
|
|
tt.TestCmd = cmdtest.NewTestCmd(t, tt)
|
|
for i, tc := range []struct {
|
|
base string
|
|
input t8nInput
|
|
output t8nOutput
|
|
expExitCode int
|
|
expOut string
|
|
}{
|
|
{ // Test exit (3) on bad config
|
|
base: "./testdata/1",
|
|
input: t8nInput{
|
|
"alloc.json", "txs.json", "env.json", "Frontier+1346", "",
|
|
},
|
|
output: t8nOutput{alloc: true, result: true},
|
|
expExitCode: 3,
|
|
},
|
|
{
|
|
base: "./testdata/1",
|
|
input: t8nInput{
|
|
"alloc.json", "txs.json", "env.json", "Byzantium", "",
|
|
},
|
|
output: t8nOutput{alloc: true, result: true},
|
|
expOut: "exp.json",
|
|
},
|
|
{ // blockhash test
|
|
base: "./testdata/3",
|
|
input: t8nInput{
|
|
"alloc.json", "txs.json", "env.json", "Berlin", "",
|
|
},
|
|
output: t8nOutput{alloc: true, result: true},
|
|
expOut: "exp.json",
|
|
},
|
|
{ // missing blockhash test
|
|
base: "./testdata/4",
|
|
input: t8nInput{
|
|
"alloc.json", "txs.json", "env.json", "Berlin", "",
|
|
},
|
|
output: t8nOutput{alloc: true, result: true},
|
|
expExitCode: 4,
|
|
},
|
|
{ // Uncle test
|
|
base: "./testdata/5",
|
|
input: t8nInput{
|
|
"alloc.json", "txs.json", "env.json", "Byzantium", "0x80",
|
|
},
|
|
output: t8nOutput{alloc: true, result: true},
|
|
expOut: "exp.json",
|
|
},
|
|
{ // Sign json transactions
|
|
base: "./testdata/13",
|
|
input: t8nInput{
|
|
"alloc.json", "txs.json", "env.json", "London", "",
|
|
},
|
|
output: t8nOutput{body: true},
|
|
expOut: "exp.json",
|
|
},
|
|
{ // Already signed transactions
|
|
base: "./testdata/13",
|
|
input: t8nInput{
|
|
"alloc.json", "signed_txs.rlp", "env.json", "London", "",
|
|
},
|
|
output: t8nOutput{result: true},
|
|
expOut: "exp2.json",
|
|
},
|
|
{ // Difficulty calculation - no uncles
|
|
base: "./testdata/14",
|
|
input: t8nInput{
|
|
"alloc.json", "txs.json", "env.json", "London", "",
|
|
},
|
|
output: t8nOutput{result: true},
|
|
expOut: "exp.json",
|
|
},
|
|
{ // Difficulty calculation - with uncles
|
|
base: "./testdata/14",
|
|
input: t8nInput{
|
|
"alloc.json", "txs.json", "env.uncles.json", "London", "",
|
|
},
|
|
output: t8nOutput{result: true},
|
|
expOut: "exp2.json",
|
|
},
|
|
{ // Difficulty calculation - with ommers + Berlin
|
|
base: "./testdata/14",
|
|
input: t8nInput{
|
|
"alloc.json", "txs.json", "env.uncles.json", "Berlin", "",
|
|
},
|
|
output: t8nOutput{result: true},
|
|
expOut: "exp_berlin.json",
|
|
},
|
|
{ // Difficulty calculation on arrow glacier
|
|
base: "./testdata/19",
|
|
input: t8nInput{
|
|
"alloc.json", "txs.json", "env.json", "London", "",
|
|
},
|
|
output: t8nOutput{result: true},
|
|
expOut: "exp_london.json",
|
|
},
|
|
{ // Difficulty calculation on arrow glacier
|
|
base: "./testdata/19",
|
|
input: t8nInput{
|
|
"alloc.json", "txs.json", "env.json", "ArrowGlacier", "",
|
|
},
|
|
output: t8nOutput{result: true},
|
|
expOut: "exp_arrowglacier.json",
|
|
},
|
|
{ // Difficulty calculation on gray glacier
|
|
base: "./testdata/19",
|
|
input: t8nInput{
|
|
"alloc.json", "txs.json", "env.json", "GrayGlacier", "",
|
|
},
|
|
output: t8nOutput{result: true},
|
|
expOut: "exp_grayglacier.json",
|
|
},
|
|
{ // Sign unprotected (pre-EIP155) transaction
|
|
base: "./testdata/23",
|
|
input: t8nInput{
|
|
"alloc.json", "txs.json", "env.json", "Berlin", "",
|
|
},
|
|
output: t8nOutput{result: true},
|
|
expOut: "exp.json",
|
|
},
|
|
{ // Test post-merge transition
|
|
base: "./testdata/24",
|
|
input: t8nInput{
|
|
"alloc.json", "txs.json", "env.json", "Paris", "",
|
|
},
|
|
output: t8nOutput{alloc: true, result: true},
|
|
expOut: "exp.json",
|
|
},
|
|
{ // Test post-merge transition where input is missing random
|
|
base: "./testdata/24",
|
|
input: t8nInput{
|
|
"alloc.json", "txs.json", "env-missingrandom.json", "Paris", "",
|
|
},
|
|
output: t8nOutput{alloc: false, result: false},
|
|
expExitCode: 3,
|
|
},
|
|
{ // Test base fee calculation
|
|
base: "./testdata/25",
|
|
input: t8nInput{
|
|
"alloc.json", "txs.json", "env.json", "Paris", "",
|
|
},
|
|
output: t8nOutput{alloc: true, result: true},
|
|
expOut: "exp.json",
|
|
},
|
|
{ // Test withdrawals transition
|
|
base: "./testdata/26",
|
|
input: t8nInput{
|
|
"alloc.json", "txs.json", "env.json", "Shanghai", "",
|
|
},
|
|
output: t8nOutput{alloc: true, result: true},
|
|
expOut: "exp.json",
|
|
},
|
|
{ // Cancun tests
|
|
base: "./testdata/28",
|
|
input: t8nInput{
|
|
"alloc.json", "txs.rlp", "env.json", "Cancun", "",
|
|
},
|
|
output: t8nOutput{alloc: true, result: true},
|
|
expOut: "exp.json",
|
|
},
|
|
{ // More cancun tests
|
|
base: "./testdata/29",
|
|
input: t8nInput{
|
|
"alloc.json", "txs.json", "env.json", "Cancun", "",
|
|
},
|
|
output: t8nOutput{alloc: true, result: true},
|
|
expOut: "exp.json",
|
|
},
|
|
{ // More cancun test, plus example of rlp-transaction that cannot be decoded properly
|
|
base: "./testdata/30",
|
|
input: t8nInput{
|
|
"alloc.json", "txs_more.rlp", "env.json", "Cancun", "",
|
|
},
|
|
output: t8nOutput{alloc: true, result: true},
|
|
expOut: "exp.json",
|
|
},
|
|
} {
|
|
args := []string{"t8n"}
|
|
args = append(args, tc.output.get()...)
|
|
args = append(args, tc.input.get(tc.base)...)
|
|
var qArgs []string // quoted args for debugging purposes
|
|
for _, arg := range args {
|
|
if len(arg) == 0 {
|
|
qArgs = append(qArgs, `""`)
|
|
} else {
|
|
qArgs = append(qArgs, arg)
|
|
}
|
|
}
|
|
tt.Logf("args: %v\n", strings.Join(qArgs, " "))
|
|
tt.Run("evm-test", args...)
|
|
// Compare the expected output, if provided
|
|
if tc.expOut != "" {
|
|
file := fmt.Sprintf("%v/%v", tc.base, tc.expOut)
|
|
want, err := os.ReadFile(file)
|
|
if err != nil {
|
|
t.Fatalf("test %d: could not read expected output: %v", i, err)
|
|
}
|
|
have := tt.Output()
|
|
ok, err := cmpJson(have, want)
|
|
switch {
|
|
case err != nil:
|
|
t.Fatalf("test %d, file %v: json parsing failed: %v", i, file, err)
|
|
case !ok:
|
|
t.Fatalf("test %d, file %v: output wrong, have \n%v\nwant\n%v\n", i, file, string(have), string(want))
|
|
}
|
|
}
|
|
tt.WaitExit()
|
|
if have, want := tt.ExitStatus(), tc.expExitCode; have != want {
|
|
t.Fatalf("test %d: wrong exit code, have %d, want %d", i, have, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func lineIterator(path string) func() (string, error) {
|
|
data, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return func() (string, error) { return err.Error(), err }
|
|
}
|
|
scanner := bufio.NewScanner(strings.NewReader(string(data)))
|
|
return func() (string, error) {
|
|
if scanner.Scan() {
|
|
return scanner.Text(), nil
|
|
}
|
|
if err := scanner.Err(); err != nil {
|
|
return "", err
|
|
}
|
|
return "", io.EOF // scanner gobbles io.EOF, but we want it
|
|
}
|
|
}
|
|
|
|
// TestT8nTracing is a test that checks the tracing-output from t8n.
|
|
func TestT8nTracing(t *testing.T) {
|
|
t.Parallel()
|
|
tt := new(testT8n)
|
|
tt.TestCmd = cmdtest.NewTestCmd(t, tt)
|
|
for i, tc := range []struct {
|
|
base string
|
|
input t8nInput
|
|
expExitCode int
|
|
extraArgs []string
|
|
expectedTraces []string
|
|
}{
|
|
{
|
|
base: "./testdata/31",
|
|
input: t8nInput{
|
|
"alloc.json", "txs.json", "env.json", "Cancun", "",
|
|
},
|
|
extraArgs: []string{"--trace"},
|
|
expectedTraces: []string{"trace-0-0x88f5fbd1524731a81e49f637aa847543268a5aaf2a6b32a69d2c6d978c45dcfb.jsonl"},
|
|
},
|
|
{
|
|
base: "./testdata/31",
|
|
input: t8nInput{
|
|
"alloc.json", "txs.json", "env.json", "Cancun", "",
|
|
},
|
|
extraArgs: []string{"--trace.tracer", `
|
|
{
|
|
result: function(){
|
|
return "hello world"
|
|
},
|
|
fault: function(){}
|
|
}`},
|
|
expectedTraces: []string{"trace-0-0x88f5fbd1524731a81e49f637aa847543268a5aaf2a6b32a69d2c6d978c45dcfb.json"},
|
|
},
|
|
{
|
|
base: "./testdata/32",
|
|
input: t8nInput{
|
|
"alloc.json", "txs.json", "env.json", "Paris", "",
|
|
},
|
|
extraArgs: []string{"--trace", "--trace.callframes"},
|
|
expectedTraces: []string{"trace-0-0x47806361c0fa084be3caa18afe8c48156747c01dbdfc1ee11b5aecdbe4fcf23e.jsonl"},
|
|
},
|
|
} {
|
|
args := []string{"t8n"}
|
|
args = append(args, tc.input.get(tc.base)...)
|
|
// Place the output somewhere we can find it
|
|
outdir := t.TempDir()
|
|
args = append(args, "--output.basedir", outdir)
|
|
args = append(args, tc.extraArgs...)
|
|
|
|
var qArgs []string // quoted args for debugging purposes
|
|
for _, arg := range args {
|
|
if len(arg) == 0 {
|
|
qArgs = append(qArgs, `""`)
|
|
} else {
|
|
qArgs = append(qArgs, arg)
|
|
}
|
|
}
|
|
tt.Logf("args: %v\n", strings.Join(qArgs, " "))
|
|
tt.Run("evm-test", args...)
|
|
t.Log(string(tt.Output()))
|
|
|
|
// Compare the expected traces
|
|
for _, traceFile := range tc.expectedTraces {
|
|
haveFn := lineIterator(filepath.Join(outdir, traceFile))
|
|
wantFn := lineIterator(filepath.Join(tc.base, traceFile))
|
|
|
|
for line := 0; ; line++ {
|
|
want, wErr := wantFn()
|
|
have, hErr := haveFn()
|
|
if want != have {
|
|
t.Fatalf("test %d, trace %v, line %d\nwant: %v\nhave: %v\n",
|
|
i, traceFile, line, want, have)
|
|
}
|
|
if wErr != nil && hErr != nil {
|
|
break
|
|
}
|
|
if wErr != nil {
|
|
t.Fatal(wErr)
|
|
}
|
|
if hErr != nil {
|
|
t.Fatal(hErr)
|
|
}
|
|
t.Logf("%v\n", want)
|
|
}
|
|
}
|
|
if have, want := tt.ExitStatus(), tc.expExitCode; have != want {
|
|
t.Fatalf("test %d: wrong exit code, have %d, want %d", i, have, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
type t9nInput struct {
|
|
inTxs string
|
|
stFork string
|
|
}
|
|
|
|
func (args *t9nInput) get(base string) []string {
|
|
var out []string
|
|
if opt := args.inTxs; opt != "" {
|
|
out = append(out, "--input.txs")
|
|
out = append(out, fmt.Sprintf("%v/%v", base, opt))
|
|
}
|
|
if opt := args.stFork; opt != "" {
|
|
out = append(out, "--state.fork", opt)
|
|
}
|
|
return out
|
|
}
|
|
|
|
func TestT9n(t *testing.T) {
|
|
t.Parallel()
|
|
tt := new(testT8n)
|
|
tt.TestCmd = cmdtest.NewTestCmd(t, tt)
|
|
for i, tc := range []struct {
|
|
base string
|
|
input t9nInput
|
|
expExitCode int
|
|
expOut string
|
|
}{
|
|
{ // London txs on homestead
|
|
base: "./testdata/15",
|
|
input: t9nInput{
|
|
inTxs: "signed_txs.rlp",
|
|
stFork: "Homestead",
|
|
},
|
|
expOut: "exp.json",
|
|
},
|
|
{ // London txs on London
|
|
base: "./testdata/15",
|
|
input: t9nInput{
|
|
inTxs: "signed_txs.rlp",
|
|
stFork: "London",
|
|
},
|
|
expOut: "exp2.json",
|
|
},
|
|
{ // An RLP list (a blockheader really)
|
|
base: "./testdata/15",
|
|
input: t9nInput{
|
|
inTxs: "blockheader.rlp",
|
|
stFork: "London",
|
|
},
|
|
expOut: "exp3.json",
|
|
},
|
|
{ // Transactions with too low gas
|
|
base: "./testdata/16",
|
|
input: t9nInput{
|
|
inTxs: "signed_txs.rlp",
|
|
stFork: "London",
|
|
},
|
|
expOut: "exp.json",
|
|
},
|
|
{ // Transactions with value exceeding 256 bits
|
|
base: "./testdata/17",
|
|
input: t9nInput{
|
|
inTxs: "signed_txs.rlp",
|
|
stFork: "London",
|
|
},
|
|
expOut: "exp.json",
|
|
},
|
|
{ // Invalid RLP
|
|
base: "./testdata/18",
|
|
input: t9nInput{
|
|
inTxs: "invalid.rlp",
|
|
stFork: "London",
|
|
},
|
|
expExitCode: t8ntool.ErrorIO,
|
|
},
|
|
} {
|
|
args := []string{"t9n"}
|
|
args = append(args, tc.input.get(tc.base)...)
|
|
|
|
tt.Run("evm-test", args...)
|
|
tt.Logf("args:\n go run . %v\n", strings.Join(args, " "))
|
|
// Compare the expected output, if provided
|
|
if tc.expOut != "" {
|
|
want, err := os.ReadFile(fmt.Sprintf("%v/%v", tc.base, tc.expOut))
|
|
if err != nil {
|
|
t.Fatalf("test %d: could not read expected output: %v", i, err)
|
|
}
|
|
have := tt.Output()
|
|
ok, err := cmpJson(have, want)
|
|
switch {
|
|
case err != nil:
|
|
t.Log(string(have))
|
|
t.Fatalf("test %d, json parsing failed: %v", i, err)
|
|
case !ok:
|
|
t.Fatalf("test %d: output wrong, have \n%v\nwant\n%v\n", i, string(have), string(want))
|
|
}
|
|
}
|
|
tt.WaitExit()
|
|
if have, want := tt.ExitStatus(), tc.expExitCode; have != want {
|
|
t.Fatalf("test %d: wrong exit code, have %d, want %d", i, have, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
type b11rInput struct {
|
|
inEnv string
|
|
inOmmersRlp string
|
|
inWithdrawals string
|
|
inTxsRlp string
|
|
inClique string
|
|
ethash bool
|
|
ethashMode string
|
|
ethashDir string
|
|
}
|
|
|
|
func (args *b11rInput) get(base string) []string {
|
|
var out []string
|
|
if opt := args.inEnv; opt != "" {
|
|
out = append(out, "--input.header")
|
|
out = append(out, fmt.Sprintf("%v/%v", base, opt))
|
|
}
|
|
if opt := args.inOmmersRlp; opt != "" {
|
|
out = append(out, "--input.ommers")
|
|
out = append(out, fmt.Sprintf("%v/%v", base, opt))
|
|
}
|
|
if opt := args.inWithdrawals; opt != "" {
|
|
out = append(out, "--input.withdrawals")
|
|
out = append(out, fmt.Sprintf("%v/%v", base, opt))
|
|
}
|
|
if opt := args.inTxsRlp; opt != "" {
|
|
out = append(out, "--input.txs")
|
|
out = append(out, fmt.Sprintf("%v/%v", base, opt))
|
|
}
|
|
if opt := args.inClique; opt != "" {
|
|
out = append(out, "--seal.clique")
|
|
out = append(out, fmt.Sprintf("%v/%v", base, opt))
|
|
}
|
|
if args.ethash {
|
|
out = append(out, "--seal.ethash")
|
|
}
|
|
if opt := args.ethashMode; opt != "" {
|
|
out = append(out, "--seal.ethash.mode")
|
|
out = append(out, fmt.Sprintf("%v/%v", base, opt))
|
|
}
|
|
if opt := args.ethashDir; opt != "" {
|
|
out = append(out, "--seal.ethash.dir")
|
|
out = append(out, fmt.Sprintf("%v/%v", base, opt))
|
|
}
|
|
out = append(out, "--output.block")
|
|
out = append(out, "stdout")
|
|
return out
|
|
}
|
|
|
|
func TestB11r(t *testing.T) {
|
|
t.Parallel()
|
|
tt := new(testT8n)
|
|
tt.TestCmd = cmdtest.NewTestCmd(t, tt)
|
|
for i, tc := range []struct {
|
|
base string
|
|
input b11rInput
|
|
expExitCode int
|
|
expOut string
|
|
}{
|
|
{ // unsealed block
|
|
base: "./testdata/20",
|
|
input: b11rInput{
|
|
inEnv: "header.json",
|
|
inOmmersRlp: "ommers.json",
|
|
inTxsRlp: "txs.rlp",
|
|
},
|
|
expOut: "exp.json",
|
|
},
|
|
{ // ethash test seal
|
|
base: "./testdata/21",
|
|
input: b11rInput{
|
|
inEnv: "header.json",
|
|
inOmmersRlp: "ommers.json",
|
|
inTxsRlp: "txs.rlp",
|
|
},
|
|
expOut: "exp.json",
|
|
},
|
|
{ // clique test seal
|
|
base: "./testdata/21",
|
|
input: b11rInput{
|
|
inEnv: "header.json",
|
|
inOmmersRlp: "ommers.json",
|
|
inTxsRlp: "txs.rlp",
|
|
inClique: "clique.json",
|
|
},
|
|
expOut: "exp-clique.json",
|
|
},
|
|
{ // block with ommers
|
|
base: "./testdata/22",
|
|
input: b11rInput{
|
|
inEnv: "header.json",
|
|
inOmmersRlp: "ommers.json",
|
|
inTxsRlp: "txs.rlp",
|
|
},
|
|
expOut: "exp.json",
|
|
},
|
|
{ // block with withdrawals
|
|
base: "./testdata/27",
|
|
input: b11rInput{
|
|
inEnv: "header.json",
|
|
inOmmersRlp: "ommers.json",
|
|
inWithdrawals: "withdrawals.json",
|
|
inTxsRlp: "txs.rlp",
|
|
},
|
|
expOut: "exp.json",
|
|
},
|
|
} {
|
|
args := []string{"b11r"}
|
|
args = append(args, tc.input.get(tc.base)...)
|
|
|
|
tt.Run("evm-test", args...)
|
|
tt.Logf("args:\n go run . %v\n", strings.Join(args, " "))
|
|
// Compare the expected output, if provided
|
|
if tc.expOut != "" {
|
|
want, err := os.ReadFile(fmt.Sprintf("%v/%v", tc.base, tc.expOut))
|
|
if err != nil {
|
|
t.Fatalf("test %d: could not read expected output: %v", i, err)
|
|
}
|
|
have := tt.Output()
|
|
ok, err := cmpJson(have, want)
|
|
switch {
|
|
case err != nil:
|
|
t.Log(string(have))
|
|
t.Fatalf("test %d, json parsing failed: %v", i, err)
|
|
case !ok:
|
|
t.Fatalf("test %d: output wrong, have \n%v\nwant\n%v\n", i, string(have), string(want))
|
|
}
|
|
}
|
|
tt.WaitExit()
|
|
if have, want := tt.ExitStatus(), tc.expExitCode; have != want {
|
|
t.Fatalf("test %d: wrong exit code, have %d, want %d", i, have, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
// cmpJson compares the JSON in two byte slices.
|
|
func cmpJson(a, b []byte) (bool, error) {
|
|
var j, j2 interface{}
|
|
if err := json.Unmarshal(a, &j); err != nil {
|
|
return false, err
|
|
}
|
|
if err := json.Unmarshal(b, &j2); err != nil {
|
|
return false, err
|
|
}
|
|
return reflect.DeepEqual(j2, j), nil
|
|
}
|