@@ -7,14 +7,14 @@ import (
7
7
"io"
8
8
"net"
9
9
"net/http"
10
- "os"
11
- "path/filepath"
12
10
"regexp"
13
11
"strings"
14
12
"time"
15
13
16
14
"github.com/ethereum/go-ethereum/common"
17
15
"github.com/ethereum/hive/hivesim"
16
+ "github.com/tidwall/gjson"
17
+ "github.com/tidwall/sjson"
18
18
diff "github.com/yudai/gojsondiff"
19
19
"github.com/yudai/gojsondiff/formatter"
20
20
)
26
26
}
27
27
)
28
28
29
- type test struct {
30
- Name string
31
- Data []byte
32
- }
33
-
34
29
func main () {
35
30
// Load fork environment.
36
31
var clientEnv hivesim.Params
@@ -66,63 +61,71 @@ conformance with the execution API specification.`[1:],
66
61
func runAllTests (t * hivesim.T , c * hivesim.Client , clientName string ) {
67
62
_ , testPattern := t .Sim .TestPattern ()
68
63
re := regexp .MustCompile (testPattern )
69
-
70
64
tests := loadTests (t , "tests" , re )
71
65
for _ , test := range tests {
66
+ test := test
72
67
t .Run (hivesim.TestSpec {
73
- Name : fmt .Sprintf ("%s (%s)" , test .Name , clientName ),
68
+ Name : fmt .Sprintf ("%s (%s)" , test .name , clientName ),
69
+ Description : test .comment ,
74
70
Run : func (t * hivesim.T ) {
75
- if err := runTest (t , c , test . Data ); err != nil {
71
+ if err := runTest (t , c , & test ); err != nil {
76
72
t .Fatal (err )
77
73
}
78
74
},
79
75
})
80
76
}
81
77
}
82
78
83
- func runTest (t * hivesim.T , c * hivesim.Client , data [] byte ) error {
79
+ func runTest (t * hivesim.T , c * hivesim.Client , test * rpcTest ) error {
84
80
var (
85
- client = & http.Client {
86
- Timeout : 5 * time .Second ,
87
- Transport : & loggingRoundTrip {
88
- t : t ,
89
- inner : http .DefaultTransport ,
90
- },
91
- }
92
- url = fmt .Sprintf ("http://%s" , net .JoinHostPort (c .IP .String (), "8545" ))
93
- err error
94
- resp []byte
81
+ client = & http.Client {Timeout : 5 * time .Second }
82
+ url = fmt .Sprintf ("http://%s" , net .JoinHostPort (c .IP .String (), "8545" ))
83
+ err error
84
+ respBytes []byte
95
85
)
96
86
97
- for _ , line := range strings .Split (string (data ), "\n " ) {
98
- line = strings .TrimSpace (line )
99
- switch {
100
- case len (line ) == 0 || strings .HasPrefix (line , "//" ):
101
- // Skip comments, blank lines.
102
- continue
103
- case strings .HasPrefix (line , ">> " ):
87
+ for _ , msg := range test .messages {
88
+ if msg .send {
104
89
// Send request.
105
- resp , err = postHttp (client , url , []byte (line [3 :]))
90
+ t .Log (">> " , msg .data )
91
+ respBytes , err = postHttp (client , url , strings .NewReader (msg .data ))
106
92
if err != nil {
107
93
return err
108
94
}
109
- case strings .HasPrefix (line , "<< " ):
110
- // Read response. Unmarshal to interface{} to verify deep equality. Marshal
111
- // again to remove padding differences and to print each field in the same
112
- // order. This makes it easy to spot any discrepancies.
113
- if resp == nil {
95
+ } else {
96
+ // Receive a response.
97
+ if respBytes == nil {
114
98
return fmt .Errorf ("invalid test, response before request" )
115
99
}
116
- want := []byte (strings .TrimSpace (line )[3 :]) // trim leading "<< "
117
- // Now compare.
118
- d , err := diff .New ().Compare (resp , want )
100
+ expectedData := msg .data
101
+ resp := string (bytes .TrimSpace (respBytes ))
102
+ t .Log ("<< " , resp )
103
+ if ! gjson .Valid (resp ) {
104
+ return fmt .Errorf ("invalid JSON response" )
105
+ }
106
+
107
+ // Patch JSON to remove error messages. We only do this in the specific case
108
+ // where an error is expected AND returned by the client.
109
+ var errorRedacted bool
110
+ if gjson .Get (resp , "error" ).Exists () && gjson .Get (expectedData , "error" ).Exists () {
111
+ resp , _ = sjson .Delete (resp , "error.message" )
112
+ expectedData , _ = sjson .Delete (expectedData , "error.message" )
113
+ errorRedacted = true
114
+ }
115
+
116
+ // Compare responses.
117
+ d , err := diff .New ().Compare ([]byte (resp ), []byte (expectedData ))
119
118
if err != nil {
120
119
return fmt .Errorf ("failed to unmarshal value: %s\n " , err )
121
120
}
121
+
122
122
// If there is a discrepancy, return error.
123
123
if d .Modified () {
124
- var got map [string ]interface {}
125
- json .Unmarshal (resp , & got )
124
+ if errorRedacted {
125
+ t .Log ("note: error messages removed from comparison" )
126
+ }
127
+ var got map [string ]any
128
+ json .Unmarshal ([]byte (resp ), & got )
126
129
config := formatter.AsciiFormatterConfig {
127
130
ShowArrayIndex : true ,
128
131
Coloring : false ,
@@ -131,22 +134,20 @@ func runTest(t *hivesim.T, c *hivesim.Client, data []byte) error {
131
134
diffString , _ := formatter .Format (d )
132
135
return fmt .Errorf ("response differs from expected (-- client, ++ test):\n %s" , diffString )
133
136
}
134
- resp = nil
135
- default :
136
- t .Fatalf ("invalid line in test script: %s" , line )
137
+ respBytes = nil
137
138
}
138
139
}
139
- if resp != nil {
140
+
141
+ if respBytes != nil {
140
142
t .Fatalf ("unhandled response in test case" )
141
143
}
142
144
return nil
143
145
}
144
146
145
147
// sendHttp sends an HTTP POST with the provided json data and reads the
146
148
// response into a byte slice and returns it.
147
- func postHttp (c * http.Client , url string , d []byte ) ([]byte , error ) {
148
- data := bytes .NewBuffer (d )
149
- req , err := http .NewRequest ("POST" , url , data )
149
+ func postHttp (c * http.Client , url string , d io.Reader ) ([]byte , error ) {
150
+ req , err := http .NewRequest ("POST" , url , d )
150
151
if err != nil {
151
152
return nil , fmt .Errorf ("error building request: %v" , err )
152
153
}
@@ -158,35 +159,7 @@ func postHttp(c *http.Client, url string, d []byte) ([]byte, error) {
158
159
return io .ReadAll (resp .Body )
159
160
}
160
161
161
- // loadTests walks the given directory looking for *.io files to load.
162
- func loadTests (t * hivesim.T , root string , re * regexp.Regexp ) []test {
163
- tests := make ([]test , 0 )
164
- filepath .Walk (root , func (path string , info os.FileInfo , err error ) error {
165
- if err != nil {
166
- t .Logf ("unable to walk path: %s" , err )
167
- return err
168
- }
169
- if info .IsDir () {
170
- return nil
171
- }
172
- if fname := info .Name (); ! strings .HasSuffix (fname , ".io" ) {
173
- return nil
174
- }
175
- pathname := strings .TrimSuffix (strings .TrimPrefix (path , root ), ".io" )
176
- if ! re .MatchString (pathname ) {
177
- fmt .Println ("skip" , pathname )
178
- return nil // skip
179
- }
180
- data , err := os .ReadFile (path )
181
- if err != nil {
182
- return err
183
- }
184
- tests = append (tests , test {strings .TrimLeft (pathname , "/" ), data })
185
- return nil
186
- })
187
- return tests
188
- }
189
-
162
+ // sendForkchoiceUpdated delivers the initial FcU request to the client.
190
163
func sendForkchoiceUpdated (t * hivesim.T , client * hivesim.Client ) {
191
164
var request struct {
192
165
Method string
@@ -195,43 +168,11 @@ func sendForkchoiceUpdated(t *hivesim.T, client *hivesim.Client) {
195
168
if err := common .LoadJSON ("tests/headfcu.json" , & request ); err != nil {
196
169
t .Fatal ("error loading forkchoiceUpdated:" , err )
197
170
}
198
- err := client .EngineAPI ().Call (nil , request .Method , request .Params ... )
171
+ t .Logf ("sending %s: %v" , request .Method , request .Params )
172
+ var resp any
173
+ err := client .EngineAPI ().Call (& resp , request .Method , request .Params ... )
199
174
if err != nil {
200
175
t .Fatal ("client rejected forkchoiceUpdated:" , err )
201
176
}
202
- }
203
-
204
- // loggingRoundTrip writes requests and responses to the test log.
205
- type loggingRoundTrip struct {
206
- t * hivesim.T
207
- inner http.RoundTripper
208
- }
209
-
210
- func (rt * loggingRoundTrip ) RoundTrip (req * http.Request ) (* http.Response , error ) {
211
- // Read and log the request body.
212
- reqBytes , err := io .ReadAll (req .Body )
213
- req .Body .Close ()
214
- if err != nil {
215
- return nil , err
216
- }
217
- rt .t .Logf (">> %s" , bytes .TrimSpace (reqBytes ))
218
- reqCopy := * req
219
- reqCopy .Body = io .NopCloser (bytes .NewReader (reqBytes ))
220
-
221
- // Do the round trip.
222
- resp , err := rt .inner .RoundTrip (& reqCopy )
223
- if err != nil {
224
- return nil , err
225
- }
226
- defer resp .Body .Close ()
227
-
228
- // Read and log the response bytes.
229
- respBytes , err := io .ReadAll (resp .Body )
230
- if err != nil {
231
- return nil , err
232
- }
233
- respCopy := * resp
234
- respCopy .Body = io .NopCloser (bytes .NewReader (respBytes ))
235
- rt .t .Logf ("<< %s" , bytes .TrimSpace (respBytes ))
236
- return & respCopy , nil
177
+ t .Logf ("response: %v" , resp )
237
178
}
0 commit comments