Skip to content

Commit 3d1bc88

Browse files
committed
refactor(tests): Refactor types in json_infra
1 parent 7acc1bf commit 3d1bc88

File tree

6 files changed

+347
-508
lines changed

6 files changed

+347
-508
lines changed

tests/json_infra/conftest.py

Lines changed: 15 additions & 215 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,15 @@
77
from glob import glob
88
from pathlib import Path
99
from typing import (
10-
Any,
1110
Callable,
12-
Dict,
1311
Final,
1412
Generator,
15-
List,
1613
Optional,
1714
Self,
1815
Set,
19-
Type,
2016
)
2117

2218
import git
23-
import pytest
2419
import requests_cache
2520
from _pytest.config import Config
2621
from _pytest.config.argparsing import Parser
@@ -31,18 +26,8 @@
3126
from requests_cache import CachedSession
3227
from requests_cache.backends.sqlite import SQLiteCache
3328

34-
from ethereum_spec_tools.evm_tools.statetest import TestCase as StateTestCase
35-
from ethereum_spec_tools.evm_tools.statetest import (
36-
read_test_case as read_state_test_case,
37-
)
38-
39-
from . import FORKS, TEST_FIXTURES
40-
from .helpers.exceptional_test_patterns import (
41-
exceptional_blockchain_test_patterns,
42-
exceptional_state_test_patterns,
43-
)
44-
from .helpers.load_blockchain_tests import Load, run_blockchain_st_test
45-
from .helpers.load_state_tests import run_state_test
29+
from . import TEST_FIXTURES
30+
from .helpers import ALL_FIXTURE_TYPES
4631

