Configures isolate to use cgroups for improved resource management. Adds the `--cg` flag to the isolate init and cleanup commands, leveraging cgroups for better resource isolation during code execution testing. Makes isolate configuration file explicit.
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
Процесс тестирования
- Приём запроса: Gateway получает запрос с кодом и URL пакета
- Скачивание пакета: Gateway скачивает Polygon пакет
- Маршрутизация: Gateway отправляет запрос в Worker для нужного языка
- Парсинг: Worker распаковывает и парсит пакет (тесты, лимиты)
- Компиляция: Worker выбирает нужный компилятор на основе языка и компилирует код
- Тестирование: Worker последовательно запускает все тесты
- Callback: Worker отправляет статусы на callback URL на каждом этапе
- 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
Расширение на другие языки
Для добавления поддержки нового языка:
- Создайте новую реализацию
ICompilationServiceдля языка (например,GoCompilationService.cs) - Создайте новую реализацию
IExecutionServiceдля языка (например,GoExecutionService.cs) - Зарегистрируйте новые сервисы в
Program.cs:builder.Services.AddSingleton<GoCompilationService>(); builder.Services.AddSingleton<GoExecutionService>(); - Обновите фабрики (
CompilationServiceFactoryиExecutionServiceFactory), добавив поддержку нового языка - Добавьте конфигурацию языка в
appsettings.jsonWorker - Обновите
DockerfileWorker, добавив установку компилятора/runtime - Добавьте конфигурацию воркера для нового языка в
appsettings.jsonGateway - Обновите
WorkerClientServiceGateway, добавив маппинг языка на URL воркера
Разработка
Структура кода
- Controllers: REST API endpoints
- Services: Бизнес-логика (компиляция, тестирование, callback)
- Models: Модели данных
Логирование
Используется встроенный ILogger ASP.NET Core.
Тестирование
dotnet test
Лицензия
[Укажите лицензию]
Авторы
LiquidCode Team