# 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 \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(); builder.Services.AddSingleton(); ``` 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