434 lines
14 KiB
Markdown
434 lines
14 KiB
Markdown
# 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 (запрос на тестирование)
|
||
```csharp
|
||
public record SubmitForTesterModel(
|
||
long Id,
|
||
long MissionId,
|
||
string Language,
|
||
string LanguageVersion,
|
||
string SourceCode,
|
||
string PackageUrl,
|
||
string CallbackUrl
|
||
);
|
||
```
|
||
|
||
### TesterResponseModel (результат тестирования)
|
||
```csharp
|
||
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)
|
||
|
||
```bash
|
||
# Сборка и запуск всех сервисов
|
||
docker-compose up --build
|
||
|
||
# Gateway доступен на http://localhost:8080
|
||
# Worker доступен на http://localhost:8081
|
||
```
|
||
|
||
### Разработка без Docker
|
||
|
||
```bash
|
||
# Сборка всего решения
|
||
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. Отправка запроса
|
||
|
||
```bash
|
||
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**
|
||
|
||
Отправка решения с удаленным пакетом тестов:
|
||
|
||
```json
|
||
{
|
||
"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**
|
||
|
||
Отправка решения с локальным пакетом тестов (для отладки):
|
||
|
||
```bash
|
||
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)
|
||
```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)
|
||
```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): путь к интерпретатору
|
||
|
||
Пример использования:
|
||
```bash
|
||
# Использование 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`:
|
||
```csharp
|
||
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.
|
||
|
||
### Тестирование
|
||
```bash
|
||
dotnet test
|
||
```
|
||
|
||
## Лицензия
|
||
|
||
[Укажите лицензию]
|
||
|
||
## Авторы
|
||
|
||
LiquidCode Team
|