#!/usr/bin/env python3 import os import sys import tempfile import shutil import subprocess import re import atexit import signal import traceback import glob # Цвета для терминала GREEN = '\033[92m' RED = '\033[91m' YELLOW = '\033[93m' BLUE = '\033[94m' RESET = '\033[0m' BOLD = '\033[1m' # Глобальные переменные для состояния тестов _TESTS_PASSED = 0 _TESTS_FAILED = 0 _TESTS_TOTAL = 0 _TEST_RESULTS: list = [] def main(): # Проверяем, что передан путь к исполняемому файлу rusi if len(sys.argv) < 2: print("Использование: {} <путь-к-rusi>".format(sys.argv[0]), file=sys.stderr) sys.exit(1) rusi = sys.argv[1] # Путь к тестируемому компилятору # Создаем временную папку для тестов tmp = tempfile.mkdtemp(prefix='rusi-test-') def cleanup(): # Удаляем временную папку после завершения тестов shutil.rmtree(tmp, ignore_errors=True) atexit.register(cleanup) # Регистрируем функцию очистки # Обработчики сигналов для корректного завершения signal.signal(signal.SIGINT, lambda s, f: завершить_тесты()) signal.signal(signal.SIGTERM, lambda s, f: завершить_тесты()) if hasattr(signal, 'SIGHUP'): signal.signal(signal.SIGHUP, lambda s, f: завершить_тесты()) # Создаем пустой C-файл для тестирования tmp_c = os.path.join(tmp, 'tmp.c') with open(tmp_c, 'w') as f: f.write('') # Запускаем все тесты try: запустить_тесты(rusi, tmp, tmp_c) except (AssertionError, subprocess.CalledProcessError, Exception) as e: # При падении любого теста выводим отчет и выходим с ошибкой вывести_отчет() print(f"\n{RED}❌ ТЕСТЫ НЕ ПРОЙДЕНЫ: {str(e)}{RESET}") sys.exit(1) # Если все тесты прошли успешно вывести_отчет() if _TESTS_FAILED == 0: print(f"\n{GREEN}{BOLD}🎉 ВСЕ ТЕСТЫ ПРОЙДЕНЫ УСПЕШНО!{RESET}") sys.exit(0) else: print(f"\n{RED}{BOLD}❌ НЕ ПРОЙДЕНО {_TESTS_FAILED} ТЕСТОВ ИЗ {_TESTS_TOTAL}{RESET}") sys.exit(1) def завершить_тесты(): """Принудительное завершение с выводом отчета""" вывести_отчет() sys.exit(1) def запустить_тесты(rusi: str, tmp: str, tmp_c: str): """Запускает все тесты""" global _TESTS_TOTAL # Список всех тестов с их описаниями тесты = [ ("-o", lambda: тест_o(rusi, tmp, tmp_c)), ("--help", lambda: тест_help(rusi)), ("-S", lambda: тест_S(rusi)), ("имя выходного файла по умолчанию", lambda: тест_имя_выходного_файла(rusi, tmp)), ("несколько входных файлов", lambda: тест_несколько_входных(rusi, tmp)), ("линковщик", lambda: тест_линковщик(rusi, tmp)), ("a.out", lambda: тест_a_out(rusi, tmp)), ("-E", lambda: тест_E(rusi, tmp)), ("-E и -o", lambda: тест_E_o(rusi, tmp)), ("-I", lambda: тест_I(rusi, tmp)), ("-D", lambda: тест_D(rusi)), ("-D со значением", lambda: тест_D_значение(rusi)), ("-U", lambda: тест_U(rusi)), ("игнорируемые опции", lambda: тест_игнорируемые(rusi, tmp_c)), ("маркер BOM", lambda: тест_BOM(rusi)), ("inline", lambda: тест_inline(rusi, tmp)), ("-idirafter", lambda: тест_idirafter(rusi, tmp)), ("-fcommon (по умолчанию)", lambda: тест_fcommon_по_умолчанию(rusi)), ("-fcommon", lambda: тест_fcommon(rusi)), ("-fno-common", lambda: тест_fno_common(rusi)), ("-include", lambda: тест_include(rusi, tmp)), ("-include stdio.h", lambda: тест_include_stdio(rusi)), ("-xc", lambda: тест_xc(rusi, tmp)), ("-x assembler", lambda: тест_x_assembler(rusi, tmp)), ("-x none", lambda: тест_x_none(rusi, tmp)), ("-E без -xc", lambda: тест_E_без_xc(rusi)), (".a", lambda: тест_a_архив(rusi, tmp)), (".so", lambda: тест_so_библиотека(rusi, tmp)), ("хеш-таблица", lambda: тест_хеш_таблица(rusi)), ("-M", lambda: тест_M(rusi, tmp)), ("-MF", lambda: тест_MF(rusi, tmp)), ("-MP", lambda: тест_MP(rusi, tmp)), ("-MT", lambda: тест_MT(rusi, tmp)), ("-MT несколько целей", lambda: тест_MT_несколько(rusi, tmp)), ("-MD", lambda: тест_MD(rusi, tmp)), ("-MD с -MF", lambda: тест_MD_MF(rusi, tmp)), ("-fPIC", lambda: тест_fPIC(rusi, tmp)), ("#include_next", lambda: тест_include_next(rusi, tmp)), ("-static", lambda: тест_static(rusi, tmp)), ("-shared", lambda: тест_shared(rusi, tmp)), ("-L", lambda: тест_L(rusi, tmp)), ("-Wl,", lambda: тест_Wl(rusi, tmp)), ("-Xlinker", lambda: тест_Xlinker(rusi, tmp)), # ============================================================ # НОВЫЕ ТЕСТЫ: Сборка и запуск .c файлов из папки test/ # ============================================================ ("Сборка тестов .c", lambda: тест_сборка_c_файлов(rusi)), ] _TESTS_TOTAL = len(тесты) # Вычисляем максимальную длину названия теста для выравнивания макс_длина = 0 for название, _ in тесты: макс_длина = max(макс_длина, len(название)) for название, тест_функция in тесты: выполнить_тест(название, тест_функция, макс_длина) def выполнить_тест(название: str, тест_функция, макс_длина: int): """Выполняет отдельный тест - при ошибке сразу прерывает выполнение""" global _TESTS_PASSED, _TESTS_FAILED try: тест_функция() _TESTS_PASSED += 1 _TEST_RESULTS.append((название, True, None)) print(f"{GREEN}✓{RESET} {название:<{макс_длина}} ... {GREEN}ПРОЙДЕНО{RESET}") except AssertionError as e: _TESTS_FAILED += 1 ошибка = str(e) if str(e) else "проверка не пройдена" _TEST_RESULTS.append((название, False, ошибка)) print(f"{RED}✗{RESET} {название:<{макс_длина}} ... {RED}НЕ ПРОЙДЕНО{RESET}: {ошибка}") raise # Прерываем выполнение при падении теста except subprocess.CalledProcessError as e: _TESTS_FAILED += 1 ошибка = f"команда завершилась с кодом {e.returncode}" _TEST_RESULTS.append((название, False, ошибка)) print(f"{RED}✗{RESET} {название:<{макс_длина}} ... {RED}НЕ ПРОЙДЕНО{RESET}: {ошибка}") raise # Прерываем выполнение при падении теста except Exception as e: _TESTS_FAILED += 1 ошибка = f"{type(e).__name__}: {str(e)}" _TEST_RESULTS.append((название, False, ошибка)) print(f"{RED}✗{RESET} {название:<{макс_длина}} ... {RED}НЕ ПРОЙДЕНО{RESET}: {ошибка}") traceback.print_exc() raise # Прерываем выполнение при падении теста def вывести_отчет(): """Выводит итоговый отчет о тестировании""" global _TESTS_PASSED, _TESTS_FAILED, _TESTS_TOTAL print("\n" + "=" * 60) print(f"{BOLD}ИТОГОВЫЙ ОТЧЕТ О ТЕСТИРОВАНИИ{RESET}") print("=" * 60) if _TEST_RESULTS: print("\nПОДРОБНЫЕ РЕЗУЛЬТАТЫ:") for название, успешно, ошибка in _TEST_RESULTS: if успешно: статус = f"{GREEN}✓ ПРОЙДЕНО{RESET}" else: статус = f"{RED}✗ НЕ ПРОЙДЕНО{RESET}" строка = f" {статус} {название}" if not успешно and ошибка: строка += f" ({RED}ошибка: {ошибка}{RESET})" print(строка) print("\n" + "-" * 60) print(f"ВСЕГО ТЕСТОВ: {_TESTS_TOTAL}") print(f"ПРОЙДЕНО: {GREEN}{_TESTS_PASSED}{RESET}") print(f"НЕ ПРОЙДЕНО: {RED}{_TESTS_FAILED}{RESET}") print("=" * 60) def проверка_существования_файла(путь: str, сообщение: str = ""): """Вспомогательная функция для проверки существования файла""" if not os.path.exists(путь): raise AssertionError(f"файл {путь} не создан {сообщение}") def проверка_содержимого(текст: str, паттерн, сообщение: str = ""): """Вспомогательная функция для проверки содержимого""" if isinstance(паттерн, str): if паттерн not in текст: raise AssertionError(f"'{паттерн}' не найден в выводе {сообщение}") else: # регулярное выражение if not паттерн.search(текст): raise AssertionError(f"паттерн не найден в выводе {сообщение}") def run_rusi(rusi: str, *args, **kwargs): """Запускает rusi с заданными аргументами""" cmd = [rusi] + list(args) if 'input' in kwargs: input_text = kwargs.pop('input') kwargs['input'] = input_text if 'capture_output' not in kwargs and 'stdout' not in kwargs and 'stderr' not in kwargs: kwargs['capture_output'] = True kwargs['text'] = True return subprocess.run(cmd, **kwargs) # ============================================================================ # НОВАЯ ФУНКЦИЯ: Сборка и запуск .c файлов из папки test/ # ============================================================================ def тест_сборка_c_файлов(rusi: str): """Собирает и запускает все .c файлы из папки test/, проверяя вывод на OK""" # Находим все .c файлы в папке test/ test_dir = "test" if not os.path.exists(test_dir): raise AssertionError(f"Папка {test_dir} не найдена") c_files = glob.glob(os.path.join(test_dir, "*.c")) if not c_files: raise AssertionError("В папке test/ нет .c файлов") # Файлы, которые нужно исключить (требуют специальной линковки или не являются тестами) исключения = [] # atomic.c теперь работает # Файлы, которые требуют stdvv.c stdvv = os.path.join(test_dir, "stdvv.c") if not os.path.exists(stdvv): stdvv = None # Файлы, которые требуют test.c (если есть) test_c = os.path.join(test_dir, "test.c") if os.path.exists(test_c): runtime = test_c elif stdvv: runtime = stdvv else: runtime = None # Счетчики для отчета всего_тестов = 0 пройдено_тестов = 0 пропущено_тестов = 0 # Вычисляем максимальную длину имени файла для выравнивания макс_длина = 0 for c_file in c_files: имя = os.path.basename(c_file) if имя not in ["stdvv.c", "test.c", "тест.з"]: макс_длина = max(макс_длина, len(имя)) print(f"\n{BOLD}=== ЗАПУСК СЕМАНТИЧЕСКИХ ТЕСТОВ ==={RESET}") print() # Проходим по всем .c файлам for c_file in sorted(c_files): имя_файла = os.path.basename(c_file) имя_без_расш = os.path.splitext(имя_файла)[0] # Пропускаем исключения if имя_файла in исключения: пропущено_тестов += 1 print(f"{YELLOW}⚠{RESET} {имя_файла:<{макс_длина}} ... {YELLOW}ПРОПУЩЕН{RESET} (требует специальной линковки)") continue # Пропускаем runtime файлы (не тесты) if имя_файла in ["stdvv.c", "test.c", "тест.з"]: continue всего_тестов += 1 # Определяем, нужен ли runtime для этого теста with open(c_file, 'r', encoding='utf-8', errors='ignore') as f: содержимое = f.read() нужен_runtime = 'ASSERT' in содержимое or 'ПРОВЕРИТЬ' in содержимое or 'printf' not in содержимое # Собираем команду компиляции выходной_файл = os.path.join(test_dir, f"test_{имя_без_расш}") аргументы = [rusi, "-o", выходной_файл, c_file] # Добавляем runtime если нужен if нужен_runtime and runtime and os.path.exists(runtime): аргументы.append(runtime) # Собираем try: subprocess.run(аргументы, capture_output=True, text=True, check=True) except subprocess.CalledProcessError as e: raise AssertionError(f"Ошибка компиляции {имя_файла}: {e.stderr}") # Проверяем, что исполняемый файл создан if not os.path.exists(выходной_файл): raise AssertionError(f"Исполняемый файл не создан для {имя_файла}") # Запускаем и проверяем вывод try: результат = subprocess.run([выходной_файл], capture_output=True, text=True, timeout=5) except subprocess.TimeoutExpired: raise AssertionError(f"Таймаут при выполнении {имя_файла}") # Проверяем, что программа завершилась успешно if результат.returncode != 0: raise AssertionError(f"{имя_файла} завершилась с кодом {результат.returncode}: {результат.stderr}") # Проверяем, что вывод содержит OK if "OK" not in результат.stdout: raise AssertionError(f"{имя_файла} не вывела OK. Вывод: {результат.stdout}") # Тест пройден! пройдено_тестов += 1 print(f"{GREEN}✓{RESET} {имя_файла:<{макс_длина}} ... {GREEN}ПРОЙДЕНО{RESET}") # Удаляем исполняемый файл try: os.unlink(выходной_файл) except: pass # Выводим итоговый отчет по семантическим тестам print() print(f"{BOLD}=== ИТОГ СЕМАНТИЧЕСКИХ ТЕСТОВ ==={RESET}") print(f" Всего тестов: {всего_тестов}") print(f" {GREEN}Пройдено:{RESET} {пройдено_тестов}") if пропущено_тестов > 0: print(f" {YELLOW}Пропущено:{RESET} {пропущено_тестов}") print() # ============================================================================ # ОСТАЛЬНЫЕ ТЕСТЫ (без изменений) # ============================================================================ def тест_o(rusi: str, tmp: str, tmp_c: str): """Тест опции -o""" out_file = os.path.join(tmp, 'out') if os.path.exists(out_file): os.unlink(out_file) subprocess.run([rusi, '-c', '-o', out_file, tmp_c], check=True) проверка_существования_файла(out_file) def тест_help(rusi: str): """Тест --help""" result = subprocess.run([rusi, '--help'], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True) if not result.stdout.strip(): raise AssertionError("пустой вывод справки") def тест_S(rusi: str): """Тест -S""" result = subprocess.run([rusi, '-S', '-o-', '-xc', '-'], input='int main() {}', capture_output=True, text=True) проверка_содержимого(result.stdout, 'main:', "метка main не найдена в ассемблерном коде") def тест_имя_выходного_файла(rusi: str, tmp: str): """Тест имени выходного файла по умолчанию""" out_c = os.path.join(tmp, 'out.c') with open(out_c, 'w') as f: f.write('int main() {}') out_o = os.path.join(tmp, 'out.o') out_s = os.path.join(tmp, 'out.s') for f in [out_o, out_s]: if os.path.exists(f): os.unlink(f) old_pwd = os.getcwd() os.chdir(tmp) try: subprocess.run([os.path.join(old_pwd, rusi), '-c', 'out.c'], check=True) проверка_существования_файла(out_o) finally: os.chdir(old_pwd) os.chdir(tmp) try: subprocess.run([os.path.join(old_pwd, rusi), '-c', '-S', 'out.c'], check=True) проверка_существования_файла(out_s) finally: os.chdir(old_pwd) def тест_несколько_входных(rusi: str, tmp: str): """Тест с несколькими входными файлами""" foo_o = os.path.join(tmp, 'foo.o') bar_o = os.path.join(tmp, 'bar.o') for f in [foo_o, bar_o]: if os.path.exists(f): os.unlink(f) foo_c = os.path.join(tmp, 'foo.c') bar_c = os.path.join(tmp, 'bar.c') with open(foo_c, 'w') as f: f.write('int x;') with open(bar_c, 'w') as f: f.write('int y;') old_pwd = os.getcwd() os.chdir(tmp) try: subprocess.run([os.path.join(old_pwd, rusi), '-c', foo_c, bar_c], check=True) проверка_существования_файла(foo_o) проверка_существования_файла(bar_o) finally: os.chdir(old_pwd) foo_s = os.path.join(tmp, 'foo.s') bar_s = os.path.join(tmp, 'bar.s') for f in [foo_s, bar_s]: if os.path.exists(f): os.unlink(f) os.chdir(tmp) try: subprocess.run([os.path.join(old_pwd, rusi), '-c', '-S', foo_c, bar_c], check=True) проверка_существования_файла(foo_s) проверка_существования_файла(bar_s) finally: os.chdir(old_pwd) def тест_линковщик(rusi: str, tmp: str): """Тест линковщика""" foo_exe = os.path.join(tmp, 'foo') if os.path.exists(foo_exe): os.unlink(foo_exe) subprocess.run([rusi, '-o', foo_exe, '-xc', '-xc', '-'], input='int main() { return 0; }', text=True, check=True) result = subprocess.run([foo_exe]) if result.returncode != 0: raise AssertionError(f"программа вернула {result.returncode}, ожидалось 0") if os.path.exists(foo_exe): os.unlink(foo_exe) foo_c = os.path.join(tmp, 'foo.c') bar_c = os.path.join(tmp, 'bar.c') with open(foo_c, 'w') as f: f.write('int bar(); int main() { return bar(); }') with open(bar_c, 'w') as f: f.write('int bar() { return 42; }') subprocess.run([rusi, '-o', foo_exe, foo_c, bar_c], check=True) result = subprocess.run([foo_exe]) if result.returncode != 42: raise AssertionError(f"программа вернула {result.returncode}, ожидалось 42") def тест_a_out(rusi: str, tmp: str): """Тест a.out""" a_out = os.path.join(tmp, 'a.out') if os.path.exists(a_out): os.unlink(a_out) foo_c = os.path.join(tmp, 'foo.c') with open(foo_c, 'w') as f: f.write('int main() {}') old_pwd = os.getcwd() os.chdir(tmp) try: subprocess.run([os.path.join(old_pwd, rusi), 'foo.c'], check=True) проверка_существования_файла(a_out) finally: os.chdir(old_pwd) def тест_E(rusi: str, tmp: str): """Тест -E""" out_file = os.path.join(tmp, 'out') with open(out_file, 'w') as f: f.write('foo') result = subprocess.run([rusi, '-E', '-xc', '-'], input=f'#include "{out_file}"\n', capture_output=True, text=True) проверка_содержимого(result.stdout, 'foo', "препроцессор не вставил содержимое файла") def тест_E_o(rusi: str, tmp: str): """Тест -E и -o""" out1 = os.path.join(tmp, 'out1') with open(out1, 'w') as f: f.write('foo') out2 = os.path.join(tmp, 'out2') subprocess.run([rusi, '-E', '-o', out2, '-xc', '-'], input=f'#include "{out1}"\n', text=True, check=True) with open(out2) as f: content = f.read() проверка_содержимого(content, 'foo', "результат препроцессинга не записан в файл") def тест_I(rusi: str, tmp: str): """Тест -I""" dir_path = os.path.join(tmp, 'dir') os.makedirs(dir_path, exist_ok=True) i_test = os.path.join(dir_path, 'i-option-test') with open(i_test, 'w') as f: f.write('foo') result = subprocess.run([rusi, f'-I{dir_path}', '-E', '-xc', '-'], input='#include "i-option-test"\n', capture_output=True, text=True) проверка_содержимого(result.stdout, 'foo', "файл не найден в указанной папке") def тест_D(rusi: str): """Тест -D""" result = subprocess.run([rusi, '-Dfoo', '-E', '-xc', '-'], input='foo', capture_output=True, text=True) проверка_содержимого(result.stdout, '1', "макрос без значения не подставлен как 1") def тест_D_значение(rusi: str): """Тест -D со значением""" result = subprocess.run([rusi, '-Dfoo=bar', '-E', '-xc', '-'], input='foo', capture_output=True, text=True) проверка_содержимого(result.stdout, 'bar', "макрос не подставлен с указанным значением") def тест_U(rusi: str): """Тест -U""" result = subprocess.run([rusi, '-Dfoo=bar', '-Ufoo', '-E', '-xc', '-'], input='foo', capture_output=True, text=True) проверка_содержимого(result.stdout, 'foo', "макрос не остался неопределенным") def тест_игнорируемые(rusi: str, tmp_c: str): """Тест игнорируемых опций""" subprocess.run([rusi, '-c', '-O', '-Wall', '-g', '-std=c11', '-ffreestanding', '-fno-builtin', '-fno-omit-frame-pointer', '-fno-stack-protector', '-fno-strict-aliasing', '-m64', '-mno-red-zone', '-w', '-o', '/dev/null', tmp_c], check=True) def тест_BOM(rusi: str): """Тест маркера BOM""" result = subprocess.run([rusi, '-E', '-o-', '-xc', '-'], input='\ufeffxyz\n', capture_output=True, text=True) проверка_содержимого(result.stdout, re.compile(r'^xyz', re.MULTILINE), "BOM не удален") def тест_inline(rusi: str, tmp: str): """Тесты inline-функций""" inline1 = os.path.join(tmp, 'inline1.c') inline2 = os.path.join(tmp, 'inline2.c') inline3 = os.path.join(tmp, 'inline3.c') # Две одинаковые inline-функции в разных файлах with open(inline1, 'w') as f: f.write('inline void foo() {}') with open(inline2, 'w') as f: f.write('inline void foo() {}') with open(inline3, 'w') as f: f.write('int main() { return 0; }') subprocess.run([rusi, '-o', '/dev/null', inline1, inline2, inline3], check=True) # extern inline with open(inline1, 'w') as f: f.write('extern inline void foo() {}') with open(inline2, 'w') as f: f.write('int foo(); int main() { foo(); }') subprocess.run([rusi, '-o', '/dev/null', inline1, inline2], check=True) # static inline не используется result = subprocess.run([rusi, '-o-', '-S', '-xc', '-'], input='static inline void f1() {}', capture_output=True, text=True) if 'f1:' in result.stdout: raise AssertionError("static inline функция сгенерировала код без использования") # static inline используется result = subprocess.run([rusi, '-o-', '-S', '-xc', '-'], input='static inline void f1() {} void foo() { f1(); }', capture_output=True, text=True) проверка_содержимого(result.stdout, 'f1:', "static inline функция не сгенерировала код при использовании") # Дополнительные проверки inline test_input = 'static inline void f1() {} static inline void f2() { f1(); } void foo() { f1(); }' result = subprocess.run([rusi, '-o-', '-S', '-xc', '-'], input=test_input, capture_output=True, text=True) проверка_содержимого(result.stdout, 'f1:', "f1 должна быть сгенерирована") if 'f2:' in result.stdout: raise AssertionError("f2 сгенерирована, хотя не используется") def тест_idirafter(rusi: str, tmp: str): """Тест -idirafter""" dir1 = os.path.join(tmp, 'dir1') dir2 = os.path.join(tmp, 'dir2') os.makedirs(dir1, exist_ok=True) os.makedirs(dir2, exist_ok=True) idirafter1 = os.path.join(dir1, 'idirafter') idirafter2 = os.path.join(dir2, 'idirafter') with open(idirafter1, 'w') as f: f.write('foo') with open(idirafter2, 'w') as f: f.write('bar') # Обычная -I ищет в первой папке result = subprocess.run([rusi, f'-I{dir1}', f'-I{dir2}', '-E', '-xc', '-'], input='#include "idirafter"\n', capture_output=True, text=True) проверка_содержимого(result.stdout, 'foo', "-I не нашла файл в первой папке") # -idirafter ищет после -I result = subprocess.run([rusi, f'-idirafter', dir1, f'-I{dir2}', '-E', '-xc', '-'], input='#include "idirafter"\n', capture_output=True, text=True) проверка_содержимого(result.stdout, 'bar', "-idirafter не нашла файл после -I") def тест_fcommon_по_умолчанию(rusi: str): """Тест -fcommon (по умолчанию)""" result = subprocess.run([rusi, '-S', '-o-', '-xc', '-'], input='int foo;', capture_output=True, text=True) проверка_содержимого(result.stdout, '.comm foo', "директива .comm не сгенерирована") def тест_fcommon(rusi: str): """Тест -fcommon""" result = subprocess.run([rusi, '-fcommon', '-S', '-o-', '-xc', '-'], input='int foo;', capture_output=True, text=True) проверка_содержимого(result.stdout, '.comm foo', "директива .comm не сгенерирована с -fcommon") def тест_fno_common(rusi: str): """Тест -fno-common""" result = subprocess.run([rusi, '-fno-common', '-S', '-o-', '-xc', '-'], input='int foo;', capture_output=True, text=True) проверка_содержимого(result.stdout, re.compile(r'^foo:', re.MULTILINE), "foo не сгенерирована как обычная переменная") def тест_include(rusi: str, tmp: str): """Тест -include""" out_h = os.path.join(tmp, 'out.h') with open(out_h, 'w') as f: f.write('foo') result = subprocess.run([rusi, '-include', out_h, '-E', '-o-', '-xc', '-'], input='bar', capture_output=True, text=True) проверка_содержимого(result.stdout, 'foo', "файл не включен через -include") проверка_содержимого(result.stdout, 'bar', "входной текст отсутствует после -include") def тест_include_stdio(rusi: str): """Тест -include stdio.h""" result = subprocess.run([rusi, '-Iinclude', '-include', 'stdio.h', '-E', '-o-', '-xc', '-'], input='NULL', capture_output=True, text=True) проверка_содержимого(result.stdout, '0', "NULL не определен через -include stdio.h") def тест_xc(rusi: str, tmp: str): """Тест -xc""" foo_o = os.path.join(tmp, 'foo.o') subprocess.run([rusi, '-c', '-xc', '-o', foo_o, '-'], input='int x;', text=True, check=True) def тест_x_assembler(rusi: str, tmp: str): """Тест -x assembler""" foo_o = os.path.join(tmp, 'foo.o') subprocess.run([rusi, '-c', '-x', 'assembler', '-o', foo_o, '-'], input='x:', text=True, check=True) def тест_x_none(rusi: str, tmp: str): """Тест -x none""" foo_o = os.path.join(tmp, 'foo.o') foo_c = os.path.join(tmp, 'foo.c') with open(foo_c, 'w') as f: f.write('int x;') subprocess.run([rusi, '-c', '-x', 'assembler', '-x', 'none', '-o', foo_o, foo_c], check=True) def тест_E_без_xc(rusi: str): """Тест -E без -xc""" result = subprocess.run([rusi, '-E', '-'], input='foo', capture_output=True, text=True) проверка_содержимого(result.stdout, 'foo', "препроцессинг без -xc не прошел") def тест_a_архив(rusi: str, tmp: str): """Тест работы с архивными файлами (.a)""" foo_o = os.path.join(tmp, 'foo.o') bar_o = os.path.join(tmp, 'bar.o') subprocess.run([rusi, '-c', '-xc', '-o', foo_o, '-'], input='void foo() {}', text=True, check=True) subprocess.run([rusi, '-c', '-xc', '-o', bar_o, '-'], input='void bar() {}', text=True, check=True) foo_a = os.path.join(tmp, 'foo.a') subprocess.run(['ar', 'rcs', foo_a, foo_o, bar_o], check=True) main_c = os.path.join(tmp, 'main.c') with open(main_c, 'w') as f: f.write('void foo(); void bar(); int main() { foo(); bar(); }') foo_exe = os.path.join(tmp, 'foo') subprocess.run([rusi, '-o', foo_exe, main_c, foo_a], check=True) def тест_so_библиотека(rusi: str, tmp: str): """Тест работы с динамическими библиотеками (.so)""" foo_o = os.path.join(tmp, 'foo.o') bar_o = os.path.join(tmp, 'bar.o') subprocess.run(['cc', '-fPIC', '-c', '-xc', '-o', foo_o, '-'], input='void foo() {}', text=True, check=True) subprocess.run(['cc', '-fPIC', '-c', '-xc', '-o', bar_o, '-'], input='void bar() {}', text=True, check=True) foo_so = os.path.join(tmp, 'foo.so') subprocess.run(['cc', '-shared', '-o', foo_so, foo_o, bar_o], check=True) main_c = os.path.join(tmp, 'main.c') with open(main_c, 'w') as f: f.write('void foo(); void bar(); int main() { foo(); bar(); }') foo_exe = os.path.join(tmp, 'foo') subprocess.run([rusi, '-o', foo_exe, main_c, foo_so], check=True) def тест_хеш_таблица(rusi: str): """Тест хеш-таблицы""" subprocess.run([rusi, '-hashmap-test'], check=True) def тест_M(rusi: str, tmp: str): """Тест -M""" out_c = os.path.join(tmp, 'out.c') with open(out_c, 'w') as f: f.write('#include "out2.h"\n') f.write('#include "out3.h"\n') out2_h = os.path.join(tmp, 'out2.h') out3_h = os.path.join(tmp, 'out3.h') for f in [out2_h, out3_h]: with open(f, 'w') as fh: fh.write('') result = subprocess.run([rusi, '-M', f'-I{tmp}', out_c], capture_output=True, text=True) проверка_содержимого(result.stdout, 'out.o:', "цель out.o не найдена") проверка_содержимого(result.stdout, 'out.c', "зависимость out.c не найдена") проверка_содержимого(result.stdout, 'out2.h', "зависимость out2.h не найдена") проверка_содержимого(result.stdout, 'out3.h', "зависимость out3.h не найдена") def тест_MF(rusi: str, tmp: str): """Тест -MF""" out_c = os.path.join(tmp, 'out.c') with open(out_c, 'w') as f: f.write('#include "out2.h"\n') f.write('#include "out3.h"\n') out2_h = os.path.join(tmp, 'out2.h') out3_h = os.path.join(tmp, 'out3.h') for f in [out2_h, out3_h]: with open(f, 'w') as fh: fh.write('') mf_file = os.path.join(tmp, 'mf') subprocess.run([rusi, '-MF', mf_file, '-M', f'-I{tmp}', out_c], check=True) with open(mf_file) as f: content = f.read() проверка_содержимого(content, 'out.o:', "цель out.o не найдена в MF") проверка_содержимого(content, 'out.c', "зависимость out.c не найдена в MF") проверка_содержимого(content, 'out2.h', "зависимость out2.h не найдена в MF") проверка_содержимого(content, 'out3.h', "зависимость out3.h не найдена в MF") def тест_MP(rusi: str, tmp: str): """Тест -MP""" out_c = os.path.join(tmp, 'out.c') with open(out_c, 'w') as f: f.write('#include "out2.h"\n') f.write('#include "out3.h"\n') out2_h = os.path.join(tmp, 'out2.h') out3_h = os.path.join(tmp, 'out3.h') for f in [out2_h, out3_h]: with open(f, 'w') as fh: fh.write('') mp_file = os.path.join(tmp, 'mp') subprocess.run([rusi, '-MF', mp_file, '-MP', '-M', f'-I{tmp}', out_c], check=True) with open(mp_file) as f: content = f.read() if not re.search(r'out2\.h:', content): raise AssertionError("цель out2.h не найдена в MP") if not re.search(r'out3\.h:', content): raise AssertionError("цель out3.h не найдена в MP") def тест_MT(rusi: str, tmp: str): """Тест -MT""" out_c = os.path.join(tmp, 'out.c') with open(out_c, 'w') as f: f.write('#include "out2.h"\n') out2_h = os.path.join(tmp, 'out2.h') with open(out2_h, 'w') as f: f.write('') result = subprocess.run([rusi, '-MT', 'foo', '-M', f'-I{tmp}', out_c], capture_output=True, text=True) if not result.stdout.startswith('foo:'): raise AssertionError("цель не изменена на foo") def тест_MT_несколько(rusi: str, tmp: str): """Тест -MT с несколькими целями""" out_c = os.path.join(tmp, 'out.c') with open(out_c, 'w') as f: f.write('#include "out2.h"\n') out2_h = os.path.join(tmp, 'out2.h') with open(out2_h, 'w') as f: f.write('') result = subprocess.run([rusi, '-MT', 'foo', '-MT', 'bar', '-M', f'-I{tmp}', out_c], capture_output=True, text=True) if not result.stdout.startswith('foo bar:'): raise AssertionError("несколько целей не установлены") def тест_MD(rusi: str, tmp: str): """Тест -MD""" md2_c = os.path.join(tmp, 'md2.c') md3_c = os.path.join(tmp, 'md3.c') with open(md2_c, 'w') as f: f.write('#include "out2.h"\n') with open(md3_c, 'w') as f: f.write('#include "out3.h"\n') out2_h = os.path.join(tmp, 'out2.h') out3_h = os.path.join(tmp, 'out3.h') for f in [out2_h, out3_h]: with open(f, 'w') as fh: fh.write('') old_pwd = os.getcwd() os.chdir(tmp) try: subprocess.run([os.path.join(old_pwd, rusi), '-c', '-MD', '-I.', 'md2.c', 'md3.c'], check=True) finally: os.chdir(old_pwd) md2_d = os.path.join(tmp, 'md2.d') md3_d = os.path.join(tmp, 'md3.d') with open(md2_d) as f: content = f.read() проверка_содержимого(content, 'md2.o:', "цель md2.o не найдена в MD") проверка_содержимого(content, 'md2.c', "зависимость md2.c не найдена в MD") проверка_содержимого(content, 'out2.h', "зависимость out2.h не найдена в MD") with open(md3_d) as f: content = f.read() проверка_содержимого(content, 'md3.o:', "цель md3.o не найдена в MD") проверка_содержимого(content, 'md3.c', "зависимость md3.c не найдена в MD") проверка_содержимого(content, 'out3.h', "зависимость out3.h не найдена в MD") def тест_MD_MF(rusi: str, tmp: str): """Тест -MD с -MF""" md2_c = os.path.join(tmp, 'md2.c') with open(md2_c, 'w') as f: f.write('#include "out2.h"\n') out2_h = os.path.join(tmp, 'out2.h') with open(out2_h, 'w') as f: f.write('') md_mf_d = os.path.join(tmp, 'md-mf.d') subprocess.run([rusi, '-c', '-MD', '-MF', md_mf_d, '-I.', md2_c], check=True) with open(md_mf_d) as f: content = f.read() проверка_содержимого(content, 'md2.o:', "цель md2.o не найдена в MD+MF") проверка_содержимого(content, 'md2.c', "зависимость md2.c не найдена в MD+MF") проверка_содержимого(content, 'out2.h', "зависимость out2.h не найдена в MD+MF") def тест_fPIC(rusi: str, tmp: str): """Тест -fPIC""" foo_o = os.path.join(tmp, 'foo.o') subprocess.run([rusi, '-fPIC', '-xc', '-c', '-o', foo_o, '-'], input='extern int bar; int foo() { return bar; }', text=True, check=True) foo_so = os.path.join(tmp, 'foo.so') subprocess.run(['cc', '-shared', '-o', foo_so, foo_o], check=True) main_c = os.path.join(tmp, 'main.c') with open(main_c, 'w') as f: f.write('int foo(); int bar=3; int main() { foo(); }') foo_exe = os.path.join(tmp, 'foo') subprocess.run([rusi, '-o', foo_exe, main_c, foo_so], check=True) def тест_include_next(rusi: str, tmp: str): """Тест #include_next""" next1 = os.path.join(tmp, 'next1') next2 = os.path.join(tmp, 'next2') next3 = os.path.join(tmp, 'next3') os.makedirs(next1, exist_ok=True) os.makedirs(next2, exist_ok=True) os.makedirs(next3, exist_ok=True) file_c = os.path.join(tmp, 'file.c') with open(file_c, 'w') as f: f.write('#include "file1.h"\n') with open(os.path.join(next1, 'file1.h'), 'w') as f: f.write('#include_next "file1.h"\n') with open(os.path.join(next2, 'file1.h'), 'w') as f: f.write('#include_next "file2.h"\n') with open(os.path.join(next3, 'file2.h'), 'w') as f: f.write('foo') result = subprocess.run([rusi, f'-I{next1}', f'-I{next2}', f'-I{next3}', '-E', file_c], capture_output=True, text=True) проверка_содержимого(result.stdout, 'foo', "цепочка include_next не привела к foo") def тест_static(rusi: str, tmp: str): """Тест -static""" foo_c = os.path.join(tmp, 'foo.c') bar_c = os.path.join(tmp, 'bar.c') with open(foo_c, 'w') as f: f.write('extern int bar; int foo() { return bar; }') with open(bar_c, 'w') as f: f.write('int foo(); int bar=3; int main() { foo(); }') foo_exe = os.path.join(tmp, 'foo') subprocess.run([rusi, '-static', '-o', foo_exe, foo_c, bar_c], check=True) result = subprocess.run(['file', foo_exe], capture_output=True, text=True) if 'statically linked' not in result.stdout: raise AssertionError("файл не является статически слинкованным") def тест_shared(rusi: str, tmp: str): """Тест -shared""" foo_so = os.path.join(tmp, 'foo.so') foo_c = os.path.join(tmp, 'foo.c') bar_c = os.path.join(tmp, 'bar.c') with open(foo_c, 'w') as f: f.write('extern int bar; int foo() { return bar; }') with open(bar_c, 'w') as f: f.write('int foo(); int bar=3; int main() { foo(); }') subprocess.run([rusi, '-fPIC', '-shared', '-o', foo_so, foo_c, bar_c], check=True) def тест_L(rusi: str, tmp: str): """Тест -L""" foo_c = os.path.join(tmp, 'foo.c') with open(foo_c, 'w') as f: f.write('extern int bar; int foo() { return bar; }') libfoobar_so = os.path.join(tmp, 'libfoobar.so') subprocess.run([rusi, '-fPIC', '-shared', '-o', libfoobar_so, foo_c], check=True) bar_c = os.path.join(tmp, 'bar.c') with open(bar_c, 'w') as f: f.write('int foo(); int bar=3; int main() { foo(); }') foo_exe = os.path.join(tmp, 'foo') subprocess.run([rusi, '-o', foo_exe, bar_c, f'-L{tmp}', '-lfoobar'], check=True) def тест_Wl(rusi: str, tmp: str): """Тест -Wl,""" foo_o = os.path.join(tmp, 'foo.o') bar_o = os.path.join(tmp, 'bar.o') baz_o = os.path.join(tmp, 'baz.o') subprocess.run([rusi, '-c', '-o', foo_o, '-xc', '-'], input='int foo() {}', text=True, check=True) subprocess.run([rusi, '-c', '-o', bar_o, '-xc', '-'], input='int foo() {}', text=True, check=True) subprocess.run([rusi, '-c', '-o', baz_o, '-xc', '-'], input='int main() {}', text=True, check=True) foo_exe = os.path.join(tmp, 'foo') subprocess.run(['cc', '-Wl,-z,muldefs,--gc-sections', '-o', foo_exe, foo_o, bar_o, baz_o], check=True) def тест_Xlinker(rusi: str, tmp: str): """Тест -Xlinker""" foo_o = os.path.join(tmp, 'foo.o') bar_o = os.path.join(tmp, 'bar.o') baz_o = os.path.join(tmp, 'baz.o') subprocess.run([rusi, '-c', '-o', foo_o, '-xc', '-'], input='int foo() {}', text=True, check=True) subprocess.run([rusi, '-c', '-o', bar_o, '-xc', '-'], input='int foo() {}', text=True, check=True) subprocess.run([rusi, '-c', '-o', baz_o, '-xc', '-'], input='int main() {}', text=True, check=True) foo_exe = os.path.join(tmp, 'foo') subprocess.run(['cc', '-Xlinker', '-z', '-Xlinker', 'muldefs', '-Xlinker', '--gc-sections', '-o', foo_exe, foo_o, bar_o, baz_o], check=True) if __name__ == '__main__': main()