Skip to content

Commit

Permalink
✨ add feature
Browse files Browse the repository at this point in the history
  • Loading branch information
MeditationDuck committed Sep 24, 2024
1 parent 2ae57db commit c5f224c
Show file tree
Hide file tree
Showing 7 changed files with 136 additions and 13 deletions.
32 changes: 29 additions & 3 deletions wake/cli/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
import click.shell_completion as shell_completion
import rich_click as click

import pickle

if TYPE_CHECKING:
from wake.config import WakeConfig

Expand All @@ -22,6 +24,7 @@ def run_no_pytest(
proc_count: int,
coverage: int,
random_seeds: List[bytes],
random_states: Optional[List[bytes]],
attach_first: bool,
args: Tuple[str, ...],
) -> None:
Expand Down Expand Up @@ -85,8 +88,13 @@ def run_no_pytest(
test_functions.append((func_name, func))

if proc_count == 1:
random.seed(random_seeds[0])
console.print(f"Using random seed {random_seeds[0].hex()}")

if random_states:
random.setstate(pickle.loads(random_states[0]))
console.print(f"Using random state {random_states[0].hex()}")
else:
random.seed(random_seeds[0])
console.print(f"Using random seed {random_seeds[0].hex()}")

if debug:
set_exception_handler(partial(attach_debugger, seed=random_seeds[0]))
Expand Down Expand Up @@ -129,6 +137,7 @@ def run_no_pytest(
func,
proc_count,
random_seeds,
random_states,
logs_dir,
attach_first,
coverage,
Expand Down Expand Up @@ -204,6 +213,14 @@ def shell_complete(
type=str,
help="Random seeds",
)
@click.option(
"--random-state",
"-RS",
"random_states",
multiple=True,
type=str,
help="Random statuses",
)
@click.option(
"--attach-first",
is_flag=True,
Expand All @@ -223,6 +240,7 @@ def shell_complete(
count=True,
help="Increase verbosity. Can be specified multiple times.",
)

@click.argument("paths_or_pytest_args", nargs=-1, type=FileAndPassParamType())
@click.pass_context
def run_test(
Expand All @@ -233,6 +251,7 @@ def run_test(
coverage: int,
no_pytest: bool,
seeds: Tuple[str],
random_states: Tuple[str],
attach_first: bool,
dist: str,
verbosity: int,
Expand Down Expand Up @@ -267,6 +286,11 @@ def run_test(
except ValueError:
raise click.BadParameter("Seeds must be hex numbers.")

try:
random_states_byte = [bytes.fromhex(random_state) for random_state in random_states]
except ValueError:
raise click.BadParameter("Random states must be hex numbers.")

config = WakeConfig(local_config_path=context.obj.get("local_config_path", None))
config.load_configs()

Expand All @@ -287,6 +311,7 @@ def run_test(
proc_count,
coverage,
random_seeds,
random_states_byte,
attach_first,
paths_or_pytest_args,
)
Expand Down Expand Up @@ -325,6 +350,7 @@ def run_test(
coverage,
proc_count,
random_seeds,
random_states_byte,
attach_first,
debug,
dist,
Expand All @@ -340,7 +366,7 @@ def run_test(
pytest.main(
pytest_args,
plugins=[
PytestWakePluginSingle(config, debug, coverage, random_seeds)
PytestWakePluginSingle(config, debug, coverage, random_seeds, random_states_byte)
],
)
)
10 changes: 9 additions & 1 deletion wake/development/globals.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@
] = None
_exception_handled = False

_initial_internal_state: bytes = b""

_coverage_handler: Optional[CoverageHandler] = None

_config: Optional[WakeConfig] = None
Expand Down Expand Up @@ -130,7 +132,13 @@ def set_exception_handler(
):
global _exception_handler
_exception_handler = handler


def set_sequence_initial_internal_state(intenral_state: bytes):
global _initial_internal_state
_initial_internal_state = intenral_state

def get_sequence_initial_internal_state() -> bytes:
return _initial_internal_state

def reset_exception_handled():
global _exception_handled
Expand Down
10 changes: 9 additions & 1 deletion wake/testing/fuzzing/fuzz_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@

from typing_extensions import get_type_hints

from wake.development.globals import random
from wake.development.globals import random, set_sequence_initial_internal_state

from ..core import get_connected_chains
from .generators import generate

import pickle

def flow(
*,
Expand Down Expand Up @@ -78,6 +79,13 @@ def run(
)

snapshots = [chain.snapshot() for chain in chains]

set_sequence_initial_internal_state(
pickle.dumps(
random.getstate()
)
)

self._flow_num = 0
self._sequence_num = i
self.pre_sequence()
Expand Down
7 changes: 6 additions & 1 deletion wake/testing/fuzzing/fuzzer.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import types
from contextlib import redirect_stderr, redirect_stdout
from pathlib import Path
from typing import Any, Callable, Dict, Iterable, List, Optional, Type
from typing import Any, Callable, Dict, Iterable, List, Optional, Type, Tuple

import rich.progress
from pathvalidate import sanitize_filename # type: ignore
Expand Down Expand Up @@ -39,6 +39,7 @@ def _run(
fuzz_test: Callable,
index: int,
random_seed: bytes,
random_state: Optional[bytes],
log_file: Path,
tee: bool,
finished_event: multiprocessing.synchronize.Event,
Expand Down Expand Up @@ -91,6 +92,8 @@ def coverage_callback() -> None:

pickling_support.install()
random.seed(random_seed)
if random_state is not None:
random.setstate(pickle.loads(random_state))

set_exception_handler(exception_handler)
if coverage is not None:
Expand Down Expand Up @@ -159,6 +162,7 @@ def fuzz(
fuzz_test: types.FunctionType,
process_count: int,
random_seeds: Iterable[bytes],
random_state: Optional[List[bytes]],
logs_dir: Path,
attach_first: bool,
cov_proc_num: int,
Expand Down Expand Up @@ -187,6 +191,7 @@ def fuzz(
fuzz_test,
i,
seed,
random_state[i] if random_state is not None and i < len(random_state) else None,
log_path,
attach_first and i == 0,
finished_event,
Expand Down
37 changes: 34 additions & 3 deletions wake/testing/pytest_plugin_multiprocess.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,26 @@
reset_exception_handled,
set_coverage_handler,
set_exception_handler,
get_sequence_initial_internal_state
)
from wake.testing.coverage import CoverageHandler
from wake.utils.tee import StderrTee, StdoutTee

from datetime import datetime
import shutil

import rich.traceback
from rich.console import Console


class PytestWakePluginMultiprocess:
_index: int
_conn: multiprocessing.connection.Connection
_coverage: Optional[CoverageHandler]
_log_file: Path
_crash_log_file: Path
_random_seed: bytes
_random_state: Optional[bytes]
_tee: bool
_debug: bool
_exception_handled: bool
Expand All @@ -49,7 +58,9 @@ def __init__(
queue: multiprocessing.Queue,
coverage: Optional[CoverageHandler],
log_dir: Path,
crash_log_dir: Path,
random_seed: bytes,
random_state: Optional[bytes],
tee: bool,
debug: bool,
):
Expand All @@ -58,7 +69,9 @@ def __init__(
self._queue = queue
self._coverage = coverage
self._log_file = log_dir / sanitize_filename(f"process-{index}.ansi")
self._crash_log_dir = crash_log_dir
self._random_seed = random_seed
self._random_state = random_state
self._tee = tee
self._debug = debug
self._exception_handled = False
Expand Down Expand Up @@ -152,6 +165,20 @@ def pytest_exception_interact(self, node, call, report):
call.excinfo.type, call.excinfo.value, call.excinfo.tb
)

state = get_sequence_initial_internal_state()
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')

crash_log_file = self._crash_log_dir / F"crash_log_{timestamp}.txt"

with crash_log_file.open('w') as f:
# Create the rich traceback object
rich_tb = rich.traceback.Traceback.from_exception(
call.excinfo.type, call.excinfo.value, call.excinfo.tb
)
file_console = Console(file=f, force_terminal=False)
file_console.print(rich_tb)
f.write(f"\nInternal state of beginning of sequence : \n{state.hex()}")

def pytest_runtestloop(self, session: Session):
if (
session.testsfailed
Expand Down Expand Up @@ -202,9 +229,13 @@ def sigint_handler(signum, frame):
try:
indexes = self._conn.recv()
for i in range(len(indexes)):
# set random seed before each test item
random.seed(self._random_seed)
console.print(f"Setting random seed '{self._random_seed.hex()}'")
# set random seed before each test item
if self._random_state is not None:
random.setstate(pickle.loads(self._random_state))
console.print(f"Using random state '{random.getstate()[1]}'")
else:
random.seed(self._random_seed)
console.print(f"Setting random seed '{self._random_seed.hex()}'")

item = session.items[indexes[i]]
nextitem = (
Expand Down
13 changes: 13 additions & 0 deletions wake/testing/pytest_plugin_multiprocess_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ class PytestWakePluginMultiprocessServer:
int, Tuple[multiprocessing.Process, multiprocessing.connection.Connection]
]
_random_seeds: List[bytes]
_random_states: Optional[List[bytes]]
_attach_first: bool
_debug: bool
_pytest_args: List[str]
Expand All @@ -47,6 +48,7 @@ def __init__(
coverage: int,
proc_count: int,
random_seeds: List[bytes],
raondom_states: Optional[List[bytes]],
attach_first: bool,
debug: bool,
dist: str,
Expand All @@ -57,6 +59,7 @@ def __init__(
self._proc_count = proc_count
self._processes = {}
self._random_seeds = random_seeds
self._random_states = raondom_states
self._attach_first = attach_first
self._debug = debug
self._dist = dist
Expand All @@ -74,10 +77,18 @@ def pytest_sessionstart(self, session: pytest.Session):
logs_dir = self._config.project_root_path / ".wake" / "logs" / "testing"
shutil.rmtree(logs_dir, ignore_errors=True)
logs_dir.mkdir(parents=True, exist_ok=True)

crash_logs_dir = self._config.project_root_path / ".wake" / "crash_logs" / "testing"
crash_logs_dir.mkdir(parents=True, exist_ok=True)
# write crash log file.


self._queue = multiprocessing.Queue(1000)

for i in range(self._proc_count):
crash_logs_process_dir = crash_logs_dir / f"process-{i}"
crash_logs_process_dir.mkdir(parents=True, exist_ok=True)

parent_conn, child_conn = multiprocessing.Pipe()
p = multiprocessing.Process(
target=pytest.main,
Expand All @@ -90,7 +101,9 @@ def pytest_sessionstart(self, session: pytest.Session):
self._queue,
empty_coverage if i < self._coverage else None,
logs_dir,
crash_logs_process_dir,
self._random_seeds[i],
self._random_states[i] if self._random_states else None,
self._attach_first and i == 0,
self._debug,
),
Expand Down
Loading

0 comments on commit c5f224c

Please sign in to comment.