4732
try:
4833
from xdist import get_xdist_worker_id
@@ -316,201 +301,12 @@ def pytest_collect_file(
316301
return None
317302

318303

319-
class Fixture:
320-
"""Single fixture from a JSON file."""
321-
322-
@classmethod
323-
def is_format(cls, obj: object) -> bool:
324-
"""Return true if the object can be parsed as the fixture type."""
325-
raise NotImplementedError("Not implemented.")
326-
327-
@classmethod
328-
def collect(
329-
cls, file_path: str, key: str, obj: Dict[str, Any]
330-
) -> Generator[Item, None, None]:
331-
"""Collect tests from a single fixture dictionary."""
332-
pass
333-
334-
335-
class StateTest(Item):
336-
"""Single state test case item."""
337-
338-
test_case: StateTestCase
339-
test_dict: Dict[str, Any]
340-
341-
def __init__(
342-
self,
343-
*args: Any,
344-
test_case: StateTestCase,
345-
test_dict: Dict[str, Any],
346-
**kwargs: Any,
347-
) -> None:
348-
"""Initialize a single test case item."""
349-
super().__init__(*args, **kwargs)
350-
self.test_case = test_case
351-
self.test_dict = test_dict
352-
self.own_markers.append(pytest.mark.fork(self.test_case.fork_name))
353-
self.own_markers.append(pytest.mark.evm_tools)
354-
self.own_markers.append(pytest.mark.json_state_tests)
355-
eels_fork = FORKS[test_case.fork_name]["eels_fork"]
356-
test_patterns = exceptional_state_test_patterns(
357-
test_case.fork_name, eels_fork
358-
)
359-
if any(x.search(test_case.key) for x in test_patterns.slow):
360-
self.own_markers.append(pytest.mark.slow)
361-
362-
def runtest(self) -> None:
363-
"""Execute the test logic for this specific static test."""
364-
test_case_dict = {
365-
"test_file": self.test_case.path,
366-
"test_key": self.test_case.key,
367-
"index": self.test_case.index,
368-
"json_fork": self.test_case.fork_name,
369-
"test_dict": self.test_dict,
370-
}
371-
run_state_test(test_case_dict)
372-
373-
374-
class StateTestFixture(Fixture):
375-
"""Single state test fixture from a JSON file."""
376-
377-
@classmethod
378-
def is_format(cls, obj: object) -> bool:
379-
"""Return true if the object can be parsed as the fixture type."""
380-
if "env" not in obj:
381-
return False
382-
if "pre" not in obj:
383-
return False
384-
if "transaction" not in obj:
385-
return False
386-
if "post" not in obj:
387-
return False
388-
return True
389-
390-
@classmethod
391-
def collect(
392-
cls, parent: Collector, file_path: str, key: str, obj: Dict[str, Any]
393-
) -> Generator[Item, None, None]:
394-
"""Collect state tests from a single fixture dictionary."""
395-
for test_case in read_state_test_case(
396-
test_file_path=file_path, key=key, test=obj
397-
):
398-
if test_case.fork_name not in FORKS:
399-
continue
400-
name = f"{key} - {test_case.index}"
401-
new_item = StateTest.from_parent(
402-
parent,
403-
name=name,
404-
test_case=test_case,
405-
test_dict=obj,
406-
)
407-
yield new_item
408-
409-
410-
class BlockchainTest(Item):
411-
"""Single state test case item."""
412-
413-
test_file: str
414-
test_key: str
415-
fork_name: str
416-
test_dict: Dict[str, Any]
417-
418-
def __init__(
419-
self,
420-
*args: Any,
421-
test_file: str,
422-
test_key: str,
423-
fork_name: str,
424-
test_dict: Dict[str, Any],
425-
**kwargs: Any,
426-
) -> None:
427-
"""Initialize a single test case item."""
428-
super().__init__(*args, **kwargs)
429-
self.test_file = test_file
430-
self.test_key = test_key
431-
self.test_dict = test_dict
432-
self.fork_name = fork_name
433-
self.own_markers.append(pytest.mark.fork(fork_name))
434-
self.own_markers.append(pytest.mark.evm_tools)
435-
self.own_markers.append(pytest.mark.json_blockchain_tests)
436-
eels_fork = FORKS[fork_name]["eels_fork"]
437-
test_patterns = exceptional_blockchain_test_patterns(
438-
fork_name, eels_fork
439-
)
440-
_identifier = "(" + test_file + "|" + test_key + ")"
441-
if any(
442-
x.search(test_file) for x in test_patterns.expected_fail
443-
) or any(x.search(_identifier) for x in test_patterns.expected_fail):
444-
self.own_markers.append(pytest.mark.skip("Expected to fail"))
445-
if any(x.search(_identifier) for x in test_patterns.slow):
446-
self.own_markers.append(pytest.mark.slow)
447-
if any(x.search(_identifier) for x in test_patterns.big_memory):
448-
self.own_markers.append(pytest.mark.bigmem)
449-
450-
def runtest(self) -> None:
451-
"""Execute the test logic for this specific static test."""
452-
test_case_dict = {
453-
"test_file": self.test_file,
454-
"test_key": self.test_key,
455-
"test_dict": self.test_dict,
456-
}
457-
eels_fork = FORKS[self.fork_name]["eels_fork"]
458-
load = Load(
459-
self.fork_name,
460-
eels_fork,
461-
)
462-
run_blockchain_st_test(test_case_dict, load=load)
463-
464-
465-
class BlockchainTestFixture(Fixture):
466-
"""Single blockchain test fixture from a JSON file."""
467-
468-
@classmethod
469-
def is_format(cls, obj: Dict) -> bool:
470-
"""Return true if the object can be parsed as the fixture type."""
471-
if "genesisBlockHeader" not in obj:
472-
return False
473-
if "blocks" not in obj:
474-
return False
475-
if "engineNewPayloads" in obj:
476-
return False
477-
if "preHash" in obj:
478-
return False
479-
if "network" not in obj:
480-
return False
481-
return True
482-
483-
@classmethod
484-
def collect(
485-
cls, parent: Collector, file_path: str, key: str, obj: Dict[str, Any]
486-
) -> Generator[Item, None, None]:
487-
"""Collect blockchain tests from a single fixture dictionary."""
488-
name = f"{key}"
489-
if "network" not in obj or obj["network"] not in FORKS:
490-
return
491-
new_item = BlockchainTest.from_parent(
492-
parent,
493-
name=name,
494-
test_file=file_path,
495-
test_key=key,
496-
fork_name=obj["network"],
497-
test_dict=obj,
498-
)
499-
yield new_item
500-
501-
502-
FixtureTypes: List[Type[Fixture]] = [
503-
StateTestFixture,
504-
BlockchainTestFixture,
505-
]
506-
507-
508304
class FixturesFile(File):
509305
"""Single JSON file containing fixtures."""
510306

511307
def collect(
512308
self: Self,
513-
) -> Generator[StateTestFixture | BlockchainTestFixture, None, None]:
309+
) -> Generator[Item | Collector, None, None]:
514310
"""Collect test cases from a single JSON fixtures file."""
515311
with open(self.path, "r") as file:
516312
try:
@@ -519,15 +315,19 @@ def collect(
519315
return # Skip *.json files that are unreadable.
520316
if not isinstance(loaded_file, dict):
521317
return
522-
for key, fixture_dict in loaded_file.items():
523-
if not isinstance(fixture_dict, dict):
318+
for key, test_dict in loaded_file.items():
319+
if not isinstance(test_dict, dict):
524320
continue
525-
for fixture_type in FixtureTypes:
526-
if not fixture_type.is_format(fixture_dict):
321+
for fixture_type in ALL_FIXTURE_TYPES:
322+
if not fixture_type.is_format(test_dict):
527323
continue
528-
yield from fixture_type.collect(
324+
name = key
325+
if "::" in name:
326+
name = name.split("::")[1]
327+
yield fixture_type.from_parent( # type: ignore
529328
parent=self,
530-
file_path=str(self.path),
531-
key=key,
532-
obj=fixture_dict,
329+
name=name,
330+
test_file=str(self.path),
331+
test_key=key,
332+
test_dict=test_dict,
533333
)
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,14 @@
11
"""Helpers to load tests from JSON files."""
2+
3+
from typing import List, Type
4+
5+
from .fixtures import Fixture
6+
from .load_blockchain_tests import BlockchainTestFixture
7+
from .load_state_tests import StateTestFixture
8+
9+
ALL_FIXTURE_TYPES: List[Type[Fixture]] = [
10+
BlockchainTestFixture,
11+
StateTestFixture,
12+
]
13+
14+
__all__ = ["ALL_FIXTURE_TYPES", "Fixture"]

tests/json_infra/helpers/exceptional_test_patterns.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import re
77
from dataclasses import dataclass
8+
from functools import lru_cache
89
from typing import Pattern, Tuple
910

1011

@@ -20,6 +21,7 @@ class TestPatterns:
2021
big_memory: Tuple[Pattern[str], ...]
2122

2223

24+
@lru_cache
2325
def exceptional_blockchain_test_patterns(
2426
json_fork: str, eels_fork: str
2527
) -> TestPatterns:
@@ -104,6 +106,7 @@ def exceptional_blockchain_test_patterns(
104106
)
105107

106108

109+
@lru_cache
107110
def exceptional_state_test_patterns(
108111
json_fork: str, eels_fork: str
109112
) -> TestPatterns:
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
"""Base class for all fixture loaders."""
2+
3+
from abc import ABC, abstractmethod
4+
from typing import Any, Dict, Self
5+
6+
from _pytest.nodes import Node
7+
8+
9+
class Fixture(ABC):
10+
"""
11+
Single fixture from a JSON file.
12+
13+
It can be subclassed in combination with Item or Collector to create a
14+
fixture that can be collected by pytest.
15+
"""
16+
17+
test_file: str
18+
test_key: str
19+
test_dict: Dict[str, Any]
20+
21+
def __init__(
22+
self,
23+
*args: Any,
24+
test_file: str,
25+
test_key: str,
26+
test_dict: Dict[str, Any],
27+
**kwargs: Any,
28+
):
29+
super().__init__(*args, **kwargs)
30+
self.test_file = test_file
31+
self.test_key = test_key
32+
self.test_dict = test_dict
33+
34+
@classmethod
35+
def from_parent(
36+
cls,
37+
parent: Node,
38+
**kwargs: Any,
39+
) -> Self:
40+
"""Pytest hook that returns a fixture from a JSON file."""
41+
return super().from_parent( # type: ignore[misc]
42+
parent=parent, **kwargs
43+
)
44+
45+
@classmethod
46+
@abstractmethod
47+
def is_format(cls, test_dict: Dict[str, Any]) -> bool:
48+
"""Return true if the object can be parsed as the fixture type."""
49+
pass

0 commit comments

Comments
 (0)