Files
LiquidCode.Tester/README.md

14 KiB
Raw Blame History

LiquidCode Tester

Система автоматической проверки решений задач по программированию (Online Judge) для платформы LiquidCode.

Архитектура

Проект состоит из трёх основных компонентов:

1. Gateway (Шлюз)

  • Технология: ASP.NET Core Web API
  • Функции:
    • Приём запросов на тестирование через REST API
    • Скачивание пакетов задач из Polygon
    • Маршрутизация запросов к соответствующим Worker'ам по языку программирования

2. Worker (Универсальный тестировщик)

  • Технология: ASP.NET Core Web API + компиляторы всех поддерживаемых языков
  • Функции:
    • Динамический выбор компилятора/интерпретатора на основе языка
    • Компиляция пользовательского кода (C++, Java, Kotlin, C#) или подготовка к исполнению (Python)
    • Запуск в изолированной среде
    • Тестирование на наборе тестов
    • Контроль ограничений по времени и памяти
    • Отправка статусов на callback URL

3. Common (Общая библиотека)

  • Технология: .NET Class Library
  • Функции: Общие модели данных для Gateway и Worker

Поддерживаемые языки

  • C++ (реализовано)
  • Java (реализовано)
  • Kotlin (реализовано)
  • C# (реализовано)
  • Python (реализовано)

Модель данных

SubmitForTesterModel (запрос на тестирование)

public record SubmitForTesterModel(
    long Id,
    long MissionId,
    string Language,
    string LanguageVersion,
    string SourceCode,
    string PackageUrl,
    string CallbackUrl
);

TesterResponseModel (результат тестирования)

public record TesterResponseModel(
    long SubmitId,
    State State,          // Waiting, Compiling, Testing, Done
    ErrorCode ErrorCode,  // None, CompileError, RuntimeError, etc.
    string Message,
    int CurrentTest,
    int AmountOfTests
);

Быстрый старт

Локальная разработка (Docker Compose)

# Сборка и запуск всех сервисов
docker-compose up --build

# Gateway доступен на http://localhost:8080
# Worker доступен на http://localhost:8081

Разработка без Docker

# Сборка всего решения
dotnet build

# Запуск Gateway
cd src/LiquidCode.Tester.Gateway
dotnet run

# Запуск Worker (в другом терминале)
cd src/LiquidCode.Tester.Worker
dotnet run

Локальное тестирование

Для тестирования системы без внешних зависимостей используйте endpoint /api/Tester/submit-local:

1. Подготовка тестового пакета

Создайте ZIP файл с тестами в формате:

test-package.zip
├── 1.in      # Входные данные для теста 1
├── 1.out     # Ожидаемый результат для теста 1
├── 2.in      # Входные данные для теста 2
├── 2.out     # Ожидаемый результат для теста 2
└── ...

2. Отправка запроса

curl -X 'POST' \
  'http://localhost:8080/api/Tester/submit-local' \
  -F 'id=1' \
  -F 'missionId=1' \
  -F 'language=python' \
  -F 'languageVersion=3.11' \
  -F 'sourceCode=n = int(input())
print(n * 2)' \
  -F 'callbackUrl=log' \
  -F 'package=@./test-package.zip'

3. Просмотр результатов

При использовании callbackUrl=log результаты тестирования выводятся в консоль Worker:

╔═══════════════════════════════════════════════════════════════╗
║                    CALLBACK RESULT                            ║
╠═══════════════════════════════════════════════════════════════╣
{
  "submitId": 1,
  "state": "Done",
  "errorCode": "None",
  "message": "All tests passed",
  "currentTest": 3,
  "amountOfTests": 3
}
╚═══════════════════════════════════════════════════════════════╝

Специальные значения callbackUrl

  • "log" - Вывод результатов в логи Worker
  • "console" - Аналогично "log"
  • "log://" - Аналогично "log"
  • Любой HTTP URL - Отправка результатов на указанный endpoint

API Endpoints

Gateway

POST /api/tester/submit

Отправка решения с удаленным пакетом тестов:

{
  "id": 123,
  "missionId": 456,
  "language": "C++",
  "languageVersion": "17",
  "sourceCode": "#include <iostream>\nint main() { ... }",
  "packageUrl": "https://example.com/package.zip",
  "callbackUrl": "https://example.com/api/submit/callback"
}

POST /api/tester/submit-local

Отправка решения с локальным пакетом тестов (для отладки):

curl -X 'POST' \
  'http://localhost:8080/api/Tester/submit-local' \
  -F 'id=1' \
  -F 'missionId=1' \
  -F 'language=python' \
  -F 'languageVersion=3.11' \
  -F 'sourceCode=print("Hello, World!")' \
  -F 'callbackUrl=log' \
  -F 'package=@/path/to/test-package.zip'

Параметры:

  • id - ID сабмита
  • missionId - ID задачи
  • language - Язык программирования (C++, Java, Kotlin, C#, Python)
  • languageVersion - Версия языка (опционально, используется "latest" по умолчанию)
  • sourceCode - Исходный код решения
  • callbackUrl - URL для отправки результатов или "log" для вывода в консоль
  • package - ZIP файл с тестами

GET /api/tester/health

  • Проверка состояния Gateway

Worker

POST /api/test

  • Принимает multipart/form-data с метаданными и ZIP файлом пакета

GET /api/test/health

  • Проверка состояния Worker

Процесс тестирования

  1. Приём запроса: Gateway получает запрос с кодом и URL пакета
  2. Скачивание пакета: Gateway скачивает Polygon пакет
  3. Маршрутизация: Gateway отправляет запрос в Worker для нужного языка
  4. Парсинг: Worker распаковывает и парсит пакет (тесты, лимиты)
  5. Компиляция: Worker выбирает нужный компилятор на основе языка и компилирует код
  6. Тестирование: Worker последовательно запускает все тесты
  7. Callback: Worker отправляет статусы на callback URL на каждом этапе
  8. Cleanup: Worker удаляет временные файлы

Изоляция и безопасность

Текущая реализация

  • Процессы запускаются с ограничением по времени
  • Контроль памяти через PeakWorkingSet64
  • Процессы убиваются при превышении лимитов

Production рекомендации

  • Linux namespaces (PID, network, mount)
  • cgroups для контроля CPU и памяти
  • seccomp для ограничения системных вызовов
  • AppArmor/SELinux профили
  • chroot окружение
  • Отключение доступа к сети для тестируемого кода

Конфигурация

Gateway (appsettings.json)

{
  "PackageDownloadDirectory": "/tmp/packages",
  "Workers": {
    "Cpp": "http://localhost:8081",
    "Java": "http://localhost:8081",
    "Kotlin": "http://localhost:8081",
    "CSharp": "http://localhost:8081",
    "Python": "http://localhost:8081"
  }
}

Worker (appsettings.json)

{
  "Cpp": {
    "Compiler": "g++",
    "CompilerFlags": "-O2 -std=c++17 -Wall",
    "Versions": {
      "14": {
        "Compiler": "g++",
        "CompilerFlags": "-O2 -std=c++14 -Wall"
      },
      "17": {
        "Compiler": "g++",
        "CompilerFlags": "-O2 -std=c++17 -Wall"
      },
      "20": {
        "Compiler": "g++",
        "CompilerFlags": "-O2 -std=c++20 -Wall"
      }
    }
  },
  "Java": {
    "Compiler": "javac",
    "CompilerFlags": "",
    "Versions": {
      "8": {
        "Compiler": "javac",
        "CompilerFlags": "-source 8 -target 8"
      },
      "11": {
        "Compiler": "javac",
        "CompilerFlags": "-source 11 -target 11"
      },
      "17": {
        "Compiler": "javac",
        "CompilerFlags": ""
      }
    }
  },
  "Kotlin": {
    "Compiler": "kotlinc",
    "CompilerFlags": "",
    "Versions": {
      "1.9": {
        "Compiler": "kotlinc",
        "CompilerFlags": ""
      }
    }
  },
  "CSharp": {
    "Compiler": "csc",
    "CompilerFlags": "/optimize+",
    "Versions": {
      "7": {
        "Compiler": "csc",
        "CompilerFlags": "/optimize+ /langversion:7"
      },
      "8": {
        "Compiler": "csc",
        "CompilerFlags": "/optimize+ /langversion:8"
      },
      "9": {
        "Compiler": "csc",
        "CompilerFlags": "/optimize+ /langversion:9"
      }
    }
  },
  "Python": {
    "Executable": "python3",
    "Versions": {
      "3.8": {
        "Executable": "python3.8"
      },
      "3.9": {
        "Executable": "python3.9"
      },
      "3.10": {
        "Executable": "python3.10"
      },
      "3.11": {
        "Executable": "python3.11"
      }
    }
  }
}

Поддержка версий языков

Система поддерживает указание версии языка программирования через параметр languageVersion:

  • "latest" или null - использует версию по умолчанию из основной конфигурации
  • Конкретная версия (например, "17", "3.11") - использует конфигурацию из секции Versions
  • Несуществующая версия - логируется предупреждение и используется версия по умолчанию

Каждый язык имеет свою конфигурацию с возможностью указания:

  • Для компилируемых языков (C++, Java, Kotlin, C#): путь к компилятору и флаги компиляции
  • Для интерпретируемых языков (Python): путь к интерпретатору

Пример использования:

# Использование Python 3.11
curl -X POST 'http://localhost:8080/api/Tester/submit-local' \
  -F 'languageVersion=3.11' \
  -F 'language=python' \
  ...

# Использование C++20
curl -X POST 'http://localhost:8080/api/Tester/submit-local' \
  -F 'languageVersion=20' \
  -F 'language=C++' \
  ...

Структура проекта

LiquidCode.Tester/
├── src/
│   ├── LiquidCode.Tester.Common/        # Общие модели
│   │   └── Models/
│   ├── LiquidCode.Tester.Gateway/       # API Gateway
│   │   ├── Controllers/
│   │   ├── Services/
│   │   └── Dockerfile
│   └── LiquidCode.Tester.Worker/        # Универсальный Worker (все языки)
│       ├── Controllers/
│       ├── Services/
│       │   ├── Compilation/             # Компиляторы
│       │   ├── Execution/               # Исполнители
│       │   └── Factories/               # Фабрики
│       └── Dockerfile
├── compose.yaml                          # Docker Compose
└── README.md

Требования

  • .NET 9.0 SDK
  • Docker & Docker Compose (для контейнеризации)
  • Компиляторы языков программирования (устанавливаются автоматически в Docker):
    • g++ (C++)
    • OpenJDK 17 (Java)
    • Kotlin compiler (Kotlin)
    • Mono (C#)
    • Python 3

Расширение на другие языки

Для добавления поддержки нового языка:

  1. Создайте новую реализацию ICompilationService для языка (например, GoCompilationService.cs)
  2. Создайте новую реализацию IExecutionService для языка (например, GoExecutionService.cs)
  3. Зарегистрируйте новые сервисы в Program.cs:
    builder.Services.AddSingleton<GoCompilationService>();
    builder.Services.AddSingleton<GoExecutionService>();
    
  4. Обновите фабрики (CompilationServiceFactory и ExecutionServiceFactory), добавив поддержку нового языка
  5. Добавьте конфигурацию языка в appsettings.json Worker
  6. Обновите Dockerfile Worker, добавив установку компилятора/runtime
  7. Добавьте конфигурацию воркера для нового языка в appsettings.json Gateway
  8. Обновите WorkerClientService Gateway, добавив маппинг языка на URL воркера

Разработка

Структура кода

  • Controllers: REST API endpoints
  • Services: Бизнес-логика (компиляция, тестирование, callback)
  • Models: Модели данных

Логирование

Используется встроенный ILogger ASP.NET Core.

Тестирование

dotnet test

Лицензия

[Укажите лицензию]

Авторы

LiquidCode Team