package main import ( "encoding/json" "fmt" "os" "reflect" "strings" "testing" "github.com/docker/docker/pkg/reexec" "github.com/ethereum/go-ethereum/cmd/evm/internal/t8ntool" "github.com/ethereum/go-ethereum/internal/cmdtest" ) 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) { 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, }, { // Ommer 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", }, } { args := []string{"t8n"} args = append(args, tc.output.get()...) args = append(args, tc.input.get(tc.base)...) tt.Run("evm-test", args...) tt.Logf("args: %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.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 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) { 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.Logf(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 }