Skip to content

Commit e80addb

Browse files
authored
feat(fw/consume): write the test class & function docstrings to _info["description"] for use in hive reports (#579)
* feat(fw): write test class and function docstrings to the fixture _info * feat(consume): propagate the test fixture description to the hive test report * feat(consume): prepend the test id to the test description * refactor(docs|fw): move utility functions to ethereum_test_tools.utility * feat(fill): add test function github permalink to _info['url'] * feat(consume): add test source url to hive test report description * fix(fw): add workaround to fix fw pytest_plugin tests * fix(fw): fix pytest plugin fw tests on macos * feat(consume): format id as code * Revert "feat(consume): format id as code" This reverts commit cffb871. * fix(consume): use the consume pytest test id as hive test id * fix(fw): add improvements to versioning.py from review * docs: update changelog
1 parent 5bc2df7 commit e80addb

File tree

10 files changed

+129
-44
lines changed

10 files changed

+129
-44
lines changed

docs/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ Test fixtures for use by clients are available for each release on the [Github r
3535
- ✨ Add a "slow" pytest marker, in order to be able to limit the filled tests until release ([#562](https://github.com/ethereum/execution-spec-tests/pull/562)).
3636
- ✨ Add a CLI tool that generates blockchain tests as Python from a transaction hash ([#470](https://github.com/ethereum/execution-spec-tests/pull/470), [#576](https://github.com/ethereum/execution-spec-tests/pull/576)).
3737
- ✨ Add more Transaction and Block exceptions from existing ethereum/tests repo ([#572](https://github.com/ethereum/execution-spec-tests/pull/572)).
38+
- ✨ Add "description" and "url" fields containing test case documentation and a source code permalink to fixtures during `fill` and use them in `consume`-generated Hive test reports ([#579](https://github.com/ethereum/execution-spec-tests/pull/579)).
3839

3940
### 🔧 EVM Tools
4041

docs/gen_test_case_reference.py

Lines changed: 4 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,12 @@
1616

1717
import mkdocs_gen_files
1818
import pytest
19-
from git import Repo
2019

2120
from ethereum_test_forks import get_development_forks, get_forks
21+
from ethereum_test_tools.utility.versioning import (
22+
generate_github_url,
23+
get_current_commit_hash_or_tag,
24+
)
2225

2326
logger = logging.getLogger("mkdocs")
2427

@@ -206,44 +209,6 @@ def run_collect_only(test_path: Path = source_directory) -> Tuple[str, str]:
206209
return f'fill {" ".join(collect_only_args)}', collect_only_output
207210

208211

209-
def generate_github_url(file_path, branch_or_commit_or_tag="main"):
210-
"""
211-
Generate a link to a source file in Github.
212-
"""
213-
base_url = "https://github.com"
214-
username = "ethereum"
215-
repository = "execution-spec-tests"
216-
if re.match(
217-
r"^v[0-9]{1,2}\.[0-9]{1,3}\.[0-9]{1,3}(a[0-9]+|b[0-9]+|rc[0-9]+)?$",
218-
branch_or_commit_or_tag,
219-
):
220-
return f"{base_url}/{username}/{repository}/tree/{branch_or_commit_or_tag}/{file_path}"
221-
else:
222-
return f"{base_url}/{username}/{repository}/blob/{branch_or_commit_or_tag}/{file_path}"
223-
224-
225-
def get_current_commit_hash_or_tag(repo_path="."):
226-
"""
227-
Get the latest commit hash or tag from the clone where doc is being built.
228-
"""
229-
repo = Repo(repo_path)
230-
try:
231-
# Get the tag that points to the current commit
232-
current_tag = next((tag for tag in repo.tags if tag.commit == repo.head.commit))
233-
return current_tag.name
234-
except StopIteration:
235-
# If there are no tags that point to the current commit, return the commit hash
236-
return repo.head.commit.hexsha
237-
238-
239-
def get_current_commit_hash(repo_path="."):
240-
"""
241-
Get the latest commit hash from the clone where doc is being built.
242-
"""
243-
repo = Repo(repo_path)
244-
return repo.head.commit.hexsha
245-
246-
247212
COMMIT_HASH_OR_TAG = get_current_commit_hash_or_tag()
248213

249214

setup.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ install_requires =
2828
hive.py@git+https://github.com/danceratopz/hive.py@chore/setup.cfg/move-mypy-deps-to-lint-extras
2929
setuptools
3030
types-setuptools
31+
gitpython>=3.1.31,<4
3132
PyJWT>=2.3.0,<3
3233
tenacity>8.2.0,<9
3334
bidict>=0.23,<1
@@ -87,7 +88,6 @@ lint =
8788

8889
docs =
8990
cairosvg>=2.7.0,<3 # required for social plugin (material)
90-
gitpython>=3.1.31,<4
9191
mike>=1.1.2,<2
9292
mkdocs>=1.4.3,<2
9393
mkdocs-gen-files>=0.5.0,<1

src/ethereum_test_tools/spec/base/base_test.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,8 @@ def json_dict_with_info(self, hash_only: bool = False) -> Dict[str, Any]:
100100
def fill_info(
101101
self,
102102
t8n: TransitionTool,
103+
fixture_description: str,
104+
fixture_source_url: str,
103105
ref_spec: ReferenceSpec | None,
104106
):
105107
"""
@@ -108,6 +110,8 @@ def fill_info(
108110
if "comment" not in self.info:
109111
self.info["comment"] = "`execution-spec-tests` generated test"
110112
self.info["filling-transition-tool"] = t8n.version()
113+
self.info["description"] = fixture_description
114+
self.info["url"] = fixture_source_url
111115
if ref_spec is not None:
112116
ref_spec.write_info(self.info)
113117

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
"""
2+
Sub-package for utility functions and classes.
3+
"""
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
"""
2+
Utility module with helper functions for versioning.
3+
"""
4+
5+
import re
6+
7+
from git import InvalidGitRepositoryError, Repo # type: ignore
8+
9+
10+
def get_current_commit_hash_or_tag(repo_path="."):
11+
"""
12+
Get the latest commit hash or tag from the clone where doc is being built.
13+
"""
14+
try:
15+
repo = Repo(repo_path)
16+
# Try to get the current tag that points to the current commit
17+
current_tag = next((tag for tag in repo.tags if tag.commit == repo.head.commit), None)
18+
# Return the commit hash if no such tag exits
19+
return current_tag.name if current_tag else repo.head.commit.hexsha
20+
except InvalidGitRepositoryError:
21+
# This hack is necessary for our framework tests. We use the pytester/tempdir fixtures
22+
# to execute pytest within a pytest session (for top-level tests of our pytest plugins).
23+
# The pytester fixture executes these tests in a temporary directory, which is not a git
24+
# repository; this is a workaround to stop these tests failing.
25+
#
26+
# Tried monkeypatching the pytest plugin tests, but it didn't play well with pytester.
27+
return "Not a git repository; this should only be seen in framework tests."
28+
29+
30+
def generate_github_url(file_path, branch_or_commit_or_tag="main", line_number=""):
31+
"""
32+
Generate a permalink to a source file in Github.
33+
"""
34+
base_url = "https://github.com"
35+
username = "ethereum"
36+
repository = "execution-spec-tests"
37+
if line_number:
38+
line_number = f"#L{line_number}"
39+
release_tag_regex = r"^v[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}(a[0-9]+|b[0-9]+|rc[0-9]+)?$"
40+
tree_or_blob = "tree" if re.match(release_tag_regex, branch_or_commit_or_tag) else "blob"
41+
return (
42+
f"{base_url}/{username}/{repository}/{tree_or_blob}/"
43+
f"{branch_or_commit_or_tag}/{file_path}{line_number}"
44+
)

src/pytest_plugins/consume/simulator_common.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,18 @@ def fixture(fixture_source: JsonSource, test_case: TestCase) -> Fixture:
3636
fixtures = BlockchainFixtures.from_file(Path(fixture_source) / test_case.json_path)
3737
fixture = fixtures[test_case.id]
3838
return fixture
39+
40+
41+
@pytest.fixture(scope="function")
42+
def fixture_description(fixture: Fixture, test_case: TestCase) -> str:
43+
"""
44+
Return the description of the current test case.
45+
"""
46+
description = f"Test id: {test_case.id}"
47+
if "url" in fixture.info:
48+
description += f"\n\nTest source: {fixture.info['url']}"
49+
if "description" not in fixture.info:
50+
description += "\n\nNo description field provided in the fixture's 'info' section."
51+
else:
52+
description += f"\n\n{fixture.info['description']}"
53+
return description

src/pytest_plugins/pytest_hive/pytest_hive.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -107,11 +107,17 @@ def hive_test(request, test_suite: HiveTestSuite):
107107
"""
108108
Propagate the pytest test case and its result to the hive server.
109109
"""
110-
test_parameter_string = request.node.nodeid.split("[")[-1].rstrip("]") # test fixture name
110+
try:
111+
fixture_description = request.getfixturevalue("fixture_description")
112+
except pytest.FixtureLookupError:
113+
pytest.exit(
114+
"Error: The 'fixture_description' fixture has not been defined by the simulator "
115+
"or pytest plugin using this plugin!"
116+
)
117+
test_parameter_string = request.node.nodeid # consume pytest test id
111118
test: HiveTest = test_suite.start_test(
112-
# TODO: pass test case documentation when available
113119
name=test_parameter_string,
114-
description="TODO: This should come from the '_info' field.",
120+
description=fixture_description,
115121
)
116122
yield test
117123
try:

src/pytest_plugins/test_filler/test_filler.py

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@
2222
get_forks_with_solc_support,
2323
)
2424
from ethereum_test_tools import SPEC_TYPES, BaseTest, FixtureCollector, TestInfo, Yul
25+
from ethereum_test_tools.utility.versioning import (
26+
generate_github_url,
27+
get_current_commit_hash_or_tag,
28+
)
2529
from evm_transition_tool import FixtureFormats, TransitionTool
2630
from pytest_plugins.spec_version_checker.spec_version_checker import EIPSpecTestItem
2731

@@ -564,6 +568,38 @@ def node_to_test_info(node) -> TestInfo:
564568
)
565569

566570

571+
@pytest.fixture(scope="function")
572+
def fixture_source_url(request):
573+
"""
574+
Returns the URL to the fixture source.
575+
"""
576+
function_line_number = request.function.__code__.co_firstlineno
577+
module_relative_path = os.path.relpath(request.module.__file__)
578+
hash_or_tag = get_current_commit_hash_or_tag()
579+
github_url = generate_github_url(
580+
module_relative_path, branch_or_commit_or_tag=hash_or_tag, line_number=function_line_number
581+
)
582+
return github_url
583+
584+
585+
@pytest.fixture(scope="function")
586+
def fixture_description(request):
587+
"""Fixture to extract and combine docstrings from the test class and the test function."""
588+
description_unavailable = (
589+
"No description available - add a docstring to the python test class or function."
590+
)
591+
test_class_doc = f"Test class documentation:\n{request.cls.__doc__}" if request.cls else ""
592+
test_function_doc = (
593+
f"Test function documentation:\n{request.function.__doc__}"
594+
if request.function.__doc__
595+
else ""
596+
)
597+
if not test_class_doc and not test_function_doc:
598+
return description_unavailable
599+
combined_docstring = f"{test_class_doc}\n\n{test_function_doc}".strip()
600+
return combined_docstring
601+
602+
567603
def base_test_parametrizer(cls: Type[BaseTest]):
568604
"""
569605
Generates a pytest.fixture for a given BaseTest subclass.
@@ -584,6 +620,8 @@ def base_test_parametrizer_func(
584620
eips,
585621
dump_dir_parameter_level,
586622
fixture_collector,
623+
fixture_description,
624+
fixture_source_url,
587625
):
588626
"""
589627
Fixture used to instantiate an auto-fillable BaseTest object from within
@@ -608,7 +646,12 @@ def __init__(self, *args, **kwargs):
608646
fixture_format=fixture_format,
609647
eips=eips,
610648
)
611-
fixture.fill_info(t8n, reference_spec)
649+
fixture.fill_info(
650+
t8n,
651+
fixture_description,
652+
fixture_source_url=fixture_source_url,
653+
ref_spec=reference_spec,
654+
)
612655

613656
fixture_path = fixture_collector.add_fixture(
614657
node_to_test_info(request.node),

whitelist.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ extcodehash
128128
extcodesize
129129
F00
130130
filesystem
131+
firstlineno
131132
fn
132133
fname
133134
forkchoice
@@ -250,6 +251,7 @@ parseable
250251
pathlib
251252
pdb
252253
perf
254+
permalink
253255
petersburg
254256
pformat
255257
png
@@ -420,6 +422,7 @@ makepyfile
420422
makereport
421423
metafunc
422424
modifyitems
425+
monkeypatching
423426
nodeid
424427
noop
425428
oog
@@ -453,6 +456,7 @@ substring
453456
substrings
454457
tf
455458
teardown
459+
tempdir
456460
testdir
457461
teststatus
458462
tmpdir

0 commit comments

Comments
 (0)