1515 ExceptionMessage ,
1616 TransactionException ,
1717)
18- from ethereum_test_fixtures import BlockchainFixture , FixtureFormat , StateFixture
18+ from ethereum_test_fixtures import BlockchainFixture , EOFFixture , FixtureFormat , StateFixture
1919from ethereum_test_forks import Fork
2020
2121from ..ethereum_cli import EthereumCLI
@@ -246,10 +246,88 @@ def is_fork_supported(self, fork: Fork) -> bool:
246246class GethFixtureConsumer (
247247 GethEvm ,
248248 FixtureConsumerTool ,
249- fixture_formats = [StateFixture , BlockchainFixture ],
249+ fixture_formats = [StateFixture , BlockchainFixture , EOFFixture ],
250250):
251251 """Geth's implementation of the fixture consumer."""
252252
253+ # ------------------------------------ state test ---------------------------------------------
254+ @cache # noqa: B019
255+ def consume_state_test_file (
256+ self ,
257+ fixture_path : Path ,
258+ debug_output_path : Optional [Path ] = None ,
259+ ) -> List [Dict [str , Any ]]:
260+ """
261+ Consume an entire state test file.
262+
263+ The `evm statetest` will always execute all the tests contained in a file without the
264+ possibility of selecting a single test, so this function is cached in order to only call
265+ the command once and `consume_state_test` can simply select the result that
266+ was requested.
267+ """
268+ subcommand = "statetest"
269+ global_options : List [str ] = []
270+ subcommand_options : List [str ] = []
271+ if debug_output_path :
272+ global_options += ["--verbosity" , "100" ]
273+ subcommand_options += ["--trace" ]
274+
275+ command = (
276+ [str (self .binary )]
277+ + global_options
278+ + [subcommand ]
279+ + subcommand_options
280+ + [str (fixture_path )]
281+ )
282+ result = self ._run_command (command )
283+
284+ if debug_output_path :
285+ self ._consume_debug_dump (command , result , fixture_path , debug_output_path )
286+
287+ if result .returncode != 0 :
288+ raise Exception (
289+ f"Unexpected exit code:\n { ' ' .join (command )} \n \n Error:\n { result .stderr } "
290+ )
291+
292+ result_json = json .loads (result .stdout )
293+ print ("Output from json.loads(result.stdout):" , result_json )
294+ if not isinstance (result_json , list ):
295+ raise Exception (f"Unexpected result from evm statetest: { result_json } " )
296+ return result_json
297+
298+ def consume_state_test (
299+ self ,
300+ fixture_path : Path ,
301+ fixture_name : Optional [str ] = None ,
302+ debug_output_path : Optional [Path ] = None ,
303+ ):
304+ """
305+ Consume a single state test.
306+
307+ Uses the cached result from `consume_state_test_file` in order to not call the command
308+ every time an select a single result from there.
309+ """
310+ file_results = self .consume_state_test_file (
311+ fixture_path = fixture_path ,
312+ debug_output_path = debug_output_path ,
313+ )
314+ if fixture_name :
315+ test_result = [
316+ test_result for test_result in file_results if test_result ["name" ] == fixture_name
317+ ]
318+ assert len (test_result ) < 2 , f"Multiple test results for { fixture_name } "
319+ assert len (test_result ) == 1 , f"Test result for { fixture_name } missing"
320+ assert test_result [0 ]["pass" ], f"State test failed: { test_result [0 ]['error' ]} "
321+ else :
322+ if any (not test_result ["pass" ] for test_result in file_results ):
323+ exception_text = "State test failed: \n " + "\n " .join (
324+ f"{ test_result ['name' ]} : " + test_result ["error" ]
325+ for test_result in file_results
326+ if not test_result ["pass" ]
327+ )
328+ raise Exception (exception_text )
329+
330+ # ---------------------------------- blockchain test ------------------------------------------
253331 def consume_blockchain_test (
254332 self ,
255333 fixture_path : Path ,
@@ -290,26 +368,71 @@ def consume_blockchain_test(
290368 f"Unexpected exit code:\n { ' ' .join (command )} \n \n Error:\n { result .stderr } "
291369 )
292370
293- @cache # noqa
294- def consume_state_test_file (
371+ # ------------------------------------ eof test -----------------------------------------------
372+ def extract_int_from_subprocess_string (
373+ self , s : str , substring_of_interest_prefix : str
374+ ) -> int | None :
375+ """Hacky method to extract relevant substring from string returned by subprocess execution.""" # noqa: E501
376+ # get everything after this substring occurrence, then remove everything not in [0,9]
377+ relevant_substring = re .sub (r"\D" , "" , s .split ('total executed"=' , 1 )[1 ])
378+ if relevant_substring .isdigit ():
379+ return int (relevant_substring )
380+ else :
381+ return None
382+
383+ def eofparse_subprocess_output_to_dict (
384+ self , process_result : str , fixture_name : str | None , fork_name : str
385+ ) -> Dict :
386+ """Take subprocess output from geth's eofparse, a fixture_name and a fork name and return relevant data as dict.""" # noqa: E501
387+ # remove whitespaces if more than 1 whitespace occurs at a time
388+ process_result_cleaned : str = re .sub (r"\s+" , " " , process_result ).strip ()
389+
390+ # determine amount of total tests
391+ total_tests_amount : int | None = self .extract_int_from_subprocess_string (
392+ process_result_cleaned , 'total executed"='
393+ )
394+
395+ # determine amount of passed tests
396+ passed_tests_amount : int | None = self .extract_int_from_subprocess_string (
397+ process_result_cleaned , "tests passed="
398+ )
399+
400+ # determine if all tests of this test vector passed
401+ all_tests_passed : bool = False
402+ if total_tests_amount is not None and passed_tests_amount is not None :
403+ if total_tests_amount == passed_tests_amount :
404+ all_tests_passed = True
405+
406+ # return relevant data as dict
407+ return {
408+ "name" : fixture_name ,
409+ "pass" : all_tests_passed ,
410+ "stateRoot" : None ,
411+ "fork" : fork_name ,
412+ }
413+
414+ @cache # noqa: B019
415+ def consume_eof_test_file (
295416 self ,
296417 fixture_path : Path ,
418+ fixture_name : Optional [str ] = None ,
297419 debug_output_path : Optional [Path ] = None ,
298420 ) -> List [Dict [str , Any ]]:
299421 """
300- Consume an entire state test file.
422+ Consume an entire EOF validation test file.
301423
302- The `evm statetest` will always execute all the tests contained in a file without the
303- possibility of selecting a single test, so this function is cached in order to only call
304- the command once and `consume_state_test` can simply select the result that
305- was requested.
424+
425+ The `evm eofparse` will always validate all the eof byte codes contained in a file without
426+ the possibility of selecting a single test, so this function is cached in order to only
427+ call the command once and `consume_eof_test` can simply select the result that was
428+ requested.
306429 """
307- subcommand = "statetest "
308- global_options : List [ str ] = []
309- subcommand_options : List [ str ] = []
430+ subcommand = "eofparse "
431+ global_options = []
432+ subcommand_options = ["--test" ]
310433 if debug_output_path :
311434 global_options += ["--verbosity" , "100" ]
312- subcommand_options += [ " --trace" ]
435+ # --trace does not exist for eofparse
313436
314437 command = (
315438 [str (self .binary )]
@@ -318,6 +441,7 @@ def consume_state_test_file(
318441 + subcommand_options
319442 + [str (fixture_path )]
320443 )
444+
321445 result = self ._run_command (command )
322446
323447 if debug_output_path :
@@ -328,25 +452,28 @@ def consume_state_test_file(
328452 f"Unexpected exit code:\n { ' ' .join (command )} \n \n Error:\n { result .stderr } "
329453 )
330454
331- result_json = json .loads (result .stdout )
455+ result_json = [
456+ self .eofparse_subprocess_output_to_dict (result .stderr , fixture_name , "Osaka" )
457+ ]
332458 if not isinstance (result_json , list ):
333- raise Exception (f"Unexpected result from evm statetest : { result_json } " )
459+ raise Exception (f"Unexpected result from evm eofparse : { result_json } " )
334460 return result_json
335461
336- def consume_state_test (
462+ def consume_eof_test (
337463 self ,
338464 fixture_path : Path ,
339465 fixture_name : Optional [str ] = None ,
340466 debug_output_path : Optional [Path ] = None ,
341467 ):
342468 """
343- Consume a single state test.
469+ Consume a single eof test.
344470
345- Uses the cached result from `consume_state_test_file ` in order to not call the command
471+ Uses the cached result from `consume_eof_test_file ` in order to not call the command
346472 every time an select a single result from there.
347473 """
348- file_results = self .consume_state_test_file (
474+ file_results = self .consume_eof_test_file (
349475 fixture_path = fixture_path ,
476+ fixture_name = fixture_name ,
350477 debug_output_path = debug_output_path ,
351478 )
352479 if fixture_name :
@@ -355,10 +482,10 @@ def consume_state_test(
355482 ]
356483 assert len (test_result ) < 2 , f"Multiple test results for { fixture_name } "
357484 assert len (test_result ) == 1 , f"Test result for { fixture_name } missing"
358- assert test_result [0 ]["pass" ], f"State test failed: { test_result [0 ]['error' ]} "
485+ assert test_result [0 ]["pass" ], f"EOF test failed: { test_result [0 ]['error' ]} "
359486 else :
360487 if any (not test_result ["pass" ] for test_result in file_results ):
361- exception_text = "State test failed: \n " + "\n " .join (
488+ exception_text = "EOF test failed: \n " + "\n " .join (
362489 f"{ test_result ['name' ]} : " + test_result ["error" ]
363490 for test_result in file_results
364491 if not test_result ["pass" ]
@@ -385,6 +512,12 @@ def consume_fixture(
385512 fixture_name = fixture_name ,
386513 debug_output_path = debug_output_path ,
387514 )
515+ elif fixture_format == EOFFixture :
516+ self .consume_eof_test (
517+ fixture_path = fixture_path ,
518+ fixture_name = fixture_name ,
519+ debug_output_path = debug_output_path ,
520+ )
388521 else :
389522 raise Exception (
390523 f"Fixture format { fixture_format .format_name } not supported by { self .binary } "
0 commit comments