the_snake/tests/conftest.py
Kirill Samoylenkov 9d4e40985a
Initial commit
2025-10-29 03:46:30 +00:00

140 lines
4.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import os
import sys
from multiprocessing import Process
from pathlib import Path
from typing import Any
from pygame.time import Clock
import pytest
import pytest_timeout
BASE_DIR = Path(__file__).resolve(strict=True).parent.parent
sys.path.append(str(BASE_DIR))
# Hide the pygame screen
os.environ['SDL_VIDEODRIVER'] = 'dummy'
TIMEOUT_ASSERT_MSG = (
'Проект работает некорректно, проверка прервана.\n'
'Вероятные причины ошибки:\n'
'1. Исполняемый код (например, вызов функции `main()`) оказался в '
'глобальной зоне видимости. Как исправить: вызов функции `main` поместите '
'внутрь конструкции `if __name__ == "__main__":`.\n'
'2. В цикле `while True` внутри функции `main` отсутствует вызов метода '
'`tick` объекта `clock`. Не изменяйте прекод в этой части.'
)
def import_the_snake():
import the_snake # noqa
@pytest.fixture(scope='session')
def snake_import_test():
check_import_process = Process(target=import_the_snake)
check_import_process.start()
pid = check_import_process.pid
check_import_process.join(timeout=1)
if check_import_process.is_alive():
os.kill(pid, 9)
raise AssertionError(TIMEOUT_ASSERT_MSG)
@pytest.fixture(scope='session')
def _the_snake(snake_import_test):
try:
import the_snake
except ImportError as error:
raise AssertionError(
'При импорте модуль `the_snake` произошла ошибка:\n'
f'{type(error).__name__}: {error}'
)
for class_name in ('GameObject', 'Snake', 'Apple'):
assert hasattr(the_snake, class_name), (
f'Убедитесь, что в модуле `the_snake` определен класс `{class_name}`.'
)
return the_snake
def write_timeout_reasons(text, stream=None):
"""Write possible reasons of tests timeout to stream.
The function to replace pytest_timeout traceback output with possible
reasons of tests timeout.
Appears only when `thread` method is used.
"""
if stream is None:
stream = sys.stderr
text = TIMEOUT_ASSERT_MSG
stream.write(text)
pytest_timeout.write = write_timeout_reasons
def _create_game_object(class_name, module):
try:
return getattr(module, class_name)()
except TypeError as error:
raise AssertionError(
f'При создании объекта класса `{class_name}` произошла ошибка:\n'
f'`{type(error).__name__}: {error}`\n'
f'Если в конструктор класса `{class_name}` помимо параметра '
'`self` передаются какие-то ещё параметры - убедитесь, что для '
'них установлены значения по умолчанию. Например:\n'
'`def __init__(self, <параметр>=<значение_по_умолчанию>):`'
)
@pytest.fixture
def game_object(_the_snake):
return _create_game_object('GameObject', _the_snake)
@pytest.fixture
def snake(_the_snake):
return _create_game_object('Snake', _the_snake)
@pytest.fixture
def apple(_the_snake):
return _create_game_object('Apple', _the_snake)
class StopInfiniteLoop(Exception):
pass
def loop_breaker_decorator(func):
call_counter = 0
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
nonlocal call_counter
call_counter += 1
if call_counter > 1:
raise StopInfiniteLoop
return result
return wrapper
@pytest.fixture
def modified_clock(_the_snake):
class _Clock:
def __init__(self, clock_obj: Clock) -> None:
self.clock = clock_obj
@loop_breaker_decorator
def tick(self, *args, **kwargs):
return self.clock.tick(*args, **kwargs)
def __getattribute__(self, name: str) -> Any:
if name in ['tick', 'clock']:
return super().__getattribute__(name)
return self.clock.__getattribute__(name)
original_clock = _the_snake.clock
modified_clock_obj = _Clock(original_clock)
_the_snake.clock = modified_clock_obj
yield
_the_snake.clock = original_clock