Тестирование стало проще с помощью trio.testing
Модуль trio.testing предоставляет различные утилиты, упрощающие тестирование кода Trio. В отличие от других подмодулей в пространстве имён trio, модуль trio.testing не импортируется автоматически при выполнении import trio; его необходимо импортировать явно import trio.testing.
Интеграция с тестовым каркасом
@trio.testing.trio_test
Время и таймауты
trio.testing.MockClock — это Clock с несколькими хитростями, помогающими эффективно тестировать код с таймаутами:
По умолчанию, он начинается с времени 0, и время по часам увеличивается только при явном вызове
jump(). Это обеспечивает чрезвычайно управляемый механизм времени для тестирования.Можно установить
rateв 1.0, если нужно, чтобы он работал в реальном времени, как обычные часы. Можно останавливать и запускать часы в рамках теста. Можно установитьrateв 10.0, чтобы время по часам проходило в 10 раз быстрее (например,await trio.sleep(10)вернётся через 1 секунду).Более интересно, можно установить
autojump_thresholdв ноль или малое значение, и тогда он будет следить за выполнением цикла обработки событий, а всякий раз, когда все завершится, и все ждут таймаута, он переведёт часы вперёд до этого таймаута. Во многих случаях это позволяет естественно выглядящему коду, включающему таймауты, работать автоматически с максимальной загрузкой ЦП без изменений. (Спасибо fluxcapacitor за эту замечательную идею.)Конечно, всё это можно смешивать и сочетать по своему усмотрению.
Независимо от этих ухищрений, внутри Trio течение времени всё ещё выглядит нормальным, если вы ограничиваетесь функциями времени Trio (см. Время и часы). Ниже приведен пример, демонстрирующий два разных способа ускорения времени. Обратите внимание, как в обоих случаях две задачи сохраняют согласованное представление о реальности, и события происходят в ожидаемой последовательности, несмотря на значительное расхождение с реальным временем:
# across-realtime.py
import time
import trio
import trio.testing
YEAR = 365 * 24 * 60 * 60 # seconds
async def task1():
start = trio.current_time()
print("task1: sleeping for 1 year")
await trio.sleep(YEAR)
duration = trio.current_time() - start
print(f"task1: woke up; clock says I've slept {duration / YEAR} years")
print("task1: sleeping for 1 year, 100 times")
for _ in range(100):
await trio.sleep(YEAR)
duration = trio.current_time() - start
print(f"task1: slept {duration / YEAR} years total")
async def task2():
start = trio.current_time()
print("task2: sleeping for 5 years")
await trio.sleep(5 * YEAR)
duration = trio.current_time() - start
print(f"task2: woke up; clock says I've slept {duration / YEAR} years")
print("task2: sleeping for 500 years")
await trio.sleep(500 * YEAR)
duration = trio.current_time() - start
print(f"task2: slept {duration / YEAR} years total")
async def main():
async with trio.open_nursery() as nursery:
nursery.start_soon(task1)
nursery.start_soon(task2)
def run_example(clock):
real_start = time.perf_counter()
trio.run(main, clock=clock)
real_duration = time.perf_counter() - real_start
print(f"Total real time elapsed: {real_duration} seconds")
print("Clock where time passes at 100 years per second:\n")
run_example(trio.testing.MockClock(rate=100 * YEAR))
print("\nClock where time automatically skips past the boring parts:\n")
run_example(trio.testing.MockClock(autojump_threshold=0)) Вывод:
Clock where time passes at 100 years per second: task2: sleeping for 5 years task1: sleeping for 1 year task1: woke up; clock says I've slept 1.0365006048232317 years task1: sleeping for 1 year, 100 times task2: woke up; clock says I've slept 5.0572111969813704 years task2: sleeping for 500 years task1: slept 104.77677842136472 years total task2: slept 505.25014589075 years total Total real time elapsed: 5.053582429885864 seconds Clock where time automatically skips past the boring parts: task2: sleeping for 5 years task1: sleeping for 1 year task1: woke up; clock says I've slept 1.0 years task1: sleeping for 1 year, 100 times task2: woke up; clock says I've slept 5.0 years task2: sleeping for 500 years task1: slept 101.0 years total task2: slept 505.0 years total Total real time elapsed: 0.019298791885375977 seconds
-
Управляемый пользователем механизм часов, подходящий для написания тестов.
-
autojump_threshold (float) – начальный
autojump_threshold.
Параметры:
-
Количество секунд времени по часам, проходящих за секунду реального времени. Значение по умолчанию равно 0.0, т.е. часы увеличиваются только при ручных вызовах
jump()или при срабатыванииautojump_threshold. Вы можете изменить это значение.
rate-
Часы отслеживают цикл обработки событий, и если в какой-то момент обнаруживают, что все задачи заблокированы в течение этого количества реальных секунд (т.е. по реальным часам, а не по этим часам), то часы автоматически переходят к следующему запланированному таймауту цикла обработки событий. По умолчанию это
math.inf, т.е. никогда не происходит автоматического перехода. Вы можете изменить это значение.В принципе, идея заключается в том, что если у вас есть код или тесты, использующие паузы и таймауты, то вы можете использовать это, чтобы запустить их гораздо быстрее, полностью автоматически. (По крайней мере, пока эти паузы/таймауты происходят внутри Trio; если ваш тест включает взаимодействие с внешней службой и ожидание её таймаута, то, очевидно, мы вам не поможем.)
Вы должны установить это значение на минимальное значение, которое позволит избежать «ложных срабатываний», где какое-то ввод/вывод (например, между двумя половинами пары сокетов) находится в процессе, но порог срабатывает, и время всё равно увеличивается. Это будет зависеть от деталей ваших тестов и тестовой среды. Если вы не выполняете ввод/вывод (как в приведённом выше примере с ожиданием), то установите его в ноль, и часы будут переключаться всякий раз, когда все задачи заблокированы.
Примечание
Если вы используете
autojump_thresholdиwait_all_tasks_blockedодновременно, то вы можете задаться вопросом, как они взаимодействуют, так как оба вызывают события после того, как цикл обработки событий становится неактивным в течение некоторого времени. Ответ:wait_all_tasks_blockedимеет приоритет. Если есть задача, заблокированная вwait_all_tasks_blocked, то функция автоматического перехода рассматривает её как активную задачу и не переводит часы.
autojump_threshold-
Ручное продвижение часов на заданное количество секунд.
-
seconds (float) – количество секунд, на которое нужно продвинуть часы вперёд.
-
ValueError – если вы пытаетесь передать отрицательное значение для
seconds.
Параметры:
Исключения:
-
jump(seconds: float) → None -
class trio.testing.MockClock(rate: float = 0.0, autojump_threshold: float = inf)
Порядок выполнения задач
-
Удобный класс для принудительного выполнения кода в разных задачах в явном линейном порядке.
Экземпляры этого класса реализуют метод
__call__, который возвращает асинхронный менеджер контекста. Идея состоит в том, что вы передаете порядковый номер в__call__, чтобы указать, куда этот блок кода должен поместиться в линейной последовательности. Блок 0 запускается немедленно, а затем блок N не запускается до тех пор, пока блок N-1 не завершит работу.Пример
Крайне сложный способ вывести числа от 0 до 5 в порядке:
async def worker1(seq): async with seq(0): print(0) async with seq(4): print(4) async def worker2(seq): async with seq(2): print(2) async with seq(5): print(5) async def worker3(seq): async with seq(1): print(1) async with seq(3): print(3) async def main(): seq = trio.testing.Sequencer() async with trio.open_nursery() as nursery: nursery.start_soon(worker1, seq) nursery.start_soon(worker2, seq) nursery.start_soon(worker3, seq)
class trio.testing.Sequencer
-
Ожидание, пока не останется задач, готовых к выполнению.
Это полезно при тестировании кода, когда вы хотите дать другим задачам возможность «успокоиться». Вызывающая задача блокируется и не разблокируется до тех пор, пока все другие задачи также не заблокированы не менее чем на
cushionсекунды. (Установка ненулевого значенияcushionпредназначена для обработки случаев, подобных двум задачам, общающимся друг с другом через локальный сокет, где мы хотим проигнорировать потенциальный краткий момент между отправкой и получением, когда все задачи заблокированы.)Обратите внимание, что измерение
cushionвыполняется в реальном времени, а не в времени трио.Если несколько задач заблокированы в
wait_all_tasks_blocked(), то разблокируется та, у которой наименьшее времяcushion, и разблокирование этой задачи сбрасывает таймеры для оставшихся задач. Если у нескольких задач одинаковое времяcushion, то все они разблокируются.Вы также можете рассмотреть
trio.testing.Sequencer, который предоставляет более явный способ управления порядком выполнения в рамках теста и часто создает более читаемые тесты.Пример
Вот пример того, как можно проверить, что блокировки трио справедливы: мы берем блокировку в родительском процессе, запускаем дочерний процесс, ждем, пока дочерний процесс заблокируется, ожидая блокировки (!), и затем проверяем, что мы не можем освободить и сразу же повторно получить блокировку:
async def lock_taker(lock): await lock.acquire() lock.release() async def test_lock_fairness(): lock = trio.Lock() await lock.acquire() async with trio.open_nursery() as nursery: nursery.start_soon(lock_taker, lock) # child hasn't run yet, we have the lock assert lock.locked() assert lock._owner is trio.lowlevel.current_task() await trio.testing.wait_all_tasks_blocked() # now the child has run and is blocked on lock.acquire(), we # still have the lock assert lock.locked() assert lock._owner is trio.lowlevel.current_task() lock.release() try: # The child has a prior claim, so we can't have it lock.acquire_nowait() except trio.WouldBlock: assert lock._owner is not trio.lowlevel.current_task() print("PASS") else: print("FAIL")
await trio.testing.wait_all_tasks_blocked(cushion: float = 0.0) → None
-
Ожидание, пока ни одна нить не будет продолжать работу с задачами.
Это предназначено для использования при тестировании кода с помощью `trio.to_thread`, чтобы убедиться, что ни одна задача не делает дальнейшего прогресса в потоке. Пример использования приведен в следующем коде:
async def wait_all_settled(): while True: await trio.testing.wait_all_threads_complete() await trio.testing.wait_all_tasks_blocked() if trio.testing.active_thread_count() == 0: break
await trio.testing.wait_all_threads_completed() → None
-
Возвращает количество потоков, которые в данный момент выполняют задачу.
trio.testing.active_thread_count() → int
Потоки
Подключение к серверу сокета в том же процессе
-
Подключение к заданному
SocketListener.Это особенно полезно в тестах, когда вы хотите, чтобы сервер выбирал свой собственный порт, а затем подключаться к нему:
listeners = await trio.open_tcp_listeners(0) client = await trio.testing.open_stream_to_socket_listener(listeners[0])
-
socket_listener (SocketListener) –
SocketListenerдля подключения. -
поток, подключенный к данному слушателю.
Параметры:
Возвращаемое значение:
Тип возвращаемого значения:
-
await trio.testing.open_stream_to_socket_listener(socket_listener: SocketListener) → SocketStream
Виртуальные, управляемые потоки
Одна из особенно сложных проблем при тестировании сетевых протоколов заключается в том, чтобы убедиться, что ваша реализация может обрабатывать данные, поток которых разделяется странными способами и поступают со странными задержками: соединения localhost обычно ведут себя гораздо лучше, чем реальные сети, поэтому, если вы тестируете только на localhost, позже могут возникнуть проблемы. Для облегчения вашей задачи Trio предоставляет полностью имитируемые реализации интерфейсов потоков (см. Абстрактный интерфейс потоков), которые позволяют писать тесты с различными интересными ошибками.
Здесь есть несколько элементов, поэтому вот как они связаны:
memory_stream_pair() предоставляет пару связанных двунаправленных потоков. Это похоже на socket.socketpair(), но без участия раздражающей операционной системы и ее стека сетевых протоколов.
Для создания двунаправленного потока memory_stream_pair() использует два однонаправленных потока. Эти потоки получаются при вызове memory_stream_one_way_pair().
memory_stream_one_way_pair(), в свою очередь, реализован с помощью низкоуровневых классов MemorySendStream и MemoryReceiveStream. Это реализации (вы угадали) trio.abc.SendStream и trio.abc.ReceiveStream, которые сами по себе не привязаны ни к чему — «отправка» и «получение» просто помещают данные в и из внутренней частной буфера, принадлежащего каждому объекту. У них также есть интересные крючки, которые позволяют настроить поведение их методов. Именно здесь вы можете вставить свои злонамеренные элементы, если захотите. memory_stream_one_way_pair() использует эти крючки достаточно стандартным способом: просто устанавливает их так, что при вызове send_all или при закрытии потока отправки автоматически вызывается memory_stream_pump(), который является удобной функцией для извлечения данных из буфера MemorySendStream и помещения их в буфер MemoryReceiveStream. Но это стандартное поведение — вы можете заменить его любым желаемым поведением.
Trio также предоставляет специализированные функции для тестирования полностью безбуферных потоков: lockstep_stream_one_way_pair() и lockstep_stream_pair(). Они не настраиваются, но демонстрируют экстремальное поведение, хорошо подходящее для обнаружения особых случаев в реализациях протоколов.
Подробности API
-
Внутренняя память
SendStream.-
send_all_hook – Асинхронная функция или None. Вызывается из
send_all(). Может выполнять любые действия.wait_send_all_might_not_block_hook – Асинхронная функция или None. Вызывается из
wait_send_all_might_not_block(). Может выполнять любые действия.close_hook – Синхронная функция или None. Вызывается из
close()иaclose(). Может выполнять любые действия.
Параметры:
-
Все эти хуки также доступны в качестве атрибутов объекта, и вы можете изменять их в любое время.
send_all_hookwait_send_all_might_not_block_hookclose_hook-
То же, что и
close(), но асинхронно.
await aclose() → None-
Помечает поток как закрытый, а затем вызывает
close_hook(если он существует).
close() → None-
Получает данные из внутреннего буфера, блокируясь при необходимости.
-
max_bytes (int или None) – Максимальный объем данных для получения. None (по умолчанию) означает получение всех имеющихся данных (но всё равно блокируется, пока не будет доступен хотя бы один байт).
-
Если поток закрыт, пустой массив байтов. В противном случае — запрошенные данные.
Параметры:
Возвращает:
-
await get_data(max_bytes: int | None = None) → bytearray-
Получает данные из внутреннего буфера, но не блокируется.
См.
get_data()для подробностей.-
trio.WouldBlock – если данных для получения нет.
Исключения:
-
get_data_nowait(max_bytes: int | None = None) → bytearray-
Размещает заданные данные во внутреннем буфере объекта и затем вызывает
send_all_hook(если он существует).
await send_all(data: bytes | bytearray | memoryview) → None-
Вызывает
wait_send_all_might_not_block_hook(если он существует), а затем возвращается немедленно.
await wait_send_all_might_not_block() → None -
class trio.testing.MemorySendStream(send_all_hook: Callable[[], Awaitable[object]] | None = None, wait_send_all_might_not_block_hook: Callable[[], Awaitable[object]] | None = None, close_hook: Callable[[], object] | None = None)
-
Внутренняя память
ReceiveStream.-
receive_some_hook – Асинхронная функция или None. Вызывается из
receive_some(). Может выполнять любые действия.close_hook – Синхронная функция или None. Вызывается из
close()иaclose(). Может выполнять любые действия.
Параметры:
-
Оба хука также доступны в качестве атрибутов объекта, и вы можете изменять их в любое время.
receive_some_hookclose_hook-
То же, что и
close(), но асинхронно.
await aclose() → None-
Отбрасывает любые ожидающие данные из внутреннего буфера и отмечает поток как закрытый.
close() → None-
Добавляет заданные данные в внутренний буфер.
put_data(data: bytes | bytearray | memoryview) → None-
Добавляет маркер конца файла во внутренний буфер.
put_eof() → None-
Вызывает
receive_some_hook(если он существует), а затем получает данные из внутреннего буфера, блокируясь при необходимости.
await receive_some(max_bytes: int | None = None) → bytearray -
class trio.testing.MemoryReceiveStream(receive_some_hook: Callable[[], Awaitable[object]] | None = None, close_hook: Callable[[], object] | None = None)
-
Извлекает данные из внутреннего буфера данного
MemorySendStreamи помещает их во внутренний буфер данногоMemoryReceiveStream.-
memory_send_stream (MemorySendStream) – Поток для извлечения данных.
memory_receive_stream (MemoryReceiveStream) – Поток для помещения данных.
max_bytes (int или None) – Максимальное количество данных для передачи в этом вызове, или None для передачи всех доступных данных.
-
True, если данные были успешно переданы, или False, если данных для передачи не было.
Параметры:
Возвращает:
Используется для реализации
memory_stream_one_way_pair()иmemory_stream_pair(); см. строку документации последнего для примера использования. -
trio.testing.memory_stream_pump(memory_send_stream: MemorySendStream, memory_receive_stream: MemoryReceiveStream, *, max_bytes: int | None = None) → bool
-
Создайте подключённый, чисто-Python-потоковый объект, однонаправленный, с бесконечным буферированием и гибкими параметрами конфигурации.
Вы можете рассматривать его как версию потока Trio, не использующего операционную систему, аналогичную
os.pipe()(за исключением того, чтоos.pipe()возвращает потоки в неправильном порядке – мы следуем превосходной конвенции, что данные текут слева направо).-
Кортеж (
MemorySendStream,MemoryReceiveStream), где уMemorySendStreamустановлены ссылки, так что он вызываетmemory_stream_pump()из егоsend_all_hookиclose_hook.
Возвращает:
В итоге данные автоматически передаются от
MemorySendStreamкMemoryReceiveStream. Но вы также можете переупорядочивать их как вам угодно. Например, вы можете временно установитьsend_all_hookв None, чтобы смоделировать остановку передачи данных. Или посмотритеmemory_stream_pair()для более подробного примера. -
trio.testing.memory_stream_one_way_pair() → tuple[MemorySendStream, MemoryReceiveStream]
-
Создайте подключённый, чисто-Python-потоковый объект, двунаправленный, с бесконечным буферированием и гибкими параметрами конфигурации.
Это вспомогательная функция, которая создаёт два однонаправленных потока с помощью
memory_stream_one_way_pair(), а затем используетStapledStreamдля объединения их в один двунаправленный поток.Это аналогично версии потока Trio, не использующей операционную систему, аналогичной
socket.socketpair().-
Пара объектов
StapledStream, которые соединены таким образом, что данные автоматически передаются из одного в другой в обоих направлениях.
Возвращает:
После создания пары потоков вы можете отправлять данные туда-сюда, что достаточно для простых тестов:
left, right = memory_stream_pair() await left.send_all(b"123") assert await right.receive_some() == b"123" await right.send_all(b"456") assert await left.receive_some() == b"456"
Но если вы прочитаете документацию по
StapledStreamиmemory_stream_one_way_pair(), вы увидите, что все компоненты, участвующие в их связывании, являются общедоступными API, поэтому вы можете настроить их в соответствии с требованиями ваших тестов. Например, вот как можно настроить поток таким образом, чтобы данные, текущие слева направо, передавались по одному байту за раз (но данные, текущие справа налево, передаются с полной скоростью):left, right = memory_stream_pair() async def trickle(): # left is a StapledStream, and left.send_stream is a MemorySendStream # right is a StapledStream, and right.recv_stream is a MemoryReceiveStream while memory_stream_pump(left.send_stream, right.recv_stream, max_bytes=1): # Pause between each byte await trio.sleep(1) # Normally this send_all_hook calls memory_stream_pump directly without # passing in a max_bytes. We replace it with our custom version: left.send_stream.send_all_hook = trickleА вот простой тест с использованием изменённых объектов потока:
async def sender(): await left.send_all(b"12345") await left.send_eof() async def receiver(): async for data in right: print(data) async with trio.open_nursery() as nursery: nursery.start_soon(sender) nursery.start_soon(receiver)По умолчанию это выведет
b"12345"и немедленно завершится; с потоком-протоком он вместо этого спит 1 секунду, затем выводитb"1", затем спит 1 секунду, затем выводитb"2"и так далее.Совет: вы можете вставлять вызовы sleep (как в нашем примере выше), чтобы управлять потоком данных через задачи… а затем использовать
MockClockи его функциюautojump_threshold, чтобы держать ваш набор тестов быстро работающим.Если вы хотите провести стресс-тестирование реализации протокола, один из хороших приёмов – использовать модуль
random(предпочтительно с фиксированным значением генератора псевдослучайных чисел) для перемещения произвольного количества байтов за раз и вставки случайных задержек между ними. Вы также можете настроить пользовательскийreceive_some_hook, если хотите управлять вещами со стороны приёма, а не только со стороны отправки. -
trio.testing.memory_stream_pair() → tuple[StapledStream[MemorySendStream, MemoryReceiveStream], StapledStream[MemorySendStream, MemoryReceiveStream]]
-
Создайте подключённый, чисто-Python-потоковый объект, однонаправленный, где данные передаются в такт.
-
Кортеж (
SendStream,ReceiveStream).
Возвращает:
В этом потоке абсолютно нет буферизации. Каждый вызов
send_all()будет блокировать выполнение, пока все предоставленные данные не будут возвращены вызовомreceive_some().Это может быть полезно для тестирования механизмов управления потоком в крайнем случае или для создания «перегруженных» потоков для использования с
check_one_way_stream()и т. п.В дополнение к реализации интерфейсов
SendStreamиReceiveStream, возвращаемые объекты также имеют синхронный методclose. -
trio.testing.lockstep_stream_one_way_pair() → tuple[SendStream, ReceiveStream]
-
Создайте подключённый, чисто-Python-потоковый объект, двунаправленный, где данные передаются в такт.
-
Кортеж (
StapledStream,StapledStream).
Возвращает:
Это вспомогательная функция, которая создаёт два однонаправленных потока с помощью
lockstep_stream_one_way_pair(), а затем используетStapledStreamдля объединения их в один двунаправленный поток. -
trio.testing.lockstep_stream_pair() → tuple[StapledStream[SendStream, ReceiveStream], StapledStream[SendStream, ReceiveStream]]
Тестирование собственных реализаций потоков
Trio также предоставляет некоторые функции, которые помогут вам протестировать ваши собственные реализации потоков:
-
Выполните ряд общих тестов для реализации пользовательского одностороннего потока.
-
stream_maker – Асинхронная (!) функция, которая возвращает соединённую пару (
SendStream,ReceiveStream).clogged_stream_maker – Либо None, либо асинхронная функция, подобная stream_maker, но с дополнительным свойством, что возвращаемый поток находится в состоянии, когда
send_allиwait_send_all_might_not_blockбудут заблокированы до тех пор, пока не будет вызваноreceive_some. Это позволяет более тщательно протестировать некоторые граничные случаи, особенно вокругwait_send_all_might_not_block.
-
AssertionError – если тест завершится неудачей.
Параметры:
Исключения:
-
await trio.testing.check_one_way_stream(stream_maker: Callable[[], Awaitable[tuple[SendStream, ReceiveStream]]], clogged_stream_maker: Callable[[], Awaitable[tuple[SendStream, ReceiveStream]]] | None) → None
-
Выполните ряд общих тестов для реализации пользовательского двустороннего потока.
Это аналогично
check_one_way_stream(), за исключением того, что ожидается, что функции-создатели будут возвращать объекты, реализующие интерфейсStream.Эта функция проверяет более широкую область, чем
check_one_way_stream()– если вы её вызываете, то вам не нужно также вызыватьcheck_one_way_stream().
await trio.testing.check_two_way_stream(stream_maker: Callable[[], Awaitable[tuple[Stream, Stream]]], clogged_stream_maker: Callable[[], Awaitable[tuple[Stream, Stream]]] | None) → None
-
Выполните ряд общих тестов для реализации пользовательского потока с возможностью полузакрытия.
Это аналогично
check_two_way_stream(), за исключением того, что ожидается, что функции-создатели будут возвращать объекты, реализующие интерфейсHalfCloseableStream.Эта функция проверяет более широкую область, чем
check_two_way_stream()– если вы её вызываете, то вам не нужно также вызыватьcheck_two_way_stream().
await trio.testing.check_half_closeable_stream(stream_maker: Callable[[], Awaitable[tuple[HalfCloseableStream, HalfCloseableStream]]], clogged_stream_maker: Callable[[], Awaitable[tuple[HalfCloseableStream, HalfCloseableStream]]] | None) → None
Виртуальная сеть для тестирования
В предыдущем разделе вы узнали, как использовать виртуальные потоки в памяти для тестирования протоколов, написанных с использованием абстракции Trio’s Stream. Но что делать, если у вас есть более сложный сетевой код – такой, который устанавливает соединения с несколькими хостами, открывает сокет для прослушивания или отправляет пакеты UDP?
Сам Trio не предоставляет виртуальную сетевую реализацию в памяти для тестирования – но модуль trio.socket предоставляет необходимые крючки для написания собственной! Если вы заинтересованы в реализации многоразовой виртуальной сети для тестирования, свяжитесь с нами здесь.
Обратите внимание, что эти API фактически находятся в trio.socket и trio.abc, но мы документируем их здесь, потому что они предназначены в первую очередь для тестирования.
-
Установите пользовательский разрешитель имен хоста.
По умолчанию функции Trio’s
getaddrinfo()иgetnameinfo()используют стандартные системные функции разрешения. Эта функция позволяет настроить это поведение. Основной случай использования – для тестирования, но она также может быть полезна для использования сторонних разрешителей, таких как c-ares (хотя будьте осторожны, они редко являются полными аналогами системного разрешителя). Подробнее см.trio.abc.HostnameResolver.Установка пользовательского разрешителя имен хоста влияет на все последующие вызовы
getaddrinfo()иgetnameinfo()внутри вложенного вызоваtrio.run(). Всё остальное разрешение имён хостов в Trio реализуется через эти функции.Как правило, вы должны вызвать эту функцию только один раз, в самом начале вашей программы.
-
hostname_resolver (trio.abc.HostnameResolver или None) – Новый пользовательский разрешитель имен хоста или None для восстановления поведения по умолчанию.
-
Предыдущий разрешитель имен хоста (который может быть None).
Параметры:
Возвращаемое значение:
-
trio.socket.set_custom_hostname_resolver(hostname_resolver: HostnameResolver | None) → HostnameResolver | None
-
Если у вас есть пользовательский разрешитель имен хоста, то реализация
HostnameResolverпозволяет зарегистрировать его для использования в Trio.См.
trio.socket.set_custom_hostname_resolver().-
Пользовательская реализация
getaddrinfo().Вызывается
trio.socket.getaddrinfo().Если
hostзадан как числовой IP-адрес, тоgetaddrinfo()может обработать запрос сам, а не вызывать этот метод.Любое необходимое кодирование IDNA выполняется перед вызовом этой функции; ваша реализация может считать, что никогда не увидит U-метки, например,
"café.com", и ей нужно обрабатывать только A-метки, например,b"xn--caf-dma.com".
abstractmethod await getaddrinfo(host: bytes | None, port: bytes | str | int | None, family: int = 0, type: int = 0, proto: int = 0, flags: int = 0) → list[tuple[AddressFamily, SocketKind, int, str, tuple[str, int] | tuple[str, int, int, int] | tuple[int, bytes]]]-
Пользовательская реализация
getnameinfo().Вызывается
trio.socket.getnameinfo().
abstractmethod await getnameinfo(sockaddr: tuple[str, int] | tuple[str, int, int, int], flags: int) → tuple[str, str] -
class trio.abc.HostnameResolver
-
Установите пользовательский фабричный метод для создания сокетов.
Эта функция позволяет заменить стандартный класс сокетов Trio на пользовательский. Это очень полезно для тестирования, и, вероятно, плохая идея в других случаях. Подробнее см.
trio.abc.HostnameResolver.Установка пользовательского фабричного метода для создания сокетов влияет на все последующие вызовы
socket()внутри вложенного вызоваtrio.run().Как правило, вы должны вызвать эту функцию только один раз, в самом начале вашей программы.
-
socket_factory (trio.abc.SocketFactory или None) – Новый пользовательский фабричный метод или None для восстановления поведения по умолчанию.
-
Предыдущий фабричный метод (который может быть None).
Параметры:
Возвращаемое значение:
-
trio.socket.set_custom_socket_factory(socket_factory: SocketFactory | None) → SocketFactory | None
-
Если вы пишете пользовательский класс, реализующий интерфейс сокетов Trio, то вы можете использовать
SocketFactoryдля того, чтобы Trio использовал его.См.
trio.socket.set_custom_socket_factory().-
Создайте и верните объект сокета.
Ваш объект сокета должен наследоваться от
trio.socket.SocketType, который является пустым классом, единственной целью которого является «разметка» классов, которые должны считаться валидными сокетами Trio.Вызывается
trio.socket.socket().Обратите внимание, что в отличие от
trio.socket.socket(), это не принимаетfileno=аргумент. Еслиfileno=задан, тоtrio.socket.socket()возвращает обычный объект сокета Trio вместо вызова этого метода.
abstractmethod socket(family: socket.AddressFamily | int = AddressFamily.AF_INET, type: socket.SocketKind | int = SocketKind.SOCK_STREAM, proto: int = 0) → SocketType -
class trio.abc.SocketFactory
Тестирование контрольных точек
-
Используйте в качестве менеджера контекста, чтобы проверить, что код внутри блока
withзавершается с исключением или выполняет по крайней мере одну контрольную точку.-
AssertionError – если никакая контрольная точка не была выполнена.
Исключения:
Пример
Проверка того, что
trio.sleep()является контрольной точкой, даже если она не блокирует:with trio.testing.assert_checkpoints(): await trio.sleep(0) -
with trio.testing.assert_checkpoints() → AbstractContextManager[None]
-
Используйте в качестве менеджера контекста, чтобы проверить, что код внутри блока
withне выполняет никаких контрольных точек.-
AssertionError – если была выполнена контрольная точка.
Исключения:
Пример
Синхронный код никогда не содержит контрольных точек, но мы можем это проверить:
send_channel, receive_channel = trio.open_memory_channel(10) with trio.testing.assert_no_checkpoints(): send_channel.send_nowait(None) -
with trio.testing.assert_no_checkpoints() → AbstractContextManager[None]
Справочные материалы по ExceptionGroup
-
Менеджер контекста для проверки ожидаемой
ExceptionGroup. Он работает аналогичноpytest.raises, и версия этой функции, надеемся, будет добавлена в исходный код, после чего эту функцию можно будет сделать устаревшей и удалить. См. https://github.com/pytest-dev/pytest/issues/11538Поведение при перехвате отличается от except* по многим аспектам, будучи по умолчанию более жёстким. Используя
allow_unwrapped=Trueиflatten_subgroups=True, можно полностью сопоставитьexcept*, когда ожидается единственная ошибка.-
Все указанные исключения должны присутствовать, и никаких других.
Если вы ожидаете переменное количество исключений, вам необходимо использовать
pytest.raises(ExceptionGroup)и вручную проверить содержащиеся исключения. Рассмотрите возможность использованияMatcher.matches().
-
По умолчанию он будет перехватывать только исключения, заключенные в группу исключений.
С помощью
allow_unwrapped=Trueможно указать единственное ожидаемое исключение илиMatcher, и оно будет соответствовать исключению, даже если оно не находится внутриExceptionGroup. Если вы ожидаете один из нескольких различных типов исключений, вам необходимо использовать объектMatcher.
-
По умолчанию он учитывает полную структуру с вложенными
ExceptionGroup. Можно указать вложенныеExceptionGroup, передав объектыRaisesGroupв качестве ожидаемых исключений.С помощью
flatten_subgroups=Trueон «развернёт» поднятуюExceptionGroup, извлекая все исключения внутри любых вложенныхExceptionGroup, прежде чем выполнять сопоставление.
Порядок исключений не важен, поэтому
RaisesGroups(ValueError, TypeError)эквивалентноRaisesGroups(TypeError, ValueError).Примеры:
with RaisesGroups(ValueError): raise ExceptionGroup("", (ValueError(),)) with RaisesGroups(ValueError, ValueError, Matcher(TypeError, match="expected int")): ... with RaisesGroups(KeyboardInterrupt, match="hello", check=lambda x: type(x) is BaseExceptionGroup): ... with RaisesGroups(RaisesGroups(ValueError)): raise ExceptionGroup("", (ExceptionGroup("", (ValueError(),)),)) # flatten_subgroups with RaisesGroups(ValueError, flatten_subgroups=True): raise ExceptionGroup("", (ExceptionGroup("", (ValueError(),)),)) # allow_unwrapped with RaisesGroups(ValueError, allow_unwrapped=True): raise ValueErrorRaisesGroup.matchesтакже можно использовать непосредственно для проверки автономной группы исключений.Алгоритм сопоставления жадный, что может привести к ошибкам в таких случаях:
with RaisesGroups(ValueError, Matcher(ValueError, match="hello")): raise ExceptionGroup("", (ValueError("hello"), ValueError("goodbye")))хотя в целом он не учитывает порядок исключений в группе. Чтобы избежать этого, вы должны указать первый ValueError с помощью Matcher.
Подсказка: если вы установите
hypothesisи импортируете его вconftest.py, вы получите удобочитаемыеrepr``s of ``checkвызываемые функции в выводе.-
Устанавливается после вызова
matchesдля предоставления удобочитаемого объяснения причины неудачи сопоставления. При использовании в качестве менеджера контекста строка будет использоваться в качестве текстаAssertionError
fail_reason-
Проверка, соответствует ли исключение требованиям этого RaisesGroup. Если произошла неудача,
RaisesGroup.fail_reasonбудет установлено.Пример:
with pytest.raises(TypeError) as excinfo: ... assert RaisesGroups(ValueError).matches(excinfo.value.__cause__) # the above line is equivalent to myexc = excinfo.value.__cause assert isinstance(myexc, BaseExceptionGroup) assert len(myexc.exceptions) == 1 assert isinstance(myexc.exceptions[0], ValueError)
matches(exc_val: BaseException | None) → TypeGuard[BaseExceptionGroup[BaseExcT_co]] -
class trio.testing.RaisesGroup(exception: type[BaseExcT_1] | Matcher[BaseExcT_1] | RaisesGroup[BaseExcT_2], *other_exceptions: type[BaseExcT_1] | Matcher[BaseExcT_1] | RaisesGroup[BaseExcT_2], allow_unwrapped: bool = False, flatten_subgroups: bool = False, match: str | Pattern[str] | None = None, check: Callable[[BaseExceptionGroup[BaseExcT_1]], bool] | Callable[[ExceptionGroup[ExcT_1]], bool] | None = None)
-
Вспомогательный класс для использования совместно с RaisesGroups, когда необходимо указать требования к подисключениям. Указание только типа избыточно, а также не нужно, когда тип является вложенной
RaisesGroup, так как она поддерживает те же аргументы. Тип проверяется с помощьюisinstanceи не обязательно должен быть точным. Если нужно точное соответствие, можно использовать параметрcheck.Matcher.matches()также можно использовать автономно для проверки отдельных исключений.Примеры:
with RaisesGroups(Matcher(ValueError, match="string")) ... with RaisesGroups(Matcher(check=lambda x: x.args == (3, "hello"))): ... with RaisesGroups(Matcher(check=lambda x: type(x) is ValueError)): ...Подсказка: если вы установите
hypothesisи импортируете его вconftest.py, вы получите удобочитаемыеrepr``s of ``checkвызываемые функции в выводе.-
Устанавливается после вызова
matchesдля предоставления удобочитаемого объяснения причины неудачи сопоставления. При использовании в качестве менеджера контекста строка будет использоваться в качестве текстаAssertionError
fail_reason-
Проверка, соответствует ли исключение требованиям этого Matcher. Если произошла неудача,
Matcher.fail_reasonбудет установлено.Примеры:
assert Matcher(ValueError).matches(my_exception): # is equivalent to assert isinstance(my_exception, ValueError) # this can be useful when checking e.g. the ``__cause__`` of an exception. with pytest.raises(ValueError) as excinfo: ... assert Matcher(SyntaxError, match="foo").matches(excinfo.value.__cause__) # above line is equivalent to assert isinstance(excinfo.value.__cause__, SyntaxError) assert re.search("foo", str(excinfo.value.__cause__)
matches(exception: BaseException) → TypeGuard[MatchE] -
class trio.testing.Matcher(exception_type: type[MatchE] | None = None, match: str | Pattern[str] | None = None, check: Callable[[MatchE], bool] | None = None)
-
Минимальная реализация pytest.ExceptionInfo, используемая только в том случае, если pytest недоступен. Поддерживает подмножество его функций, необходимых для работы
trio.testing.RaisesGroupиtrio.testing.Matcher.-
Заполнение незаполненной ExceptionInfo, созданной с помощью
for_later().
fill_unfilled(exc_info: tuple[type[MatchE], MatchE, types.TracebackType]) → None-
Возвращает незаполненную ExceptionInfo.
classmethod for_later() → _ExceptionInfo[MatchE]-
Класс исключения.
property type: type[MatchE]-
Значение исключения.
property value: MatchE-
Исходный стек вызовов исключения.
property tb: types.TracebackType -
class trio.testing._raises_group._ExceptionInfo(excinfo: tuple[type[MatchE], MatchE, types.TracebackType] | None)
© 2017 Nathaniel J. Smith
Licensed under the MIT License.
https://trio.readthedocs.io/en/v0.29.0/reference-testing.html