rusi/test/driver.py

987 lines
44 KiB
Python
Executable file
Raw Permalink 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.

#!/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()