Skip to content

Commit 2dcfb50

Browse files
authored
Merge pull request #72 from spencer-tb/tf-concurrency-cache-usage
Feature & Refactor: Add Concurrency and Filler Skips to Improve `tf` Effienciency. Additional CLI arguments.`
2 parents 2d15256 + 5d70cb3 commit 2dcfb50

File tree

5 files changed

+297
-186
lines changed

5 files changed

+297
-186
lines changed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
"""
2+
Filler related utilities and classes.
3+
"""
4+
from .filler import Filler
5+
from .modules import find_modules, is_module_modified
6+
7+
__all__ = (
8+
"Filler",
9+
"find_modules",
10+
"is_module_modified",
11+
)
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
"""
2+
Provides the Filler Class:
3+
4+
Fillers are python functions that, given an `EvmTransitionTool` and
5+
`EvmBlockBuilder`, return a JSON object representing an Ethereum test case.
6+
7+
This tool will traverse a package of filler python modules, fill each test
8+
case within it, and write them to a file in a given output directory.
9+
"""
10+
import argparse
11+
import concurrent.futures
12+
import json
13+
import logging
14+
import os
15+
import time
16+
17+
from ethereum_test_tools import JSONEncoder
18+
from evm_block_builder import EvmBlockBuilder
19+
from evm_transition_tool import EvmTransitionTool
20+
21+
from .modules import find_modules, is_module_modified
22+
23+
24+
class Filler:
25+
"""
26+
A command line tool to process test fillers into full hydrated tests.
27+
"""
28+
29+
log: logging.Logger
30+
31+
def __init__(self, options: argparse.Namespace) -> None:
32+
self.log = logging.getLogger(__name__)
33+
self.options = options
34+
35+
def fill(self) -> None:
36+
"""
37+
Fill test fixtures.
38+
"""
39+
if self.options.benchmark:
40+
start_time = time.time()
41+
42+
fillers = self.get_fillers()
43+
self.log.info(f"collected {len(fillers)} fillers")
44+
45+
os.makedirs(self.options.output, exist_ok=True)
46+
47+
t8n = EvmTransitionTool(
48+
binary=self.options.evm_bin, trace=self.options.traces
49+
)
50+
b11r = EvmBlockBuilder(binary=self.options.evm_bin)
51+
52+
with concurrent.futures.ThreadPoolExecutor(
53+
max_workers=self.options.max_workers
54+
) as executor:
55+
futures = []
56+
for filler in fillers:
57+
future = executor.submit(self.fill_fixture, filler, t8n, b11r)
58+
futures.append(future)
59+
60+
for future in concurrent.futures.as_completed(futures):
61+
future.result()
62+
63+
if self.options.benchmark:
64+
end_time = time.time()
65+
elapsed_time = end_time - start_time
66+
self.log.info(
67+
f"Filled test fixtures in {elapsed_time:.2f} seconds."
68+
)
69+
70+
def get_fillers(self):
71+
"""
72+
Returns a list of all fillers found in the specified package
73+
and modules.
74+
"""
75+
fillers = []
76+
for package_name, module_name, module_loader in find_modules(
77+
os.path.abspath(self.options.filler_path),
78+
self.options.test_categories,
79+
self.options.test_module,
80+
):
81+
module_full_name = module_loader.name
82+
self.log.debug(f"searching {module_full_name} for fillers")
83+
module = module_loader.load_module()
84+
for obj in module.__dict__.values():
85+
if callable(obj) and hasattr(obj, "__filler_metadata__"):
86+
if (
87+
self.options.test_case
88+
and self.options.test_case
89+
not in obj.__filler_metadata__["name"]
90+
):
91+
continue
92+
obj.__filler_metadata__["module_path"] = [
93+
package_name,
94+
module_name,
95+
]
96+
fillers.append(obj)
97+
return fillers
98+
99+
def fill_fixture(self, filler, t8n, b11r):
100+
"""
101+
Fills the specified fixture using the given filler,
102+
transaction tool, and block builder.
103+
"""
104+
name = filler.__filler_metadata__["name"]
105+
module_path = filler.__filler_metadata__["module_path"]
106+
output_dir = os.path.join(
107+
self.options.output,
108+
*(module_path if not self.options.no_output_structure else ""),
109+
)
110+
os.makedirs(output_dir, exist_ok=True)
111+
path = os.path.join(output_dir, f"{name}.json")
112+
full_name = ".".join(module_path + [name])
113+
114+
# Only skip if the fixture file already exists, the module
115+
# has not been modified since the last test filler run, and
116+
# the user doesn't want to force a refill the
117+
# fixtures (--force-refill).
118+
if (
119+
os.path.exists(path)
120+
and not is_module_modified(
121+
path, self.options.filler_path, module_path
122+
)
123+
and not self.options.force_refill
124+
):
125+
self.log.debug(f"skipping - {full_name}")
126+
return
127+
128+
fixture = filler(t8n, b11r, "NoProof")
129+
self.log.debug(f"filling - {full_name}")
130+
with open(path, "w", encoding="utf-8") as f:
131+
json.dump(
132+
fixture, f, ensure_ascii=False, indent=4, cls=JSONEncoder
133+
)

0 commit comments

Comments
 (0)