Skip to content

RFC-036: тестовый фреймворк std.test и команда yaoxiang test

Аннотация

Введение стандартного тестового фреймворка std.test и подкоманды CLI yaoxiang test для YaoXiang. Тестовые файлы являются обычными файлами .yx, а успешное/неуспешное прохождение определяется через std.assert.assert + код завершения. Модуль std.test реализован на чистом YaoXiang и является первой библиотекой для самопроверки (dogfooding). yaoxiang test — это инструмент CLI, а не функциональность компилятора — не затрагивает никаких изменений в парсере, IR, байткоде или исполнительном механизме.

Мотивация

Зачем нужен тестовый фреймворк?

В текущей реализации YaoXiang покрытие тестами зависит от #[test] на стороне Rust и интеграционных тестов в tests/. Это означает:

  1. Модульные тесты стандартной библиотеки (std.math / std.list / std.dict / std.convert / std.io) нельзя писать на YaoXiang
  2. #117 Покрытие модульными тестами стандартной библиотеки заблокировано, поскольку нет доступной тестовой инфраструктуры
  3. Регрессионное тестирование языковых возможностей (например, изменение семантики spawn в RFC-032) не имеет автоматизации

Ключевые ограничения

  • 17 ключевых слов как железное правило: не вводить никаких новых ключевых слов или синтаксических конструкций
  • Нулевые изменения компилятора: не трогать парсер, IR, байткод, исполнитель
  • Приоритет самопроверки: тестовая библиотека написана на YaoXiang, первая библиотека для dogfooding

Архитектура

┌──────────────────────────────────────────────────────────────┐
│                    yaoxiang test                              │
│                                                              │
│  CLI слой:  yaoxiang test [--filter --fail-fast --json ...]    │
│              │                                               │
│  Слой обнаружения: чтение yaoxiang.toml → [tool.test] patterns│
│              По умолчанию: tests/**/*.yx                              │
│              │                                               │
│  Слой выполнения: для каждого файла: yaoxiang run <file>        │
│              проверка exit code → последовательное выполнение  │
│              │                                               │
│  Слой отчётности: PASS/FAIL → агрегация                        │
│              Поддержка --json / --verbose / --fail-fast         │
│                                                              │
│  Слой проверок: std.test (чистый YaoXiang, самопроверка)       │
│              Нижний уровень: std.assert.assert                  │
│              Диагностика: f"Expected {expected}, got {actual}"  │
└──────────────────────────────────────────────────────────────┘

Основные принципы

  1. Тестовый фреймворк — это не функциональность компилятора, а инструмент CLIyaoxiang run уже может "выполнять тесты", а yaoxiang test просто помогает запускать все файлы и показывать отчёт
  2. Нулевые изменения компилятора — не вводить сканирование аннотаций @test, метаданные байткода, специальные точки входа в исполнитель
  3. Самопроверка — модуль std.test реализован на чистом YaoXiang, использует в качестве основы std.assert.assert
  4. Тестовые файлы — это обычные файлы .yx — успешное/неуспешное прохождение определяется по коду завершения

Детальный дизайн

1. Дизайн CLI

yaoxiang test [OPTIONS] [PATHS]

Arguments:
  [PATHS]...      указать тестовые файлы или каталог (по умолчанию: из yaoxiang.toml, иначе tests/)

Options:
  --filter <NAME>     запустить только тесты с именем файла, содержащим <NAME>
  --fail-fast         остановиться при первом провале
  --verbose, -v       показать детальный stdout/stderr для каждого теста
  --list              только перечислить тестовые файлы, не запускать
  --no-progress       не показывать прогресс-бар (для CI)
  --json              вывести результаты в формате JSON (для интеграции CI)

Формат вывода

Вывод по умолчанию:

Running 5 tests from 3 files...

