2018-07-11 23:25:30 +00:00
|
|
|
"""
|
2025-04-03 02:40:28 +00:00
|
|
|
Top level of the testing suites!
|
|
|
|
|
|
2018-07-11 23:25:30 +00:00
|
|
|
"""
|
2025-04-03 02:40:28 +00:00
|
|
|
from __future__ import annotations
|
2020-08-03 18:49:46 +00:00
|
|
|
import sys
|
|
|
|
|
import subprocess
|
2020-07-26 01:20:34 +00:00
|
|
|
import os
|
2020-08-03 18:49:46 +00:00
|
|
|
import signal
|
2020-01-23 06:16:10 +00:00
|
|
|
import platform
|
2020-08-03 18:49:46 +00:00
|
|
|
import time
|
2026-03-25 20:50:55 +00:00
|
|
|
from pathlib import Path
|
|
|
|
|
from typing import Literal
|
2018-08-07 18:30:25 +00:00
|
|
|
|
2018-07-11 23:25:30 +00:00
|
|
|
import pytest
|
2026-03-02 19:57:40 +00:00
|
|
|
import tractor
|
2024-03-12 19:48:20 +00:00
|
|
|
from tractor._testing import (
|
|
|
|
|
examples_dir as examples_dir,
|
|
|
|
|
tractor_test as tractor_test,
|
|
|
|
|
expect_ctxc as expect_ctxc,
|
|
|
|
|
)
|
2020-07-25 16:00:04 +00:00
|
|
|
|
2025-04-17 15:20:49 +00:00
|
|
|
pytest_plugins: list[str] = [
|
|
|
|
|
'pytester',
|
2026-04-30 23:29:51 +00:00
|
|
|
# NOTE, now loaded in `pytest-ini` section of `pyproject.toml`
|
|
|
|
|
# 'tractor._testing.pytest',
|
2025-04-17 15:20:49 +00:00
|
|
|
]
|
|
|
|
|
|
2026-03-12 21:32:05 +00:00
|
|
|
_ci_env: bool = os.environ.get('CI', False)
|
2026-03-02 20:27:01 +00:00
|
|
|
_non_linux: bool = platform.system() != 'Linux'
|
2018-07-11 23:25:30 +00:00
|
|
|
|
2020-08-03 18:49:46 +00:00
|
|
|
# Sending signal.SIGINT on subprocess fails on windows. Use CTRL_* alternatives
|
|
|
|
|
if platform.system() == 'Windows':
|
|
|
|
|
_KILL_SIGNAL = signal.CTRL_BREAK_EVENT
|
|
|
|
|
_INT_SIGNAL = signal.CTRL_C_EVENT
|
|
|
|
|
_INT_RETURN_CODE = 3221225786
|
|
|
|
|
else:
|
|
|
|
|
_KILL_SIGNAL = signal.SIGKILL
|
|
|
|
|
_INT_SIGNAL = signal.SIGINT
|
|
|
|
|
_INT_RETURN_CODE = 1 if sys.version_info < (3, 8) else -signal.SIGINT.value
|
|
|
|
|
|
|
|
|
|
|
2020-07-25 16:00:04 +00:00
|
|
|
no_windows = pytest.mark.skipif(
|
|
|
|
|
platform.system() == "Windows",
|
|
|
|
|
reason="Test is unsupported on windows",
|
|
|
|
|
)
|
2026-02-25 01:02:14 +00:00
|
|
|
no_macos = pytest.mark.skipif(
|
|
|
|
|
platform.system() == "Darwin",
|
|
|
|
|
reason="Test is unsupported on MacOS",
|
|
|
|
|
)
|
2020-07-25 16:00:04 +00:00
|
|
|
|
|
|
|
|
|
2026-03-25 20:50:55 +00:00
|
|
|
def get_cpu_state(
|
|
|
|
|
icpu: int = 0,
|
|
|
|
|
setting: Literal[
|
|
|
|
|
'scaling_governor',
|
|
|
|
|
'*_pstate_max_freq',
|
|
|
|
|
'scaling_max_freq',
|
|
|
|
|
# 'scaling_cur_freq',
|
|
|
|
|
] = '*_pstate_max_freq',
|
|
|
|
|
) -> tuple[
|
|
|
|
|
Path,
|
|
|
|
|
str|int,
|
|
|
|
|
]|None:
|
|
|
|
|
'''
|
|
|
|
|
Attempt to read the (first) CPU's setting according
|
|
|
|
|
to the set `setting` from under the file-sys,
|
|
|
|
|
|
|
|
|
|
/sys/devices/system/cpu/cpu0/cpufreq/{setting}
|
|
|
|
|
|
|
|
|
|
Useful to determine latency headroom for various perf affected
|
|
|
|
|
test suites.
|
|
|
|
|
|
|
|
|
|
'''
|
|
|
|
|
try:
|
|
|
|
|
# Read governor for core 0 (usually same for all)
|
|
|
|
|
setting_path: Path = list(
|
|
|
|
|
Path(f'/sys/devices/system/cpu/cpu{icpu}/cpufreq/')
|
|
|
|
|
.glob(f'{setting}')
|
|
|
|
|
)[0] # <- XXX must be single match!
|
|
|
|
|
with open(
|
|
|
|
|
setting_path,
|
|
|
|
|
'r',
|
|
|
|
|
) as f:
|
|
|
|
|
return (
|
|
|
|
|
setting_path,
|
|
|
|
|
f.read().strip(),
|
|
|
|
|
)
|
|
|
|
|
except (FileNotFoundError, IndexError):
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def cpu_scaling_factor() -> float:
|
|
|
|
|
'''
|
|
|
|
|
Return a latency-headroom multiplier (>= 1.0) reflecting how
|
|
|
|
|
much to inflate time-limits when CPU-freq scaling is active on
|
|
|
|
|
linux.
|
|
|
|
|
|
|
|
|
|
When no scaling info is available (non-linux, missing sysfs),
|
|
|
|
|
returns 1.0 (i.e. no headroom adjustment needed).
|
|
|
|
|
|
|
|
|
|
'''
|
|
|
|
|
if _non_linux:
|
|
|
|
|
return 1.
|
|
|
|
|
|
|
|
|
|
mx = get_cpu_state()
|
|
|
|
|
cur = get_cpu_state(setting='scaling_max_freq')
|
|
|
|
|
if mx is None or cur is None:
|
|
|
|
|
return 1.
|
|
|
|
|
|
|
|
|
|
_mx_pth, max_freq = mx
|
|
|
|
|
_cur_pth, cur_freq = cur
|
|
|
|
|
cpu_scaled: float = int(cur_freq) / int(max_freq)
|
|
|
|
|
|
|
|
|
|
if cpu_scaled != 1.:
|
|
|
|
|
return 1. / (
|
|
|
|
|
cpu_scaled * 2 # <- bc likely "dual threaded"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
return 1.
|
|
|
|
|
|
|
|
|
|
|
Add `cpu_perf_headroom()` for throttle-aware deadlines
Mass `trio` deadline-miss failures on byte-identical code turned
out to be a firmware/EC power-cap (AMD PPT/STAPM) clamping the
all-core sustained clock while every static knob (`governor`,
`scaling_max_freq`, EPP, platform-profile) still read "performance"
— invisible to the existing `cpu_scaling_factor()` check. See
`scripts/cpu-perf-check` + the
`ai/conc-anal/trio_033_cancel_cascade_slowdown_depth3_issue.md`
notes.
Deats,
- add `_measure_sustained_headroom()` to `tests/conftest.py`: a
one-shot ~0.9s all-core burn (explicit `fork`-ctx `mp` procs)
sampling achieved-vs-max freq AFTER the boost window; under a 0.6
gate it returns the full inverse fraction (capped 4x), else 1.0;
best-effort 1.0 on non-linux or any error,
- add `cpu_perf_headroom()`: `max()` of the static scaling factor
and the (session-cached) sustained probe,
- inflate deadline budgets by it in `test_dynamic_pub_sub`, both
`test_clustering` cases, the
`test_multi_nested_subactors_error_through_nurseries` pexpect
waits + `test_nested_multierrors`,
- `xfail(strict=False)` `test_nested_multierrors` depth=3 under
throttle: the deep tree trips tractor's INTERNAL reap deadlines
(`soft_kill`/`hard_kill` `terminate_after=1.6`) minting a
`Cancelled` inside the runtime — not fixable by test-budget
inflation; auto-clears once the box un-throttles.
(this patch was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
2026-06-12 17:37:05 +00:00
|
|
|
# session-cached sustained-load throttle multiplier — measured
|
|
|
|
|
# once (lazily) on the first `cpu_perf_headroom()` call. `None`
|
|
|
|
|
# = not-yet-measured.
|
|
|
|
|
_sustained_headroom: float|None = None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _measure_sustained_headroom(
|
|
|
|
|
secs: float = 0.9,
|
|
|
|
|
# a healthy all-core sustained clock holds AT/ABOVE this
|
|
|
|
|
# fraction of the package single-core max ceiling (boost sags
|
|
|
|
|
# under full multi-core load even un-throttled, but not far);
|
|
|
|
|
# at/above it we assume no throttle and return 1.0.
|
|
|
|
|
throttle_gate: float = 0.6,
|
|
|
|
|
max_headroom: float = 4.,
|
|
|
|
|
) -> float:
|
|
|
|
|
'''
|
|
|
|
|
One-shot all-core burn returning a latency multiplier
|
|
|
|
|
(>= 1.0) that reflects *sustained-load* CPU throttle.
|
|
|
|
|
|
|
|
|
|
Catches the firmware/EC power-cap clamp (AMD PPT/STAPM &
|
|
|
|
|
friends) that pins achieved `scaling_cur_freq` to a fraction
|
|
|
|
|
of the ceiling under multi-core load while EVERY static knob
|
|
|
|
|
(`governor`, `scaling_max_freq`, `EPP`, `platform_profile`)
|
|
|
|
|
still reads "full performance". That cap is INVISIBLE to
|
|
|
|
|
`cpu_scaling_factor()` and is the gremlin behind mass `trio`
|
|
|
|
|
deadline-miss failures on byte-identical code — see
|
|
|
|
|
`scripts/cpu-perf-check`.
|
|
|
|
|
|
|
|
|
|
Best-effort: returns 1.0 on non-linux / missing sysfs / any
|
|
|
|
|
error so it can never break a test run.
|
|
|
|
|
|
|
|
|
|
'''
|
|
|
|
|
import glob
|
|
|
|
|
import multiprocessing as mp
|
|
|
|
|
|
|
|
|
|
def _read_mhz(path: str) -> int|None:
|
|
|
|
|
try:
|
|
|
|
|
return int(open(path).read()) // 1000
|
|
|
|
|
except OSError:
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
maxs: list[int] = [
|
|
|
|
|
v for f in glob.glob(
|
|
|
|
|
'/sys/devices/system/cpu/cpu[0-9]*/cpufreq/scaling_max_freq'
|
|
|
|
|
)
|
|
|
|
|
if (v := _read_mhz(f)) is not None
|
|
|
|
|
]
|
|
|
|
|
pkg_max: int = max(maxs) if maxs else 0
|
|
|
|
|
if not pkg_max:
|
|
|
|
|
return 1.
|
|
|
|
|
|
|
|
|
|
def _burn(stop: float) -> None:
|
|
|
|
|
x: int = 1
|
|
|
|
|
while time.perf_counter() < stop:
|
|
|
|
|
x += x * x ^ 0x5
|
|
|
|
|
|
|
|
|
|
# explicit `fork` ctx so we're immune to whatever global
|
|
|
|
|
# mp start-method tractor/the suite may have set (`spawn`
|
|
|
|
|
# would re-exec + re-import 24x — slow and pointless here).
|
|
|
|
|
ctx = mp.get_context('fork')
|
|
|
|
|
ncpu: int = os.cpu_count() or 1
|
|
|
|
|
stop: float = time.perf_counter() + secs
|
|
|
|
|
procs = [
|
|
|
|
|
ctx.Process(target=_burn, args=(stop,), daemon=True)
|
|
|
|
|
for _ in range(ncpu)
|
|
|
|
|
]
|
|
|
|
|
for p in procs:
|
|
|
|
|
p.start()
|
|
|
|
|
|
|
|
|
|
# skip the ~0.4s boost window so we sample the steady
|
|
|
|
|
# state AFTER any power-cap has engaged.
|
|
|
|
|
samples: list[int] = []
|
|
|
|
|
time.sleep(0.4)
|
|
|
|
|
while time.perf_counter() < stop - 0.1:
|
|
|
|
|
curs: list[int] = [
|
|
|
|
|
v for f in glob.glob(
|
|
|
|
|
'/sys/devices/system/cpu/cpu[0-9]*/cpufreq/scaling_cur_freq'
|
|
|
|
|
)
|
|
|
|
|
if (v := _read_mhz(f)) is not None
|
|
|
|
|
]
|
|
|
|
|
if curs:
|
|
|
|
|
samples.append(sum(curs) // len(curs))
|
|
|
|
|
time.sleep(0.15)
|
|
|
|
|
for p in procs:
|
|
|
|
|
p.join()
|
|
|
|
|
|
|
|
|
|
if not samples:
|
|
|
|
|
return 1.
|
|
|
|
|
frac: float = (sum(samples) // len(samples)) / pkg_max
|
|
|
|
|
# below the gate we read it as a power-cap throttle. The
|
|
|
|
|
# spawn/IPC/fork-bound work these budgets guard slows ~1:1
|
|
|
|
|
# with the achieved-vs-max freq ratio, so compensate by the
|
|
|
|
|
# FULL inverse fraction (a boost-discounted factor
|
|
|
|
|
# under-shoots and still trips the marginal cases).
|
|
|
|
|
if frac >= throttle_gate:
|
|
|
|
|
return 1.
|
|
|
|
|
return min(max_headroom, 1. / frac)
|
|
|
|
|
|
|
|
|
|
except Exception:
|
|
|
|
|
return 1.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def cpu_perf_headroom() -> float:
|
|
|
|
|
'''
|
|
|
|
|
Latency-headroom multiplier (>= 1.0) covering BOTH cpu-perf
|
|
|
|
|
throttle classes — multiply a test's deadline by it, e.g.
|
|
|
|
|
`timeout *= cpu_perf_headroom()`:
|
|
|
|
|
|
|
|
|
|
- static cpu-freq scaling — via `cpu_scaling_factor()`
|
|
|
|
|
(governor/policy lowered the `scaling_max_freq` ceiling).
|
|
|
|
|
|
|
|
|
|
- sustained-load power-cap throttle — via
|
|
|
|
|
`_measure_sustained_headroom()` (firmware/EC PPT/STAPM
|
|
|
|
|
clamps achieved freq under load while every static knob
|
|
|
|
|
reads "performance"; INVISIBLE to the static check). This
|
|
|
|
|
is the gremlin behind mass `trio` deadline-miss failures
|
|
|
|
|
on unchanged code — see
|
|
|
|
|
`ai/conc-anal/trio_033_cancel_cascade_slowdown_depth3_issue.md`.
|
|
|
|
|
|
|
|
|
|
The sustained probe runs ONCE per session (cached); the cost
|
|
|
|
|
is a ~0.9s all-core burn on first call only.
|
|
|
|
|
|
|
|
|
|
'''
|
|
|
|
|
global _sustained_headroom
|
|
|
|
|
static: float = cpu_scaling_factor()
|
|
|
|
|
if _non_linux:
|
|
|
|
|
return static
|
|
|
|
|
if _sustained_headroom is None:
|
|
|
|
|
_sustained_headroom = _measure_sustained_headroom()
|
|
|
|
|
return max(static, _sustained_headroom)
|
|
|
|
|
|
|
|
|
|
|
Lift `--ll`/`--tl` to plugin + `LogSpec` API
Two coupled changes that let downstream projects (eg. `modden`) inherit
the test-harness loglevel plumbing for free via
`tractor._testing.pytest`:
Plugin lift (`tests/conftest.py` → `_testing/pytest.py`),
- mv `pytest_addoption(--ll)`, the `loglevel` autouse
fixture, and `test_log` fixture out of the test-suite-
local conftest into the reusable plugin.
- add `--tl`/`--tractor-loglevel` as a DISTINCT flag from
`--ll`: `--ll` is the consuming-project's OWN app
loglevel (scoped to its pkg-hierarchy), `--tl` is the
`tractor.*` runtime loglevel. `--tl` falls back to
`--ll` when unset (preserves current `tractor`-suite
behavior).
- add `testing_pkg_name` session fixture (default
`'tractor'`) — downstream projects override to e.g.
`'modden'` so `--ll` scopes to their own hierarchy
instead of `tractor.*`.
- `loglevel` fixture now yields the resolved
tractor-runtime level (passed to
`open_root_actor(loglevel=<.>)` by `@tractor_test`)
AND separately applies `--ll` to the
`testing_pkg_name` hierarchy when that isn't
`tractor`. `test_log` scopes the per-test logger to
`testing_pkg_name`.
`tractor.log` "logging-spec" mini-DSL,
- `LogSpec = str|bool`. Accepted forms:
- `True` → enable `pkg_name` root at `default_level`
(fallback `'cancel'`).
- `False` → no-op.
- bare level eg. `'info'` → root-logger at that level.
- `'sub:info,x:cancel'` → per-sub-logger filter-spec;
each `<name>` is RELATIVE to `pkg_name` (must NOT
include the pkg-token).
- `parse_logspec()` → `{sublog|None: level}` mapping.
`None` key = root-logger. Mixed bare-level + filters
in one spec is rejected w/ a helpful err msg; so is
embedding the `pkg_name` token in a sub-name.
- `apply_logspec()` → `(primary_level, {name: log})`:
parses then enables a `colorlog` stderr handler per
named (sub)logger. Authoritative sub-logger filters
get `propagate=False` so they don't double-emit
through a parallel root-level handler.
- !GRANULARITY CAVEAT! sub-logger names match at
sub-pkg granularity, not leaf-module — so `devx.debug`
collapses to the same `tractor.devx` logger as a bare
`devx`, and top-level lib modules (eg.
`tractor.to_asyncio`) emit under the *root* logger
rather than a phantom `to_asyncio` child. Documented
inline on `LogSpec`.
Other,
- `tests/conftest.py` keeps a NOTE pointing to the
plugin for future-debugging clarity (don't remove
silently — the lift is the relevant signal).
(this patch was generated in some part by [`claude-code`][claude-code-gh])
[claude-code-gh]: https://github.com/anthropics/claude-code
2026-05-29 21:43:55 +00:00
|
|
|
# NOTE, the `--ll`/`--tl` CLI flags + the `loglevel`, `test_log`
|
|
|
|
|
# and `testing_pkg_name` fixtures have been factored into the
|
|
|
|
|
# `tractor._testing.pytest` plugin (loaded via the `-p` entry in
|
|
|
|
|
# `pyproject.toml`'s `[tool.pytest.ini_options]`) so downstream
|
|
|
|
|
# consuming projects (eg. `modden`) inherit them for free. The
|
|
|
|
|
# plugin's `testing_pkg_name` fixture defaults to `'tractor'`, so
|
|
|
|
|
# this suite keeps treating `--ll` as the runtime loglevel.
|
2026-03-02 19:57:40 +00:00
|
|
|
|
|
|
|
|
|
2020-07-26 01:20:34 +00:00
|
|
|
@pytest.fixture(scope='session')
|
2020-09-03 12:44:24 +00:00
|
|
|
def ci_env() -> bool:
|
2024-03-12 19:48:20 +00:00
|
|
|
'''
|
2025-04-04 04:05:55 +00:00
|
|
|
Detect CI environment.
|
2024-03-12 19:48:20 +00:00
|
|
|
|
|
|
|
|
'''
|
2022-07-12 17:49:36 +00:00
|
|
|
return _ci_env
|
2020-07-26 01:20:34 +00:00
|
|
|
|
|
|
|
|
|
2025-04-03 02:40:28 +00:00
|
|
|
def sig_prog(
|
|
|
|
|
proc: subprocess.Popen,
|
|
|
|
|
sig: int,
|
2026-04-14 17:57:01 +00:00
|
|
|
canc_timeout: float = 0.2,
|
|
|
|
|
tries: int = 3,
|
2025-04-03 02:40:28 +00:00
|
|
|
) -> int:
|
2026-04-14 17:57:01 +00:00
|
|
|
'''
|
|
|
|
|
Kill the actor-process with `sig`.
|
|
|
|
|
|
|
|
|
|
Prefer to kill with the provided signal and
|
|
|
|
|
failing a `canc_timeout`, send a `SIKILL`-like
|
|
|
|
|
to ensure termination.
|
|
|
|
|
|
|
|
|
|
'''
|
|
|
|
|
for i in range(tries):
|
|
|
|
|
proc.send_signal(sig)
|
2026-04-14 22:32:55 +00:00
|
|
|
if proc.poll() is None:
|
2026-04-14 17:57:01 +00:00
|
|
|
print(
|
|
|
|
|
f'WARNING, proc still alive after,\n'
|
|
|
|
|
f'canc_timeout={canc_timeout!r}\n'
|
|
|
|
|
f'sig={sig!r}\n'
|
|
|
|
|
f'\n'
|
|
|
|
|
f'{proc.args!r}\n'
|
|
|
|
|
)
|
|
|
|
|
time.sleep(canc_timeout)
|
|
|
|
|
else:
|
2020-08-03 18:49:46 +00:00
|
|
|
# TODO: why sometimes does SIGINT not work on teardown?
|
|
|
|
|
# seems to happen only when trace logging enabled?
|
2026-04-14 22:32:55 +00:00
|
|
|
if proc.poll() is None:
|
2026-04-14 17:57:01 +00:00
|
|
|
print(
|
|
|
|
|
f'XXX WARNING KILLING PROG WITH SIGINT XXX\n'
|
|
|
|
|
f'canc_timeout={canc_timeout!r}\n'
|
|
|
|
|
f'{proc.args!r}\n'
|
|
|
|
|
)
|
|
|
|
|
proc.send_signal(_KILL_SIGNAL)
|
|
|
|
|
|
2025-04-03 02:40:28 +00:00
|
|
|
ret: int = proc.wait()
|
2020-08-03 18:49:46 +00:00
|
|
|
assert ret
|
|
|
|
|
|
|
|
|
|
|
2026-05-06 17:23:42 +00:00
|
|
|
# NOTE, the `daemon` fixture (+ its `_wait_for_daemon_ready`
|
|
|
|
|
# helper + the post-yield teardown drain logic) has been
|
|
|
|
|
# moved to `tests/discovery/conftest.py` since 100% of its
|
|
|
|
|
# consumers are discovery-protocol tests now living under
|
|
|
|
|
# that subdir. See:
|
|
|
|
|
# - `tests/discovery/test_multi_program.py`
|
|
|
|
|
# - `tests/discovery/test_registrar.py`
|
|
|
|
|
# - `tests/discovery/test_tpt_bind_addrs.py`
|
2025-04-03 02:40:28 +00:00
|
|
|
|
2025-04-04 04:05:55 +00:00
|
|
|
|
2025-04-03 02:40:28 +00:00
|
|
|
# @pytest.fixture(autouse=True)
|
|
|
|
|
# def shared_last_failed(pytestconfig):
|
|
|
|
|
# val = pytestconfig.cache.get("example/value", None)
|
|
|
|
|
# breakpoint()
|
|
|
|
|
# if val is None:
|
|
|
|
|
# pytestconfig.cache.set("example/value", val)
|
|
|
|
|
# return val
|
2025-04-04 04:05:55 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
# TODO: a way to let test scripts (like from `examples/`)
|
|
|
|
|
# guarantee they won't `registry_addrs` collide!
|
|
|
|
|
# -[ ] maybe use some kinda standard `def main()` arg-spec that
|
|
|
|
|
# we can introspect from a fixture that is called from the test
|
|
|
|
|
# body?
|
|
|
|
|
# -[ ] test and figure out typing for below prototype! Bp
|
|
|
|
|
#
|
|
|
|
|
# @pytest.fixture
|
|
|
|
|
# def set_script_runtime_args(
|
|
|
|
|
# reg_addr: tuple,
|
|
|
|
|
# ) -> Callable[[...], None]:
|
|
|
|
|
|
|
|
|
|
# def import_n_partial_in_args_n_triorun(
|
|
|
|
|
# script: Path, # under examples?
|
|
|
|
|
# **runtime_args,
|
|
|
|
|
# ) -> Callable[[], Any]: # a `partial`-ed equiv of `trio.run()`
|
|
|
|
|
|
|
|
|
|
# # NOTE, below is taken from
|
|
|
|
|
# # `.test_advanced_faults.test_ipc_channel_break_during_stream`
|
|
|
|
|
# mod: ModuleType = import_path(
|
|
|
|
|
# examples_dir() / 'advanced_faults'
|
|
|
|
|
# / 'ipc_failure_during_stream.py',
|
|
|
|
|
# root=examples_dir(),
|
|
|
|
|
# consider_namespace_packages=False,
|
|
|
|
|
# )
|
|
|
|
|
# return partial(
|
|
|
|
|
# trio.run,
|
|
|
|
|
# partial(
|
|
|
|
|
# mod.main,
|
|
|
|
|
# **runtime_args,
|
|
|
|
|
# )
|
|
|
|
|
# )
|
|
|
|
|
# return import_n_partial_in_args_n_triorun
|