From f78b5a819aed1559d3f44c0f13f2430173a4fd41 Mon Sep 17 00:00:00 2001 From: spencer-tb Date: Tue, 7 Mar 2023 11:38:09 -0600 Subject: [PATCH 1/9] Feature: add concurrecy while test filling. --- src/ethereum_test_filling_tool/main.py | 97 ++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/src/ethereum_test_filling_tool/main.py b/src/ethereum_test_filling_tool/main.py index f37abc2bee..faddce56ce 100755 --- a/src/ethereum_test_filling_tool/main.py +++ b/src/ethereum_test_filling_tool/main.py @@ -10,6 +10,8 @@ import json import logging import os +import time +import concurrent.futures from pathlib import Path from pkgutil import iter_modules @@ -85,6 +87,12 @@ def parse_arguments() -> argparse.Namespace: help="removes the folder structure from test fixture output", ) + parser.add_argument( + "--benchmark", + action="store_true", + help="logs the timing of the test filler for benchmarking", + ) + return parser.parse_args() options: argparse.Namespace @@ -98,6 +106,9 @@ def fill(self) -> None: """ Fill test fixtures. """ + if self.options.benchmark: + start_time = time.time() + pkg_path = self.options.filler_path fillers = [] @@ -154,6 +165,92 @@ def fill(self) -> None: fixture, f, ensure_ascii=False, indent=4, cls=JSONEncoder ) + if self.options.benchmark: + end_time = time.time() + elapsed_time = end_time - start_time + self.log.info( + f"Filled test fixtures in {elapsed_time:.2f} seconds." + ) + + def parallel_fill(self) -> None: + """ + Parallel Fill test fixtures. + """ + if self.options.benchmark: + start_time = time.time() + + pkg_path = self.options.filler_path + + fillers = [] + + for package_name, module_name, module_loader in find_modules( + os.path.abspath(pkg_path), + self.options.test_categories, + self.options.test_module, + ): + module_full_name = module_loader.name + self.log.debug(f"searching {module_full_name} for fillers") + module = module_loader.load_module() + for obj in module.__dict__.values(): + if callable(obj): + if hasattr(obj, "__filler_metadata__"): + if ( + self.options.test_case + and self.options.test_case + not in obj.__filler_metadata__["name"] + ): + continue + obj.__filler_metadata__["module_path"] = [ + package_name, + module_name, + ] + fillers.append(obj) + + self.log.info(f"collected {len(fillers)} fillers") + + os.makedirs(self.options.output, exist_ok=True) + + t8n = EvmTransitionTool( + binary=self.options.evm_bin, trace=self.options.traces + ) + b11r = EvmBlockBuilder(binary=self.options.evm_bin) + + with concurrent.futures.ThreadPoolExecutor() as executor: + futures = [] + for filler in fillers: + name = filler.__filler_metadata__["name"] + output_dir = os.path.join( + self.options.output, + *(filler.__filler_metadata__["module_path"]) + if not self.options.no_output_structure + else "", + ) + os.makedirs(output_dir, exist_ok=True) + path = os.path.join(output_dir, f"{name}.json") + + name = path[9 : len(path) - 5].replace("/", ".") + self.log.debug(f"filling - {name}") + future = executor.submit(filler, t8n, b11r, "NoProof") + futures.append((future, path)) + + for future, path in futures: + fixture = future.result() + with open(path, "w", encoding="utf-8") as f: + json.dump( + fixture, + f, + ensure_ascii=False, + indent=4, + cls=JSONEncoder, + ) + + if self.options.benchmark: + end_time = time.time() + elapsed_time = end_time - start_time + self.log.info( + f"Filled test fixtures in {elapsed_time:.2f} seconds." + ) + def find_modules(root, include_pkg, include_modules): """ From 0263b25f2b0c6528161d04b1e032e55304a68ce2 Mon Sep 17 00:00:00 2001 From: Spencer Taylor-Brown Date: Tue, 7 Mar 2023 12:57:53 -0600 Subject: [PATCH 2/9] Review Change: Update src/ethereum_test_filling_tool/main.py Co-authored-by: Mario Vega --- src/ethereum_test_filling_tool/main.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/ethereum_test_filling_tool/main.py b/src/ethereum_test_filling_tool/main.py index faddce56ce..d0b89dae1c 100755 --- a/src/ethereum_test_filling_tool/main.py +++ b/src/ethereum_test_filling_tool/main.py @@ -219,17 +219,18 @@ def parallel_fill(self) -> None: futures = [] for filler in fillers: name = filler.__filler_metadata__["name"] + module_path = filler.__filler_metadata__["module_path"] output_dir = os.path.join( self.options.output, - *(filler.__filler_metadata__["module_path"]) + *module_path if not self.options.no_output_structure else "", ) os.makedirs(output_dir, exist_ok=True) path = os.path.join(output_dir, f"{name}.json") - name = path[9 : len(path) - 5].replace("/", ".") - self.log.debug(f"filling - {name}") + full_name = ".".join(module_path + [name]) + self.log.debug(f"filling - {full_name}") future = executor.submit(filler, t8n, b11r, "NoProof") futures.append((future, path)) From e08c253dcb146b283525f4667d2af190187ab91a Mon Sep 17 00:00:00 2001 From: spencer-tb Date: Tue, 7 Mar 2023 13:10:55 -0600 Subject: [PATCH 3/9] Feature: replace fill() with parallel_fill(), add --max-workers argument for serial execution. --- src/ethereum_test_filling_tool/main.py | 89 ++++---------------------- 1 file changed, 14 insertions(+), 75 deletions(-) diff --git a/src/ethereum_test_filling_tool/main.py b/src/ethereum_test_filling_tool/main.py index d0b89dae1c..3fa89172ca 100755 --- a/src/ethereum_test_filling_tool/main.py +++ b/src/ethereum_test_filling_tool/main.py @@ -93,6 +93,13 @@ def parse_arguments() -> argparse.Namespace: help="logs the timing of the test filler for benchmarking", ) + parser.add_argument( + "--max-workers", + type=int, + help="specifies the max number of workers for the test filler \ + set to 1 for serial execution.", + ) + return parser.parse_args() options: argparse.Namespace @@ -145,77 +152,9 @@ def fill(self) -> None: ) b11r = EvmBlockBuilder(binary=self.options.evm_bin) - for filler in fillers: - name = filler.__filler_metadata__["name"] - output_dir = os.path.join( - self.options.output, - *(filler.__filler_metadata__["module_path"]) - if not self.options.no_output_structure - else "", - ) - os.makedirs(output_dir, exist_ok=True) - path = os.path.join(output_dir, f"{name}.json") - - name = path[9 : len(path) - 5].replace("/", ".") - self.log.debug(f"filling - {name}") - fixture = filler(t8n, b11r, "NoProof") - - with open(path, "w", encoding="utf-8") as f: - json.dump( - fixture, f, ensure_ascii=False, indent=4, cls=JSONEncoder - ) - - if self.options.benchmark: - end_time = time.time() - elapsed_time = end_time - start_time - self.log.info( - f"Filled test fixtures in {elapsed_time:.2f} seconds." - ) - - def parallel_fill(self) -> None: - """ - Parallel Fill test fixtures. - """ - if self.options.benchmark: - start_time = time.time() - - pkg_path = self.options.filler_path - - fillers = [] - - for package_name, module_name, module_loader in find_modules( - os.path.abspath(pkg_path), - self.options.test_categories, - self.options.test_module, - ): - module_full_name = module_loader.name - self.log.debug(f"searching {module_full_name} for fillers") - module = module_loader.load_module() - for obj in module.__dict__.values(): - if callable(obj): - if hasattr(obj, "__filler_metadata__"): - if ( - self.options.test_case - and self.options.test_case - not in obj.__filler_metadata__["name"] - ): - continue - obj.__filler_metadata__["module_path"] = [ - package_name, - module_name, - ] - fillers.append(obj) - - self.log.info(f"collected {len(fillers)} fillers") - - os.makedirs(self.options.output, exist_ok=True) - - t8n = EvmTransitionTool( - binary=self.options.evm_bin, trace=self.options.traces - ) - b11r = EvmBlockBuilder(binary=self.options.evm_bin) - - with concurrent.futures.ThreadPoolExecutor() as executor: + with concurrent.futures.ThreadPoolExecutor( + max_workers=self.options.max_workers + ) as executor: futures = [] for filler in fillers: name = filler.__filler_metadata__["name"] @@ -228,14 +167,14 @@ def parallel_fill(self) -> None: ) os.makedirs(output_dir, exist_ok=True) path = os.path.join(output_dir, f"{name}.json") - full_name = ".".join(module_path + [name]) - self.log.debug(f"filling - {full_name}") + future = executor.submit(filler, t8n, b11r, "NoProof") - futures.append((future, path)) + futures.append((future, path, full_name)) - for future, path in futures: + for future, path, full_name in futures: fixture = future.result() + self.log.debug(f"filling - {full_name}") with open(path, "w", encoding="utf-8") as f: json.dump( fixture, From fdfbb5e4315b18f0bcba146fe7cbb7c4a995397b Mon Sep 17 00:00:00 2001 From: spencer-tb Date: Tue, 7 Mar 2023 13:12:37 -0600 Subject: [PATCH 4/9] Small refactor for tox. --- src/ethereum_test_filling_tool/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ethereum_test_filling_tool/main.py b/src/ethereum_test_filling_tool/main.py index 3fa89172ca..e3b843495a 100755 --- a/src/ethereum_test_filling_tool/main.py +++ b/src/ethereum_test_filling_tool/main.py @@ -7,11 +7,11 @@ """ import argparse +import concurrent.futures import json import logging import os import time -import concurrent.futures from pathlib import Path from pkgutil import iter_modules From 76d9a731100bcdffcb3575a2b79fe80179220771 Mon Sep 17 00:00:00 2001 From: spencer-tb Date: Tue, 7 Mar 2023 14:12:13 -0600 Subject: [PATCH 5/9] Feature: skip filling for tests that have already been filled. --- src/ethereum_test_filling_tool/main.py | 27 +++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/src/ethereum_test_filling_tool/main.py b/src/ethereum_test_filling_tool/main.py index e3b843495a..845dc67e13 100755 --- a/src/ethereum_test_filling_tool/main.py +++ b/src/ethereum_test_filling_tool/main.py @@ -36,8 +36,8 @@ def parse_arguments() -> argparse.Namespace: parser.add_argument( "--evm-bin", - help="path to evm executable that provides `t8n` and `b11r` " - + "subcommands", + help="path to evm executable that provides `t8n` and `b11r` \ + subcommands", default=None, type=Path, ) @@ -97,7 +97,13 @@ def parse_arguments() -> argparse.Namespace: "--max-workers", type=int, help="specifies the max number of workers for the test filler \ - set to 1 for serial execution.", + set to 1 for serial execution", + ) + + parser.add_argument( + "--no-skip", + action="store_true", + help="fill all test fillers and don't skip any tests", ) return parser.parse_args() @@ -169,6 +175,13 @@ def fill(self) -> None: path = os.path.join(output_dir, f"{name}.json") full_name = ".".join(module_path + [name]) + if ( + skip_filling(path, pkg_path, module_path) + and not self.options.no_skip + ): + self.log.debug(f"skipping - {full_name}") + continue + future = executor.submit(filler, t8n, b11r, "NoProof") futures.append((future, path, full_name)) @@ -192,6 +205,14 @@ def fill(self) -> None: ) +def skip_filling(path, pkg_path, module_path): + last_modified_time = os.path.getmtime( + os.path.join(pkg_path, *module_path) + ".py" + ) + last_filled_time = os.path.getmtime(path) if os.path.exists(path) else 0 + return last_modified_time <= last_filled_time + + def find_modules(root, include_pkg, include_modules): """ Find modules recursively starting with the `root`. From d792b9fc55f6a9157574a74fad7d97d358c0e00e Mon Sep 17 00:00:00 2001 From: spencer-tb Date: Tue, 7 Mar 2023 14:38:05 -0600 Subject: [PATCH 6/9] Feature: fix for skipping tests, --overwrite argument added. --- src/ethereum_test_filling_tool/main.py | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/ethereum_test_filling_tool/main.py b/src/ethereum_test_filling_tool/main.py index 845dc67e13..4cf50db862 100755 --- a/src/ethereum_test_filling_tool/main.py +++ b/src/ethereum_test_filling_tool/main.py @@ -101,9 +101,10 @@ def parse_arguments() -> argparse.Namespace: ) parser.add_argument( - "--no-skip", + "--overwrite", action="store_true", - help="fill all test fillers and don't skip any tests", + help="fill all test fillers and don't skip any tests \ + overwriting where necessary", ) return parser.parse_args() @@ -176,8 +177,9 @@ def fill(self) -> None: full_name = ".".join(module_path + [name]) if ( - skip_filling(path, pkg_path, module_path) - and not self.options.no_skip + os.path.exists(path) + and not is_module_modified(path, pkg_path, module_path) + and not self.options.overwrite ): self.log.debug(f"skipping - {full_name}") continue @@ -205,12 +207,16 @@ def fill(self) -> None: ) -def skip_filling(path, pkg_path, module_path): - last_modified_time = os.path.getmtime( +def is_module_modified(path, pkg_path, module_path): + """ + Returns True if a module was modified more recently than + it was filled, False otherwise. + """ + modified_time = os.path.getmtime( os.path.join(pkg_path, *module_path) + ".py" ) - last_filled_time = os.path.getmtime(path) if os.path.exists(path) else 0 - return last_modified_time <= last_filled_time + filled_time = os.path.getmtime(path) if os.path.exists(path) else 0 + return modified_time > filled_time def find_modules(root, include_pkg, include_modules): From c5b63b3ec499501b276a169068a6a2b686bb0273 Mon Sep 17 00:00:00 2001 From: spencer-tb Date: Tue, 7 Mar 2023 15:55:33 -0600 Subject: [PATCH 7/9] Refactor: change package structure for tf. --- src/ethereum_test_filling_tool/__init__.py | 11 + src/ethereum_test_filling_tool/filler.py | 132 +++++++++ src/ethereum_test_filling_tool/main.py | 308 +++++---------------- src/ethereum_test_filling_tool/modules.py | 70 +++++ whitelist.txt | 1 + 5 files changed, 284 insertions(+), 238 deletions(-) create mode 100644 src/ethereum_test_filling_tool/__init__.py create mode 100644 src/ethereum_test_filling_tool/filler.py create mode 100644 src/ethereum_test_filling_tool/modules.py diff --git a/src/ethereum_test_filling_tool/__init__.py b/src/ethereum_test_filling_tool/__init__.py new file mode 100644 index 0000000000..d49c8b3f9e --- /dev/null +++ b/src/ethereum_test_filling_tool/__init__.py @@ -0,0 +1,11 @@ +""" +Filler related utilities and classes. +""" +from .filler import Filler +from .modules import find_modules, is_module_modified + +__all__ = ( + "Filler", + "find_modules", + "is_module_modified", +) diff --git a/src/ethereum_test_filling_tool/filler.py b/src/ethereum_test_filling_tool/filler.py new file mode 100644 index 0000000000..b7a7974829 --- /dev/null +++ b/src/ethereum_test_filling_tool/filler.py @@ -0,0 +1,132 @@ +""" +Provides the Filler Class: + +Fillers are python functions that, given an `EvmTransitionTool` and +`EvmBlockBuilder`, return a JSON object representing an Ethereum test case. + +This tool will traverse a package of filler python modules, fill each test +case within it, and write them to a file in a given output directory. +""" +import argparse +import concurrent.futures +import json +import logging +import os +import time + +from ethereum_test_tools import JSONEncoder +from evm_block_builder import EvmBlockBuilder +from evm_transition_tool import EvmTransitionTool + +from .modules import find_modules, is_module_modified + + +class Filler: + """ + A command line tool to process test fillers into full hydrated tests. + """ + + log: logging.Logger + + def __init__(self, options: argparse.Namespace) -> None: + self.log = logging.getLogger(__name__) + self.options = options + + def fill(self) -> None: + """ + Fill test fixtures. + """ + if self.options.benchmark: + start_time = time.time() + + fillers = self.get_fillers() + self.log.info(f"collected {len(fillers)} fillers") + + os.makedirs(self.options.output, exist_ok=True) + + t8n = EvmTransitionTool( + binary=self.options.evm_bin, trace=self.options.traces + ) + b11r = EvmBlockBuilder(binary=self.options.evm_bin) + + with concurrent.futures.ThreadPoolExecutor( + max_workers=self.options.max_workers + ) as executor: + futures = [] + for filler in fillers: + future = executor.submit(self.fill_fixture, filler, t8n, b11r) + futures.append(future) + + for future in concurrent.futures.as_completed(futures): + future.result() + + if self.options.benchmark: + end_time = time.time() + elapsed_time = end_time - start_time + self.log.info( + f"Filled test fixtures in {elapsed_time:.2f} seconds." + ) + + def get_fillers(self): + """ + Returns a list of all fillers found in the specified package + and modules. + """ + fillers = [] + for package_name, module_name, module_loader in find_modules( + os.path.abspath(self.options.filler_path), + self.options.test_categories, + self.options.test_module, + ): + module_full_name = module_loader.name + self.log.debug(f"searching {module_full_name} for fillers") + module = module_loader.load_module() + for obj in module.__dict__.values(): + if callable(obj) and hasattr(obj, "__filler_metadata__"): + if ( + self.options.test_case + and self.options.test_case + not in obj.__filler_metadata__["name"] + ): + continue + obj.__filler_metadata__["module_path"] = [ + package_name, + module_name, + ] + fillers.append(obj) + return fillers + + def fill_fixture(self, filler, t8n, b11r): + """ + Fills the specified fixture using the given filler, + transaction tool, and block builder. + """ + name = filler.__filler_metadata__["name"] + module_path = filler.__filler_metadata__["module_path"] + output_dir = os.path.join( + self.options.output, + *(module_path if not self.options.no_output_structure else ""), + ) + os.makedirs(output_dir, exist_ok=True) + path = os.path.join(output_dir, f"{name}.json") + full_name = ".".join(module_path + [name]) + + # Only skip if the fixture file already exists, the module + # has not been modified since the last test filler run, and + # the user does not want to overwrite the fixtures (--overwrite). + if ( + os.path.exists(path) + and not is_module_modified( + path, self.options.filler_path, module_path + ) + and not self.options.overwrite + ): + self.log.debug(f"skipping - {full_name}") + return + + fixture = filler(t8n, b11r, "NoProof") + self.log.debug(f"filling - {full_name}") + with open(path, "w", encoding="utf-8") as f: + json.dump( + fixture, f, ensure_ascii=False, indent=4, cls=JSONEncoder + ) diff --git a/src/ethereum_test_filling_tool/main.py b/src/ethereum_test_filling_tool/main.py index 4cf50db862..e1684c1bc9 100755 --- a/src/ethereum_test_filling_tool/main.py +++ b/src/ethereum_test_filling_tool/main.py @@ -2,264 +2,96 @@ Ethereum Test Filler ^^^^^^^^^^^^^^^^^^^^ -Execute test fillers to create "filled" tests that can be consumed by execution -clients. +Executes python test fillers to create "filled" tests (fixtures) +that can be consumed by ethereum execution clients. """ - import argparse -import concurrent.futures -import json import logging -import os -import time from pathlib import Path -from pkgutil import iter_modules - -from setuptools import find_packages -from ethereum_test_tools import JSONEncoder -from evm_block_builder import EvmBlockBuilder -from evm_transition_tool import EvmTransitionTool +from .filler import Filler -class Filler: +def parse_arguments() -> argparse.Namespace: """ - A command line tool to process test fillers into full hydrated tests. + Parse command line arguments. """ + parser = argparse.ArgumentParser() + + parser.add_argument( + "--evm-bin", + help="path to evm executable that provides `t8n` and `b11r` \ + subcommands", + default=None, + type=Path, + ) - @staticmethod - def parse_arguments() -> argparse.Namespace: - """ - Parse command line arguments. - """ - parser = argparse.ArgumentParser() - - parser.add_argument( - "--evm-bin", - help="path to evm executable that provides `t8n` and `b11r` \ - subcommands", - default=None, - type=Path, - ) - - parser.add_argument( - "--filler-path", - help="path to filler directives, default: ./fillers", - default="fillers", - type=Path, - ) - - parser.add_argument( - "--output", - help="directory to store filled test fixtures, \ - default: ./fixtures", - default="fixtures", - type=Path, - ) - - parser.add_argument( - "--test-categories", - type=str, - nargs="+", - help="limit to filling tests of specific categories", - ) - - parser.add_argument( - "--test-module", - help="limit to filling tests of a specific module", - ) - - parser.add_argument( - "--test-case", - help="limit to filling only tests with matching name", - ) - - parser.add_argument( - "--traces", - action="store_true", - help="collect traces of the execution information from the " - + "transition tool", - ) - - parser.add_argument( - "--no-output-structure", - action="store_true", - help="removes the folder structure from test fixture output", - ) - - parser.add_argument( - "--benchmark", - action="store_true", - help="logs the timing of the test filler for benchmarking", - ) - - parser.add_argument( - "--max-workers", - type=int, - help="specifies the max number of workers for the test filler \ - set to 1 for serial execution", - ) - - parser.add_argument( - "--overwrite", - action="store_true", - help="fill all test fillers and don't skip any tests \ - overwriting where necessary", - ) - - return parser.parse_args() - - options: argparse.Namespace - log: logging.Logger - - def __init__(self) -> None: - self.log = logging.getLogger(__name__) - self.options = self.parse_arguments() - - def fill(self) -> None: - """ - Fill test fixtures. - """ - if self.options.benchmark: - start_time = time.time() - - pkg_path = self.options.filler_path - - fillers = [] - - for package_name, module_name, module_loader in find_modules( - os.path.abspath(pkg_path), - self.options.test_categories, - self.options.test_module, - ): - module_full_name = module_loader.name - self.log.debug(f"searching {module_full_name} for fillers") - module = module_loader.load_module() - for obj in module.__dict__.values(): - if callable(obj): - if hasattr(obj, "__filler_metadata__"): - if ( - self.options.test_case - and self.options.test_case - not in obj.__filler_metadata__["name"] - ): - continue - obj.__filler_metadata__["module_path"] = [ - package_name, - module_name, - ] - fillers.append(obj) - - self.log.info(f"collected {len(fillers)} fillers") - - os.makedirs(self.options.output, exist_ok=True) - - t8n = EvmTransitionTool( - binary=self.options.evm_bin, trace=self.options.traces - ) - b11r = EvmBlockBuilder(binary=self.options.evm_bin) - - with concurrent.futures.ThreadPoolExecutor( - max_workers=self.options.max_workers - ) as executor: - futures = [] - for filler in fillers: - name = filler.__filler_metadata__["name"] - module_path = filler.__filler_metadata__["module_path"] - output_dir = os.path.join( - self.options.output, - *module_path - if not self.options.no_output_structure - else "", - ) - os.makedirs(output_dir, exist_ok=True) - path = os.path.join(output_dir, f"{name}.json") - full_name = ".".join(module_path + [name]) + parser.add_argument( + "--filler-path", + help="path to filler directives, default: ./fillers", + default="fillers", + type=Path, + ) - if ( - os.path.exists(path) - and not is_module_modified(path, pkg_path, module_path) - and not self.options.overwrite - ): - self.log.debug(f"skipping - {full_name}") - continue + parser.add_argument( + "--output", + help="directory to store filled test fixtures, \ + default: ./fixtures", + default="fixtures", + type=Path, + ) - future = executor.submit(filler, t8n, b11r, "NoProof") - futures.append((future, path, full_name)) + parser.add_argument( + "--test-categories", + type=str, + nargs="+", + help="limit to filling tests of specific categories", + ) - for future, path, full_name in futures: - fixture = future.result() - self.log.debug(f"filling - {full_name}") - with open(path, "w", encoding="utf-8") as f: - json.dump( - fixture, - f, - ensure_ascii=False, - indent=4, - cls=JSONEncoder, - ) + parser.add_argument( + "--test-module", + help="limit to filling tests of a specific module", + ) - if self.options.benchmark: - end_time = time.time() - elapsed_time = end_time - start_time - self.log.info( - f"Filled test fixtures in {elapsed_time:.2f} seconds." - ) + parser.add_argument( + "--test-case", + help="limit to filling only tests with matching name", + ) + parser.add_argument( + "--traces", + action="store_true", + help="collect traces of the execution information from the \ + transition tool", + ) -def is_module_modified(path, pkg_path, module_path): - """ - Returns True if a module was modified more recently than - it was filled, False otherwise. - """ - modified_time = os.path.getmtime( - os.path.join(pkg_path, *module_path) + ".py" + parser.add_argument( + "--no-output-structure", + action="store_true", + help="removes the folder structure from test fixture output", ) - filled_time = os.path.getmtime(path) if os.path.exists(path) else 0 - return modified_time > filled_time + parser.add_argument( + "--benchmark", + action="store_true", + help="logs the timing of the test filler for benchmarking", + ) -def find_modules(root, include_pkg, include_modules): - """ - Find modules recursively starting with the `root`. - Only modules in a package with name found in iterable `include_pkg` will be - yielded. - Only modules with name found in iterable `include_modules` will be yielded. - """ - modules = set() - for package in find_packages( - root, - include=include_pkg if include_pkg is not None else ("*",), - ): - package = package.replace( - ".", "/" - ) # sub_package tests i.e 'vm.vm_tests' - for info, package_path in recursive_iter_modules(root, package): - module_full_name = package_path + "." + info.name - if module_full_name not in modules: - if not include_modules or include_modules in info.name: - yield ( - package, - info.name, - info.module_finder.find_module(module_full_name), - ) - modules.add(module_full_name) + parser.add_argument( + "--max-workers", + type=int, + help="specifies the max number of workers for the test filler \ + set to 1 for serial execution", + ) + parser.add_argument( + "--overwrite", + action="store_true", + help="fill all test fillers and don't skip any tests \ + overwriting where necessary", + ) -def recursive_iter_modules(root, package): - """ - Helper function for find_packages. - Iterates through all sub-packages (packages within a package). - Recursively navigates down the package tree until a new module is found. - """ - for info in iter_modules([os.path.join(root, package)]): - if info.ispkg: - yield from recursive_iter_modules( - root, os.path.join(package, info.name) - ) - else: - package_path = package.replace("/", ".") - yield info, package_path + return parser.parse_args() def main() -> None: @@ -268,5 +100,5 @@ def main() -> None: """ logging.basicConfig(level=logging.DEBUG) - filler = Filler() + filler = Filler(parse_arguments()) filler.fill() diff --git a/src/ethereum_test_filling_tool/modules.py b/src/ethereum_test_filling_tool/modules.py new file mode 100644 index 0000000000..a237a7f387 --- /dev/null +++ b/src/ethereum_test_filling_tool/modules.py @@ -0,0 +1,70 @@ +""" +Utility functions for finding and working with modules. + +Functions: + is_module_modified: Checks if a module was modified more + recently than it was filled. + find_modules: Recursively finds modules in a directory, + filtered by package and module name. + recursive_iter_modules: Iterates through all sub-packages + of a package to find modules. +""" +import os +from pkgutil import iter_modules + +from setuptools import find_packages + + +def is_module_modified(path, pkg_path, module_path): + """ + Returns True if a module was modified more recently than + it was filled, False otherwise. + """ + modified_time = os.path.getmtime( + os.path.join(pkg_path, *module_path) + ".py" + ) + filled_time = os.path.getmtime(path) if os.path.exists(path) else 0 + return modified_time > filled_time + + +def find_modules(root, include_pkg, include_modules): + """ + Find modules recursively starting with the `root`. + Only modules in a package with name found in iterable `include_pkg` will be + yielded. + Only modules with name found in iterable `include_modules` will be yielded. + """ + modules = set() + for package in find_packages( + root, + include=include_pkg if include_pkg is not None else ("*",), + ): + package = package.replace( + ".", "/" + ) # sub_package tests i.e 'vm.vm_tests' + for info, package_path in recursive_iter_modules(root, package): + module_full_name = package_path + "." + info.name + if module_full_name not in modules: + if not include_modules or include_modules in info.name: + yield ( + package, + info.name, + info.module_finder.find_module(module_full_name), + ) + modules.add(module_full_name) + + +def recursive_iter_modules(root, package): + """ + Helper function for find_packages. + Iterates through all sub-packages (packages within a package). + Recursively navigates down the package tree until a new module is found. + """ + for info in iter_modules([os.path.join(root, package)]): + if info.ispkg: + yield from recursive_iter_modules( + root, os.path.join(package, info.name) + ) + else: + package_path = package.replace("/", ".") + yield info, package_path diff --git a/whitelist.txt b/whitelist.txt index 208b44eb65..dd7ecd56f4 100644 --- a/whitelist.txt +++ b/whitelist.txt @@ -41,6 +41,7 @@ vm gwei wei +getmtime byteorder delitem dirname From 7e2ad59c9082eaebff2c699523e6319234991470 Mon Sep 17 00:00:00 2001 From: spencer-tb Date: Tue, 7 Mar 2023 15:57:10 -0600 Subject: [PATCH 8/9] Review Chaneg: Change to --force-refill. --- src/ethereum_test_filling_tool/filler.py | 2 +- src/ethereum_test_filling_tool/main.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ethereum_test_filling_tool/filler.py b/src/ethereum_test_filling_tool/filler.py index b7a7974829..5aa2b6c27a 100644 --- a/src/ethereum_test_filling_tool/filler.py +++ b/src/ethereum_test_filling_tool/filler.py @@ -119,7 +119,7 @@ def fill_fixture(self, filler, t8n, b11r): and not is_module_modified( path, self.options.filler_path, module_path ) - and not self.options.overwrite + and not self.options.force_refill ): self.log.debug(f"skipping - {full_name}") return diff --git a/src/ethereum_test_filling_tool/main.py b/src/ethereum_test_filling_tool/main.py index e1684c1bc9..8b5477a8e0 100755 --- a/src/ethereum_test_filling_tool/main.py +++ b/src/ethereum_test_filling_tool/main.py @@ -85,7 +85,7 @@ def parse_arguments() -> argparse.Namespace: ) parser.add_argument( - "--overwrite", + "--force-refill", action="store_true", help="fill all test fillers and don't skip any tests \ overwriting where necessary", From 5d70cb3b6129e04ee3a603e9185580fd4b911db3 Mon Sep 17 00:00:00 2001 From: spencer-tb Date: Tue, 7 Mar 2023 17:03:15 -0600 Subject: [PATCH 9/9] Review Change: Force refill instead of overwrite. --- src/ethereum_test_filling_tool/filler.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ethereum_test_filling_tool/filler.py b/src/ethereum_test_filling_tool/filler.py index 5aa2b6c27a..5c37b502ab 100644 --- a/src/ethereum_test_filling_tool/filler.py +++ b/src/ethereum_test_filling_tool/filler.py @@ -113,7 +113,8 @@ def fill_fixture(self, filler, t8n, b11r): # Only skip if the fixture file already exists, the module # has not been modified since the last test filler run, and - # the user does not want to overwrite the fixtures (--overwrite). + # the user doesn't want to force a refill the + # fixtures (--force-refill). if ( os.path.exists(path) and not is_module_modified(