tests/math_test.yx ........................ PASS (0.002s)
tests/list_test.yx ........................ PASS (0.001s)
tests/string_test.yx ...................... FAIL (0.003s)
  `-- Expected "hello", got "world"
      at tests/string_test.yx:12:5

Results: 2 passed, 1 failed, 0 skipped (0.006s)

JSON вывод (--json):

json
{
  "summary": { "total": 3, "passed": 2, "failed": 1, "skipped": 0, "time_secs": 0.006 },
  "tests": [
    { "file": "tests/math_test.yx", "passed": true, "time_secs": 0.002 },
    {
      "file": "tests/string_test.yx",
      "passed": false,
      "time_secs": 0.003,
      "error": "Expected \"hello\", got \"world\"",
      "exit_code": 1
    }
  ]
}

2. Конфигурация yaoxiang.toml

Располагается в [tool.test], соответствует соглашению о сторонних расширениях [tool.*] из RFC-015:

toml
[project]
name = "my-project"

[tool.test]
patterns = ["tests/**/*.yx"]
# В будущем можно расширить:
# exclude = ["tests/fixtures/**"]
# parallel = true
  • По умолчанию patterns = ["tests/**/*.yx"] — нулевая конфигурация, работает из коробки
  • Однофайловый режим (yaoxiang test foo.yx) запускается напрямую, без чтения конфигурации
  • В будущем возможно выделение в отдельный репозиторий (расположение [tool.test] не изменится)

3. Модуль std.test (чистый YaoXiang)

yaoxiang
// std/test.yx — Pure YaoXiang test assertion library
// First dogfooding library: YaoXiang's test library written in YaoXiang

use std.assert

assert_eq: (a: ?, b: ?) -> Void = (a, b) => {
    assert.assert(a == b, f"Expected {b}, got {a}")
}

assert_ne: (a: ?, b: ?) -> Void = (a, b) => {
    assert.assert(a != b, f"Expected not equal to {b}, got {a}")
}

assert_true: (cond: Bool) -> Void = (cond) => {
    assert.assert(cond, f"Expected true, got {cond}")
}

assert_false: (cond: Bool) -> Void = (cond) => {
    assert.assert(!cond, f"Expected false, got {cond}")
}
  • 4 функции проверки, все используют f"..." для диагностических сообщений
  • Параметры-обобщения ? в assert_eq / assert_ne зависят от системы обобщений
  • std.test не зависит от нативного кода, реализован на чистом YaoXiang

4. Механизм загрузки стандартной библиотеки (ключевой дизайн)

Фаза 1: Встраивание в бинарный файл

std/test.yx (и все будущие модули стандартной библиотеки на YaoXiang) встраиваются в бинарный файл при сборке:

rust
// build.rs или скрипт сборки, автогенерация
pub const STD_YX_FILES: &[(&str, &str)] = &[
    ("std/test.yx", r#"..."#),  // текст исходного кода
    // в будущем больше
];

Загрузчик модулей при разборе use std.test:

  1. Сначала проверяет нативные модули Rust (существующий механизм, например std.assert)
  2. Если не найдено, ищет во встроенных STD_YX_FILES, находит исходный код std/test.yx
  3. Компилирует этот исходный код и регистрирует в системе модулей

Преимущества:

  • В однофайловом режиме use std.test тоже работает
  • Версия стандартной библиотеки жёстко привязана к бинарному файлу, невозможно несоответствие версий
  • Не требует от пользователя настройки пути к стандартной библиотеке

Будущее: стандартная библиотека в файловой системе

Когда модель проектов YaoXiang созреет, стандартная библиотека перейдёт на файловую форму. Подробности в обновлении RFC-014.

5. Обнаружение и выполнение

Фаза обнаружения:

  1. Если указан [PATHS], используются указанные пути напрямую
  2. Иначе читается [tool.test].patterns из yaoxiang.toml
  3. Если конфигурация отсутствует, по умолчанию tests/**/*.yx
  4. Применяется фильтрация --filter (имя файла содержит)

Фаза выполнения:

  1. Для каждого файла: запуск подпроцесса yaoxiang run <file>
  2. Проверка exit code: 0 = PASS, не 0 = FAIL
  3. Захват stdout/stderr для отчётности
  4. Только последовательное выполнение (Фаза 1), в будущем поддержка --parallel
  5. При --fail-fast — немедленная остановка при первом FAIL

6. Изоляция тестов

Изоляция тестов естественно реализуется через границы процессов:

  • Каждый тестовый файл выполняется в отдельном дочернем процессе
  • Каждый дочерний процесс имеет независимые Heap, Frame, NativeContext
  • Паника в одном тестовом файле не влияет на другие тестовые файлы
  • Не требуется дополнительный механизм изоляции контекстов Heap

Связь с существующей системой

КомпонентСвязь
Rust #[test]Без изменений, внутренние тесты компилятора остаются на Rust
Существующие интеграционные тесты .yx (tests/yaoxiang/)Обнаруживаются и выполняются через yaoxiang test
std.assert.assert(cond)Сохраняется, std.test зависит от него на нижнем уровне
#200 рефакторинг (io.printlnassert.assert)Полностью согласуется с yaoxiang test
Аннотация @Не используется, @test не вводится

Стратегия реализации

Фаза 1: Основная функциональность

Область изменений:

  • src/main.rs — новая подкоманда Test
  • src/std/test.yx — новый модуль на чистом YaoXiang
  • build.rs — встраивание std/*.yx в бинарный файл
  • Загрузчик модулей — поддержка загрузки модулей .yx из встроенных источников
  • Парсинг конфигурации RFC-015 — секция [tool.test]
  • Выполнение подпроцессов + отчётность

Результат:

  • yaoxiang test базово работает
  • 4 функции проверки в std.test
  • Обнаружение по умолчанию tests/**/*.yx
  • Последовательное выполнение + формат вывода по умолчанию

Фаза 2: Улучшения

  • Параметры --filter / --fail-fast / --verbose
  • Вывод --json (интеграция CI)
  • Опция --list
  • Опция --no-progress

Фаза 3: Продвинутые возможности

  • --parallel параллельное выполнение (зависит от завершения модели параллелизма spawn)
  • Конфигурация [tool.test].exclude
  • Дополнительные функции проверки (например, assert_approx_eq для Float)

Риски и митигация

РискВероятностьМитигация
Ошибка интерполяции f"..." на обобщённых типахНизкаяБазовая применимость к типам уже проверена в std.assert.assert
Накладные расходы на запуск подпроцессов влияют на скорость тестовСредняяПоследовательное выполнение в Фазе 1 приемлемо; параллелизм в Фазе 3 смягчит
Парсинг конфигурации yaoxiang.toml отсутствует в текущем CLIНизкаяПростое расширение, не влияет на основную функциональность
Обобщения ? недоступны в std.testНизкаяВозможен откат к типу Any или специализация типов
Встраивание исходных файлов .yx в бинарный файл увеличивает размерНизкаяИсходные файлы .yx очень малы, можно пренебречь

Открытые вопросы

  • [ ] Сможет ли загрузчик модулей правильно разрешить импорт use std.assert в std/test.yx? Необходимо проверить разрешение зависимостей между встроенными модулями-источниками
  • [ ] Не создаст ли to_string обобщённого типа в f"..." новых ограничений типа? Необходимо проверить

Журнал решений по дизайну

РешениеРешениеДатаОбоснование
Способ маркировки тестовБез аннотации @test, тестовые файлы — обычные .yx2026-07-26Нулевые изменения компилятора, изоляция через подпроцессы
Способ проверокФункции модуля std.test на чистом YaoXiang2026-07-26Самопроверка, без нативного кода
Модель выполнения тестовПодпроцесс yaoxiang run <file> + exit code2026-07-26Изоляция на уровне процессов, нулевые изменения компилятора
Загрузка стандартной библиотекиТекущая: встраивание в бинарный файл, будущее: файловая система2026-07-26Привязка версий, работа в однофайловом режиме
Обобщённые проверкиЗависимость от параметра-обобщения ?2026-07-26Без специализации, доверие к системе обобщений

Ссылки