# run_tests.py
#!/usr/bin/env python3
"""
Скрипт для запуска всех тестов проекта с отладочными выводами.
Этот скрипт предоставляет удобный интерфейс для запуска всех тестов проекта
с подробным логированием, визуальными индикаторами выполнения и измерением
времени выполнения. Поддерживает автоматическое определение наличия
библиотеки tqdm для отображения прогресс-бара.
"""
import subprocess
import sys
import os
from pathlib import Path
import time
from datetime import datetime
[документация]
def get_project_root():
"""Возвращает корневую директорию проекта.
Определяет и возвращает путь к корневой директории проекта на основе
расположения текущего файла скрипта.
:return: Объект Path, представляющий корневую директорию проекта
:rtype: pathlib.Path
"""
return Path(__file__).parent
[документация]
def run_command(command, cwd=None):
"""Выполняет команду и возвращает результат.
Запускает указанную команду в подпроцессе с отображением времени выполнения,
визуальных индикаторов и обработкой ошибок. Поддерживает отображение
прогресс-бара через библиотеку tqdm, если она доступна.
:param command: Список аргументов командной строки для выполнения
:type command: list[str]
:param cwd: Рабочая директория для выполнения команды (по умолчанию None)
:type cwd: str or pathlib.Path, optional
:return: True если команда выполнена успешно (код возврата 0), False в случае ошибки
:rtype: bool
"""
print(f"🕐 [{datetime.now().strftime('%H:%M:%S')}] Выполняю: {' '.join(command)}")
start_time = time.time()
try:
# Проверяем наличие tqdm для прогресс-бара
try:
from tqdm import tqdm
has_tqdm = True
except ImportError:
has_tqdm = False
print("ℹ️ tqdm не найден. Установите его для прогресс-бара: pip install tqdm")
# Запускаем процесс
process = subprocess.Popen(
command,
cwd=cwd,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
encoding="utf-8",
errors="replace",
bufsize=1,
universal_newlines=True
)
# Читаем вывод построчно с отображением прогресса
stdout_lines = []
stderr_lines = []
if has_tqdm:
from tqdm import tqdm
# Создаем прогресс-бар
pbar = tqdm(desc="Запуск тестов", unit="сек", leave=True)
last_update = time.time()
while True:
if process.poll() is not None: # Процесс завершен
break
# Обновляем прогресс-бар
current_time = time.time()
if current_time - last_update >= 1: # Обновляем каждую секунду
pbar.update(int(current_time - last_update))
last_update = current_time
time.sleep(0.1)
pbar.close()
else:
print("⏳ Тесты запущены, ожидаем завершения...")
# Просто ждем завершения с периодическим сообщением
wait_time = 0
while process.poll() is None:
time.sleep(1)
wait_time += 1
if wait_time % 10 == 0: # Сообщение каждые 10 секунд
print(f"⏳ Тесты работают уже {wait_time} секунд...")
# Получаем финальный результат
stdout, stderr = process.communicate()
end_time = time.time()
duration = end_time - start_time
print(f"🕐 [{datetime.now().strftime('%H:%M:%S')}] Завершено за {duration:.2f} секунд")
if process.returncode == 0:
print("✅ Успешно")
else:
print("❌ Ошибка:")
if stdout:
print("STDOUT:", stdout)
if stderr:
print("STDERR:", stderr)
return process.returncode == 0
except Exception as e:
end_time = time.time()
duration = end_time - start_time
print(f"🕐 [{datetime.now().strftime('%H:%M:%S')}] Завершено за {duration:.2f} секунд")
print(f"❌ Ошибка выполнения: {e}")
return False
[документация]
def check_pytest():
"""Проверяет наличие pytest и выводит информацию о нем.
Выполняет проверку доступности pytest в текущем окружении, отображает
версию установленного пакета и обрабатывает возможные ошибки проверки.
:return: True если pytest доступен и работает корректно, False в случае ошибки
:rtype: bool
"""
print("🔍 Проверяю наличие pytest...")
try:
result = subprocess.run(
[sys.executable, "-m", "pytest", "--version"],
capture_output=True,
text=True,
timeout=10
)
if result.returncode == 0:
print(f"✅ pytest найден: {result.stdout.strip()}")
return True
else:
print("❌ Ошибка при проверке pytest")
return False
except subprocess.TimeoutExpired:
print("❌ Таймаут при проверке pytest")
return False
except Exception as e:
print(f"❌ Ошибка при проверке pytest: {e}")
return False
[документация]
def main():
"""Основная точка входа в скрипт.
Выполняет полный цикл запуска тестов: проверяет окружение, определяет
доступные тесты, запускает их выполнение и отображает результаты.
Включает подробное логирование всех этапов выполнения.
:return: True если все тесты пройдены успешно, False в случае ошибок
:rtype: bool
"""
project_root = get_project_root()
print("🚀 Запуск всех тестов проекта")
print("=" * 60)
print(f"📁 Рабочая директория: {project_root}")
print(f"🕐 Начало: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
# Проверяем наличие pytest
if not check_pytest():
print("❌ pytest не найден. Установите его: pip install pytest")
return False
# Проверяем наличие директории с тестами
test_dir = project_root / "test_suite"
if not test_dir.exists():
print(f"❌ Директория с тестами не найдена: {test_dir}")
return False
print(f"📋 Найдено тестов: {len(list(test_dir.glob('test_*.py')))} файлов")
# Запускаем все тесты
print("\n📋 Запуск всех тестов...")
test_command = [
sys.executable,
"-m",
"pytest",
"test_suite/",
"-v", # подробный вывод
"--tb=short" # краткий traceback
]
print(f"🔧 Команда: {' '.join(test_command)}")
print("-" * 60)
success = run_command(test_command, cwd=project_root)
end_time = datetime.now()
print("=" * 60)
if success:
print(f"🎉 Все тесты успешно пройдены! Завершено: {end_time.strftime('%Y-%m-%d %H:%M:%S')}")
return True
else:
print(f"💥 Некоторые тесты провалены! Завершено: {end_time.strftime('%Y-%m-%d %H:%M:%S')}")
return False
if __name__ == "__main__":
try:
success = main()
sys.exit(0 if success else 1)
except KeyboardInterrupt:
print("\n⚠️ Прервано пользователем")
sys.exit(1)
except Exception as e:
print(f"\n💥 Критическая ошибка: {e}")
sys.exit(1)