add job queue and code submission tests; remove unused main executable
This commit is contained in:
487
backend/Readme.md
Normal file
487
backend/Readme.md
Normal file
@@ -0,0 +1,487 @@
|
|||||||
|
# Monaco Backend - Code Execution Service
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
|
||||||
|
1. Introduction
|
||||||
|
2. Architecture
|
||||||
|
3. Installation
|
||||||
|
4. API Reference
|
||||||
|
5. Code Execution
|
||||||
|
6. Job Queue System
|
||||||
|
7. Language Support
|
||||||
|
8. Security Considerations
|
||||||
|
9. Configuration
|
||||||
|
10. Testing
|
||||||
|
11. Performance Tuning
|
||||||
|
12. Troubleshooting
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
|
||||||
|
Monaco is a secure, containerized code execution backend service designed to run user-submitted code in multiple programming languages. It features a job queue system to manage execution resources, containerized execution environments for security, and a RESTful API for submission and monitoring.
|
||||||
|
|
||||||
|
**Key Features:**
|
||||||
|
- Multi-language support (Python, Java, C, C++)
|
||||||
|
- Secure containerized execution using Docker
|
||||||
|
- Resource limiting to prevent abuse
|
||||||
|
- Job queuing for managing concurrent executions
|
||||||
|
- Detailed execution statistics and monitoring
|
||||||
|
- Support for user input via stdin
|
||||||
|
- CORS support for browser-based clients
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
### Component Overview
|
||||||
|
|
||||||
|
Monaco follows a layered architecture with the following key components:
|
||||||
|
|
||||||
|
1. **HTTP Handlers** (handler package) - Processes incoming HTTP requests
|
||||||
|
2. **Execution Service** (service package) - Manages code execution in containers
|
||||||
|
3. **Job Queue** (queue package) - Controls concurrent execution
|
||||||
|
4. **Data Models** (model package) - Defines data structures
|
||||||
|
|
||||||
|
### Request Flow
|
||||||
|
|
||||||
|
1. Client submits code via `/submit` endpoint
|
||||||
|
2. Request is validated and assigned a unique ID
|
||||||
|
3. Submission is added to the job queue
|
||||||
|
4. Worker picks job from queue when available
|
||||||
|
5. Code is executed in appropriate Docker container
|
||||||
|
6. Results are stored and available via `/result` endpoint
|
||||||
|
|
||||||
|
### Dependency Diagram
|
||||||
|
|
||||||
|
```
|
||||||
|
Client Request → HTTP Handlers → Execution Service → Job Queue → Docker Containers
|
||||||
|
↑
|
||||||
|
Data Models
|
||||||
|
```
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
- Go 1.22+
|
||||||
|
- Docker Engine
|
||||||
|
- Docker images for supported languages:
|
||||||
|
- `python:3.9`
|
||||||
|
- `eclipse-temurin:11-jdk-alpine`
|
||||||
|
- `gcc:latest`
|
||||||
|
|
||||||
|
### Setup Instructions
|
||||||
|
|
||||||
|
1. Clone the repository:
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/arnab-afk/monaco.git
|
||||||
|
cd monaco/backend
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Install Go dependencies:
|
||||||
|
```bash
|
||||||
|
go mod download
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Build the application:
|
||||||
|
```bash
|
||||||
|
go build -o monaco main.go
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Run the service:
|
||||||
|
```bash
|
||||||
|
./monaco
|
||||||
|
```
|
||||||
|
|
||||||
|
The service will start on port 8080 by default.
|
||||||
|
|
||||||
|
## API Reference
|
||||||
|
|
||||||
|
### Endpoints
|
||||||
|
|
||||||
|
#### `POST /submit`
|
||||||
|
|
||||||
|
Submits code for execution.
|
||||||
|
|
||||||
|
**Request Body:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"language": "python", // Required: "python", "java", "c", or "cpp"
|
||||||
|
"code": "print('Hello, World!')", // Required: source code to execute
|
||||||
|
"input": "optional input string" // Optional: input to stdin
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "6423259c-ee14-c5aa-1c90-d5e989f92aa1" // Unique ID for this submission
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Status Codes:**
|
||||||
|
- 202 Accepted - Code accepted for execution
|
||||||
|
- 400 Bad Request - Invalid request (e.g., unsupported language)
|
||||||
|
|
||||||
|
#### `GET /status?id={submissionId}`
|
||||||
|
|
||||||
|
Checks the status of a submission.
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "6423259c-ee14-c5aa-1c90-d5e989f92aa1",
|
||||||
|
"status": "completed", // "pending", "queued", "running", "completed", "failed"
|
||||||
|
"queuedAt": "2025-03-25T14:30:00Z",
|
||||||
|
"startedAt": "2025-03-25T14:30:01Z", // Only present if status is "running", "completed", or "failed"
|
||||||
|
"completedAt": "2025-03-25T14:30:02Z", // Only present if status is "completed" or "failed"
|
||||||
|
"executionTime": 1000 // Execution time in milliseconds (only if completed)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Status Codes:**
|
||||||
|
- 200 OK - Status retrieved successfully
|
||||||
|
- 400 Bad Request - Missing ID parameter
|
||||||
|
- 404 Not Found - Submission with given ID not found
|
||||||
|
|
||||||
|
#### `GET /result?id={submissionId}`
|
||||||
|
|
||||||
|
Gets the execution result of a submission.
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": "6423259c-ee14-c5aa-1c90-d5e989f92aa1",
|
||||||
|
"status": "completed",
|
||||||
|
"language": "python",
|
||||||
|
"output": "Hello, World!",
|
||||||
|
"queuedAt": "2025-03-25T14:30:00Z",
|
||||||
|
"startedAt": "2025-03-25T14:30:01Z",
|
||||||
|
"completedAt": "2025-03-25T14:30:02Z",
|
||||||
|
"executionTime": 1000,
|
||||||
|
"executionTimeFormatted": "1.0s",
|
||||||
|
"totalTime": 2000,
|
||||||
|
"totalTimeFormatted": "2.0s"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Status Codes:**
|
||||||
|
- 200 OK - Result retrieved successfully
|
||||||
|
- 400 Bad Request - Missing ID parameter
|
||||||
|
- 404 Not Found - Submission with given ID not found
|
||||||
|
|
||||||
|
#### `GET /queue-stats`
|
||||||
|
|
||||||
|
Gets statistics about the job queue.
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"queue_stats": {
|
||||||
|
"queue_length": 5,
|
||||||
|
"max_workers": 3,
|
||||||
|
"running_jobs": 3
|
||||||
|
},
|
||||||
|
"submissions": 42
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Code Execution
|
||||||
|
|
||||||
|
### Execution Process
|
||||||
|
|
||||||
|
1. **Language Detection**: The system identifies the programming language of the submission.
|
||||||
|
2. **Environment Setup**: A temporary directory is created for compiled languages.
|
||||||
|
3. **Container Setup**: Docker containers are configured with resource limits.
|
||||||
|
4. **Compilation**: For compiled languages (Java, C, C++), code is compiled first.
|
||||||
|
5. **Execution**: The program is executed with the provided input.
|
||||||
|
6. **Resource Monitoring**: Memory and CPU usage are limited during execution.
|
||||||
|
7. **Result Collection**: Output and errors are captured and stored.
|
||||||
|
|
||||||
|
### Language-Specific Processing
|
||||||
|
|
||||||
|
#### Python
|
||||||
|
- Directly executes Python code using the `-c` flag
|
||||||
|
- Uses `python:3.9` Docker image
|
||||||
|
- Resource limits: 100MB memory, 10% CPU
|
||||||
|
|
||||||
|
#### Java
|
||||||
|
- Detects class name using regex pattern matching
|
||||||
|
- Compiles with `javac` and runs with optimized JVM settings
|
||||||
|
- Uses `eclipse-temurin:11-jdk-alpine` Docker image
|
||||||
|
- Resource limits: 400MB memory, 50% CPU
|
||||||
|
- JVM flags: `-XX:+TieredCompilation`, `-XX:TieredStopAtLevel=1`, `-Xverify:none`
|
||||||
|
|
||||||
|
#### C/C++
|
||||||
|
- Saves code to a file in a temporary directory
|
||||||
|
- Compiles with `gcc`/`g++` and runs the executable
|
||||||
|
- Uses `gcc:latest` Docker image
|
||||||
|
- Resource limits: 100MB memory, 10% CPU
|
||||||
|
|
||||||
|
### Timeout Handling
|
||||||
|
|
||||||
|
All executions have a timeout limit:
|
||||||
|
- Python: 10 seconds
|
||||||
|
- Java: 15 seconds
|
||||||
|
- C/C++: 10 seconds
|
||||||
|
|
||||||
|
If execution exceeds this limit, the process is killed and an error is returned.
|
||||||
|
|
||||||
|
## Job Queue System
|
||||||
|
|
||||||
|
### Worker Pool
|
||||||
|
|
||||||
|
Monaco uses a worker pool to manage concurrent code executions:
|
||||||
|
|
||||||
|
- Default pool size: 20 workers (configurable)
|
||||||
|
- Maximum queue capacity: 100 jobs
|
||||||
|
- FIFO (First-In-First-Out) processing order
|
||||||
|
|
||||||
|
### Job Lifecycle
|
||||||
|
|
||||||
|
1. **Creation**: Job created when code is submitted
|
||||||
|
2. **Queuing**: Job added to queue with `queued` status
|
||||||
|
3. **Execution**: Worker picks job from queue and changes status to `running`
|
||||||
|
4. **Completion**: Job finishes with either `completed` or `failed` status
|
||||||
|
|
||||||
|
### Performance Metrics
|
||||||
|
|
||||||
|
The queue tracks and reports:
|
||||||
|
- Current queue length
|
||||||
|
- Number of running jobs
|
||||||
|
- Maximum worker count
|
||||||
|
- Total number of submissions
|
||||||
|
|
||||||
|
## Language Support
|
||||||
|
|
||||||
|
### Python
|
||||||
|
- **Version**: Python 3.9
|
||||||
|
- **Input Handling**: Direct stdin piping
|
||||||
|
- **Limitations**: No file I/O, no package imports outside standard library
|
||||||
|
|
||||||
|
### Java
|
||||||
|
- **Version**: Java 11 (Eclipse Temurin)
|
||||||
|
- **Class Detection**: Extracts class name from code using regex
|
||||||
|
- **Memory Settings**: 64MB min heap, 256MB max heap
|
||||||
|
- **Best Practices**: Use `public class` with the main method
|
||||||
|
|
||||||
|
### C
|
||||||
|
- **Version**: Latest GCC
|
||||||
|
- **Compilation Flags**: Default GCC settings
|
||||||
|
- **Execution**: Compiled binary
|
||||||
|
|
||||||
|
### C++
|
||||||
|
- **Version**: Latest G++
|
||||||
|
- **Standard**: C++17
|
||||||
|
- **Execution**: Compiled binary
|
||||||
|
|
||||||
|
## Security Considerations
|
||||||
|
|
||||||
|
### Containerization
|
||||||
|
|
||||||
|
All code execution happens within isolated Docker containers with:
|
||||||
|
- No network access (`--network=none`)
|
||||||
|
- Limited CPU and memory resources
|
||||||
|
- Limited file system access
|
||||||
|
- No persistent storage
|
||||||
|
|
||||||
|
### Resource Limiting
|
||||||
|
|
||||||
|
- **Memory Limits**: 100-400MB depending on language
|
||||||
|
- **CPU Limits**: 10-50% of CPU depending on language
|
||||||
|
- **File Descriptors**: Limited to 64 for Python
|
||||||
|
- **Execution Time**: Enforced timeouts (10-15 seconds)
|
||||||
|
|
||||||
|
### Known Limitations
|
||||||
|
|
||||||
|
- Container escape vulnerabilities
|
||||||
|
- Docker daemon security depends on host configuration
|
||||||
|
- Resource limits can be circumvented with certain techniques
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
The service can be configured through environment variables:
|
||||||
|
|
||||||
|
- `PORT`: HTTP port (default: 8080)
|
||||||
|
- `MAX_WORKERS`: Maximum concurrent executions (default: 3)
|
||||||
|
- `QUEUE_SIZE`: Maximum queue size (default: 100)
|
||||||
|
- `DEFAULT_LANGUAGE`: Default language if none specified (default: "python")
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
### Unit Tests
|
||||||
|
|
||||||
|
Run unit tests with:
|
||||||
|
```bash
|
||||||
|
go test ./...
|
||||||
|
```
|
||||||
|
|
||||||
|
# Monaco Backend Test Plan
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
This test plan outlines the testing approach for the Monaco code execution backend service.
|
||||||
|
|
||||||
|
## Test Environment
|
||||||
|
- Development: Local workstations with Docker and Go
|
||||||
|
- Testing: Dedicated test server with Docker
|
||||||
|
- Production-like: Staging environment with similar resources to production
|
||||||
|
|
||||||
|
## Test Types
|
||||||
|
|
||||||
|
### Unit Tests
|
||||||
|
- **Purpose**: Verify individual components work as expected
|
||||||
|
- **Components to Test**:
|
||||||
|
- Handler package
|
||||||
|
- Queue package
|
||||||
|
- Execution service
|
||||||
|
- Models
|
||||||
|
- **Tools**: Go testing framework
|
||||||
|
|
||||||
|
### Integration Tests
|
||||||
|
- **Purpose**: Verify components work together correctly
|
||||||
|
- **Focus Areas**:
|
||||||
|
- API endpoints
|
||||||
|
- End-to-end code execution flow
|
||||||
|
- Error handling
|
||||||
|
- **Tools**: Go testing framework, HTTP test utilities
|
||||||
|
|
||||||
|
### Load Tests
|
||||||
|
- **Purpose**: Verify system performance under load
|
||||||
|
- **Scenarios**:
|
||||||
|
- Concurrent submissions
|
||||||
|
- Mixed language workloads
|
||||||
|
- Queue saturation
|
||||||
|
- **Metrics**:
|
||||||
|
- Request throughput
|
||||||
|
- Response times
|
||||||
|
- Success rates
|
||||||
|
- Resource utilization
|
||||||
|
- **Tools**: Custom Python test scripts
|
||||||
|
|
||||||
|
## Test Data
|
||||||
|
- Simple programs in each language
|
||||||
|
- Programs with input requirements
|
||||||
|
- Programs with compile errors
|
||||||
|
- Programs with runtime errors
|
||||||
|
- Programs with timeouts
|
||||||
|
|
||||||
|
## Test Execution
|
||||||
|
1. Run unit tests on every code change
|
||||||
|
2. Run integration tests before merging to main branch
|
||||||
|
3. Run load tests weekly and before major releases
|
||||||
|
|
||||||
|
## Success Criteria
|
||||||
|
- All unit tests pass
|
||||||
|
- Integration tests complete successfully
|
||||||
|
- Load tests show acceptable performance metrics:
|
||||||
|
- 95% of requests complete successfully
|
||||||
|
- 95th percentile response time < 5 seconds
|
||||||
|
- System can handle 20 concurrent users
|
||||||
|
|
||||||
|
## Reporting
|
||||||
|
- Test results stored in CI/CD pipeline
|
||||||
|
- Performance metrics graphed over time
|
||||||
|
- Issues logged in GitHub issues
|
||||||
|
|
||||||
|
### Load Testing
|
||||||
|
|
||||||
|
A Python script (`test.py`) is included for load testing:
|
||||||
|
```bash
|
||||||
|
python test.py
|
||||||
|
```
|
||||||
|
|
||||||
|
This script sends 500 requests concurrently and reports performance metrics.
|
||||||
|
|
||||||
|
### Manual Testing with Curl
|
||||||
|
|
||||||
|
#### Python Example
|
||||||
|
```bash
|
||||||
|
curl -X POST http://localhost:8080/submit \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"language": "python",
|
||||||
|
"code": "print(\"Hello, World!\")\nfor i in range(5):\n print(f\"Number: {i}\")",
|
||||||
|
"input": ""
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Java Example
|
||||||
|
```bash
|
||||||
|
curl -X POST http://localhost:8080/submit \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"language": "java",
|
||||||
|
"code": "public class Solution {\n public static void main(String[] args) {\n System.out.println(\"Hello, World!\");\n for (int i = 0; i < 5; i++) {\n System.out.println(\"Number: \" + i);\n }\n }\n}",
|
||||||
|
"input": ""
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Performance Tuning
|
||||||
|
|
||||||
|
### Optimizing Worker Count
|
||||||
|
|
||||||
|
The optimal worker count depends on:
|
||||||
|
- CPU cores available
|
||||||
|
- Memory available
|
||||||
|
- Docker container startup time
|
||||||
|
|
||||||
|
For most single-server deployments, 3-5 workers is optimal.
|
||||||
|
|
||||||
|
### Memory Considerations
|
||||||
|
|
||||||
|
Each language has different memory requirements:
|
||||||
|
- Python: ~50-100MB per instance
|
||||||
|
- Java: ~200-400MB per instance
|
||||||
|
- C/C++: ~50-100MB per instance
|
||||||
|
|
||||||
|
Calculate total memory needs as: `(Python instances × 100MB) + (Java instances × 400MB) + (C/C++ instances × 100MB)`
|
||||||
|
|
||||||
|
### Disk Space Management
|
||||||
|
|
||||||
|
Temporary files are cleaned up after execution, but with high request volumes, ensure adequate disk space for concurrent operations (approximately 1-5MB per request for compiled languages).
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Common Issues
|
||||||
|
|
||||||
|
#### Docker Connection Errors
|
||||||
|
```
|
||||||
|
Error: Cannot connect to the Docker daemon
|
||||||
|
```
|
||||||
|
**Solution**: Ensure Docker daemon is running with `systemctl start docker` or `docker --version`
|
||||||
|
|
||||||
|
#### Permissions Issues
|
||||||
|
```
|
||||||
|
Error: Permission denied while trying to connect to the Docker daemon socket
|
||||||
|
```
|
||||||
|
**Solution**: Add user to docker group: `sudo usermod -aG docker $USER`
|
||||||
|
|
||||||
|
#### Container Resource Limits
|
||||||
|
```
|
||||||
|
Error: Container killed due to memory limit
|
||||||
|
```
|
||||||
|
**Solution**: Increase memory limits in execution service or optimize submitted code
|
||||||
|
|
||||||
|
#### File Not Found Errors
|
||||||
|
```
|
||||||
|
Error: Failed to write Java file
|
||||||
|
```
|
||||||
|
**Solution**: Check temporary directory permissions and disk space
|
||||||
|
|
||||||
|
### Logs
|
||||||
|
|
||||||
|
The service provides structured logs with prefixes for easier filtering:
|
||||||
|
- `[HTTP]` - API requests
|
||||||
|
- `[QUEUE]` - Queue operations
|
||||||
|
- `[WORKER-n]` - Worker activities
|
||||||
|
- `[EXEC-id]` - Execution details
|
||||||
|
- `[PYTHON/JAVA/C/CPP-id]` - Language-specific logs
|
||||||
|
- `[TIMEOUT-id]` - Timeout events
|
||||||
|
- `[RESULT-id]` - Execution results
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This project is licensed under the MIT License - see the LICENSE file for details.
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
Contributions are welcome! Please feel free to submit a Pull Request.
|
||||||
@@ -2,75 +2,13 @@ module github.com/arnab-afk/monaco
|
|||||||
|
|
||||||
go 1.22.3
|
go 1.22.3
|
||||||
|
|
||||||
|
require github.com/stretchr/testify v1.9.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
|
github.com/kr/pretty v0.3.0 // indirect
|
||||||
github.com/distribution/reference v0.6.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/docker/cli v26.1.5+incompatible // indirect
|
github.com/rogpeppe/go-internal v1.12.0 // indirect
|
||||||
github.com/docker/docker v26.1.5+incompatible // indirect
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
|
||||||
github.com/docker/go-connections v0.4.0 // indirect
|
|
||||||
github.com/docker/go-units v0.5.0 // indirect
|
|
||||||
github.com/expr-lang/expr v1.16.5 // indirect
|
|
||||||
github.com/fatih/color v1.16.0 // indirect
|
|
||||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
|
||||||
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
|
||||||
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
|
||||||
github.com/go-logr/logr v1.4.1 // indirect
|
|
||||||
github.com/go-logr/stdr v1.2.2 // indirect
|
|
||||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
|
||||||
github.com/go-playground/locales v0.14.1 // indirect
|
|
||||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
|
||||||
github.com/go-playground/validator/v10 v10.19.0 // indirect
|
|
||||||
github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1 // indirect
|
|
||||||
github.com/gogo/protobuf v1.3.2 // indirect
|
|
||||||
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
|
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
|
||||||
github.com/jmoiron/sqlx v1.3.5 // indirect
|
|
||||||
github.com/knadh/koanf/maps v0.1.1 // indirect
|
|
||||||
github.com/knadh/koanf/parsers/toml v0.1.0 // indirect
|
|
||||||
github.com/knadh/koanf/providers/env v0.1.0 // indirect
|
|
||||||
github.com/knadh/koanf/providers/file v0.1.0 // indirect
|
|
||||||
github.com/knadh/koanf/v2 v2.1.1 // indirect
|
|
||||||
github.com/labstack/echo/v4 v4.12.0 // indirect
|
|
||||||
github.com/labstack/gommon v0.4.2 // indirect
|
|
||||||
github.com/leodido/go-urn v1.4.0 // indirect
|
|
||||||
github.com/lib/pq v1.10.9 // indirect
|
|
||||||
github.com/lithammer/shortuuid/v4 v4.0.0 // indirect
|
|
||||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
|
||||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
|
||||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
|
||||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
|
||||||
github.com/moby/docker-image-spec v1.3.1 // indirect
|
|
||||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
|
||||||
github.com/opencontainers/image-spec v1.1.0 // indirect
|
|
||||||
github.com/pelletier/go-toml v1.9.5 // indirect
|
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
|
||||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
|
||||||
github.com/rabbitmq/amqp091-go v1.9.0 // indirect
|
|
||||||
github.com/rs/zerolog v1.32.0 // indirect
|
|
||||||
github.com/runabol/code-execution-demo v0.0.0-20231001172254-4b23df61090b // indirect
|
|
||||||
github.com/runabol/tork v0.1.98 // indirect
|
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
|
||||||
github.com/shirou/gopsutil/v3 v3.24.3 // indirect
|
|
||||||
github.com/shoenig/go-m1cpu v0.1.6 // indirect
|
|
||||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
|
||||||
github.com/tklauser/go-sysconf v0.3.12 // indirect
|
|
||||||
github.com/tklauser/numcpus v0.6.1 // indirect
|
|
||||||
github.com/urfave/cli/v2 v2.27.2 // indirect
|
|
||||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
|
||||||
github.com/valyala/fasttemplate v1.2.2 // indirect
|
|
||||||
github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 // indirect
|
|
||||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0 // indirect
|
|
||||||
go.opentelemetry.io/otel v1.26.0 // indirect
|
|
||||||
go.opentelemetry.io/otel/metric v1.26.0 // indirect
|
|
||||||
go.opentelemetry.io/otel/trace v1.26.0 // indirect
|
|
||||||
golang.org/x/crypto v0.22.0 // indirect
|
|
||||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect
|
|
||||||
golang.org/x/net v0.24.0 // indirect
|
|
||||||
golang.org/x/sys v0.19.0 // indirect
|
|
||||||
golang.org/x/text v0.14.0 // indirect
|
|
||||||
golang.org/x/time v0.5.0 // indirect
|
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
217
backend/go.sum
217
backend/go.sum
@@ -1,214 +1,23 @@
|
|||||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
|
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
|
||||||
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
|
||||||
github.com/docker/cli v26.1.5+incompatible h1:NxXGSdz2N+Ibdaw330TDO3d/6/f7MvHuiMbuFaIQDTk=
|
|
||||||
github.com/docker/cli v26.1.5+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
|
||||||
github.com/docker/docker v26.1.5+incompatible h1:NEAxTwEjxV6VbBMBoGG3zPqbiJosIApZjxlbrG9q3/g=
|
|
||||||
github.com/docker/docker v26.1.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
|
||||||
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
|
|
||||||
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
|
|
||||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
|
||||||
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
|
||||||
github.com/expr-lang/expr v1.16.5 h1:m2hvtguFeVaVNTHj8L7BoAyt7O0PAIBaSVbjdHgRXMs=
|
|
||||||
github.com/expr-lang/expr v1.16.5/go.mod h1:uCkhfG+x7fcZ5A5sXHKuQ07jGZRl6J0FCAaf2k4PtVQ=
|
|
||||||
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
|
|
||||||
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
|
|
||||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
|
||||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
|
||||||
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
|
|
||||||
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
|
|
||||||
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
|
|
||||||
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
|
|
||||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
|
||||||
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
|
|
||||||
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
|
||||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
|
||||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
|
||||||
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
|
||||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
|
||||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
|
||||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
|
||||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
|
||||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
|
||||||
github.com/go-playground/validator/v10 v10.19.0 h1:ol+5Fu+cSq9JD7SoSqe04GMI92cbn0+wvQ3bZ8b/AU4=
|
|
||||||
github.com/go-playground/validator/v10 v10.19.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
|
||||||
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
|
||||||
github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1 h1:TQcrn6Wq+sKGkpyPvppOz99zsMBaUOKXq6HSv655U1c=
|
|
||||||
github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
|
||||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
|
||||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
|
||||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
|
||||||
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
|
|
||||||
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
|
|
||||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
|
||||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
|
||||||
github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g=
|
|
||||||
github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ=
|
|
||||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
|
||||||
github.com/knadh/koanf/maps v0.1.1 h1:G5TjmUh2D7G2YWf5SQQqSiHRJEjaicvU0KpypqB3NIs=
|
|
||||||
github.com/knadh/koanf/maps v0.1.1/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI=
|
|
||||||
github.com/knadh/koanf/parsers/toml v0.1.0 h1:S2hLqS4TgWZYj4/7mI5m1CQQcWurxUz6ODgOub/6LCI=
|
|
||||||
github.com/knadh/koanf/parsers/toml v0.1.0/go.mod h1:yUprhq6eo3GbyVXFFMdbfZSo928ksS+uo0FFqNMnO18=
|
|
||||||
github.com/knadh/koanf/providers/env v0.1.0 h1:LqKteXqfOWyx5Ab9VfGHmjY9BvRXi+clwyZozgVRiKg=
|
|
||||||
github.com/knadh/koanf/providers/env v0.1.0/go.mod h1:RE8K9GbACJkeEnkl8L/Qcj8p4ZyPXZIQ191HJi44ZaQ=
|
|
||||||
github.com/knadh/koanf/providers/file v0.1.0 h1:fs6U7nrV58d3CFAFh8VTde8TM262ObYf3ODrc//Lp+c=
|
|
||||||
github.com/knadh/koanf/providers/file v0.1.0/go.mod h1:rjJ/nHQl64iYCtAW2QQnF0eSmDEX/YZ/eNFj5yR6BvA=
|
|
||||||
github.com/knadh/koanf/v2 v2.1.1 h1:/R8eXqasSTsmDCsAyYj+81Wteg8AqrV9CP6gvsTsOmM=
|
|
||||||
github.com/knadh/koanf/v2 v2.1.1/go.mod h1:4mnTRbZCK+ALuBXHZMjDfG9y714L7TykVnZkXbMU3Es=
|
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
|
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||||
|
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/labstack/echo/v4 v4.12.0 h1:IKpw49IMryVB2p1a4dzwlhP1O2Tf2E0Ir/450lH+kI0=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/labstack/echo/v4 v4.12.0/go.mod h1:UP9Cr2DJXbOK3Kr9ONYzNowSh7HP0aG0ShAyycHSJvM=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU=
|
|
||||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
|
||||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
|
||||||
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
|
||||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
|
||||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
|
||||||
github.com/lithammer/shortuuid/v4 v4.0.0 h1:QRbbVkfgNippHOS8PXDkti4NaWeyYfcBTHtw7k08o4c=
|
|
||||||
github.com/lithammer/shortuuid/v4 v4.0.0/go.mod h1:Zs8puNcrvf2rV9rTH51ZLLcj7ZXqQI3lv67aw4KiB1Y=
|
|
||||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
|
|
||||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
|
||||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
|
||||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
|
||||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
|
||||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
|
||||||
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
|
||||||
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
|
|
||||||
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
|
|
||||||
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
|
|
||||||
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
|
||||||
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
|
||||||
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
|
||||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
|
||||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
|
||||||
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
|
|
||||||
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
|
|
||||||
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
|
|
||||||
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
|
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
||||||
github.com/rabbitmq/amqp091-go v1.9.0 h1:qrQtyzB4H8BQgEuJwhmVQqVHB9O4+MNDJCCAcpc3Aoo=
|
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
||||||
github.com/rabbitmq/amqp091-go v1.9.0/go.mod h1:+jPrT9iY2eLjRaMSRHUhc3z14E/l85kv/f+6luSD3pc=
|
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||||
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
|
||||||
github.com/rs/zerolog v1.32.0 h1:keLypqrlIjaFsbmJOBdB/qvyF8KEtCWHwobLp5l/mQ0=
|
|
||||||
github.com/rs/zerolog v1.32.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
|
|
||||||
github.com/runabol/code-execution-demo v0.0.0-20231001172254-4b23df61090b h1:hSYt/eRkKwJ3C40aHMJJLlJg+ysSq7FvnTYYFymouhY=
|
|
||||||
github.com/runabol/code-execution-demo v0.0.0-20231001172254-4b23df61090b/go.mod h1:FRPaDaWfFOIYN5QjmQH4aH/F9Jc2CXtbzwB8c51sg4s=
|
|
||||||
github.com/runabol/tork v0.1.97 h1:XZo39ZXruiDipN3xdgQpKrLjiyDTHL8vjEtHHA8r5+U=
|
|
||||||
github.com/runabol/tork v0.1.97/go.mod h1:EuJuNGc5raepZ5fFE9meC4QbUVaWHedF5P8KEYFHBX4=
|
|
||||||
github.com/runabol/tork v0.1.98 h1:R/QkBTSF4sQLnW8nq5vlIPwtHuwllDBYjJMNlQ4vTkc=
|
|
||||||
github.com/runabol/tork v0.1.98/go.mod h1:EuJuNGc5raepZ5fFE9meC4QbUVaWHedF5P8KEYFHBX4=
|
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
|
||||||
github.com/shirou/gopsutil/v3 v3.24.3 h1:eoUGJSmdfLzJ3mxIhmOAhgKEKgQkeOwKpz1NbhVnuPE=
|
|
||||||
github.com/shirou/gopsutil/v3 v3.24.3/go.mod h1:JpND7O217xa72ewWz9zN2eIIkPWsDN/3pl0H8Qt0uwg=
|
|
||||||
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
|
|
||||||
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
|
|
||||||
github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
|
|
||||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
|
||||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
|
||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
|
||||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
|
||||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
|
||||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
|
|
||||||
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
|
|
||||||
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
|
|
||||||
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
|
|
||||||
github.com/urfave/cli/v2 v2.27.2 h1:6e0H+AkS+zDckwPCUrZkKX38mRaau4nL2uipkJpbkcI=
|
|
||||||
github.com/urfave/cli/v2 v2.27.2/go.mod h1:g0+79LmHHATl7DAcHO99smiR/T7uGLw84w8Y42x+4eM=
|
|
||||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
|
||||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
|
||||||
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
|
|
||||||
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
|
|
||||||
github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 h1:+qGGcbkzsfDQNPPe9UDgpxAWQrhbbBXOYJFQDq/dtJw=
|
|
||||||
github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913/go.mod h1:4aEEwZQutDLsQv2Deui4iYQ6DWTxR14g6m8Wv88+Xqk=
|
|
||||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
|
||||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
|
||||||
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
|
||||||
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0 h1:Xs2Ncz0gNihqu9iosIZ5SkBbWo5T8JhhLJFMQL1qmLI=
|
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.51.0/go.mod h1:vy+2G/6NvVMpwGX/NyLqcC41fxepnuKHk16E6IZUcJc=
|
|
||||||
go.opentelemetry.io/otel v1.26.0 h1:LQwgL5s/1W7YiiRwxf03QGnWLb2HW4pLiAhaA5cZXBs=
|
|
||||||
go.opentelemetry.io/otel v1.26.0/go.mod h1:UmLkJHUAidDval2EICqBMbnAd0/m2vmpf/dAM+fvFs4=
|
|
||||||
go.opentelemetry.io/otel/metric v1.26.0 h1:7S39CLuY5Jgg9CrnA9HHiEjGMF/X2VHvoXGgSllRz30=
|
|
||||||
go.opentelemetry.io/otel/metric v1.26.0/go.mod h1:SY+rHOI4cEawI9a7N1A4nIg/nTQXe1ccCNWYOJUrpX4=
|
|
||||||
go.opentelemetry.io/otel/trace v1.26.0 h1:1ieeAUb4y0TE26jUFrCIXKpTuVK7uJGN9/Z/2LP5sQA=
|
|
||||||
go.opentelemetry.io/otel/trace v1.26.0/go.mod h1:4iDxvGDQuUkHve82hJJ8UqrwswHYsZuWCBllGV2U2y0=
|
|
||||||
go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4=
|
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
|
||||||
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
|
|
||||||
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
|
|
||||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
|
|
||||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
|
|
||||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
|
||||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
|
||||||
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
|
|
||||||
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
|
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
|
||||||
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
|
|
||||||
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|
||||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
|
||||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
|
||||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
|
||||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
|
||||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
154
backend/handler/handler_test.go
Normal file
154
backend/handler/handler_test.go
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/arnab-afk/monaco/model"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSubmitHandler(t *testing.T) {
|
||||||
|
h := NewHandler()
|
||||||
|
|
||||||
|
// Test valid Python submission
|
||||||
|
body := map[string]string{
|
||||||
|
"language": "python",
|
||||||
|
"code": "print('Hello, World!')",
|
||||||
|
}
|
||||||
|
bodyBytes, _ := json.Marshal(body)
|
||||||
|
req := httptest.NewRequest("POST", "/submit", bytes.NewReader(bodyBytes))
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
h.SubmitHandler(w, req)
|
||||||
|
|
||||||
|
assert.Equal(t, http.StatusAccepted, w.Code)
|
||||||
|
var response map[string]string
|
||||||
|
err := json.Unmarshal(w.Body.Bytes(), &response)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotEmpty(t, response["id"])
|
||||||
|
|
||||||
|
// Test invalid language
|
||||||
|
body["language"] = "invalid"
|
||||||
|
bodyBytes, _ = json.Marshal(body)
|
||||||
|
req = httptest.NewRequest("POST", "/submit", bytes.NewReader(bodyBytes))
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
w = httptest.NewRecorder()
|
||||||
|
|
||||||
|
h.SubmitHandler(w, req)
|
||||||
|
|
||||||
|
assert.Equal(t, http.StatusBadRequest, w.Code)
|
||||||
|
assert.Contains(t, w.Body.String(), "Unsupported language")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStatusHandler(t *testing.T) {
|
||||||
|
h := NewHandler()
|
||||||
|
|
||||||
|
// Create a test submission
|
||||||
|
submission := &model.CodeSubmission{
|
||||||
|
ID: "test-id",
|
||||||
|
Language: "python",
|
||||||
|
Code: "print('Hello')",
|
||||||
|
Status: "completed",
|
||||||
|
QueuedAt: time.Now().Add(-2 * time.Second),
|
||||||
|
StartedAt: time.Now().Add(-1 * time.Second),
|
||||||
|
CompletedAt: time.Now(),
|
||||||
|
Output: "Hello",
|
||||||
|
}
|
||||||
|
|
||||||
|
h.submissions["test-id"] = submission
|
||||||
|
|
||||||
|
// Test valid status request
|
||||||
|
req := httptest.NewRequest("GET", "/status?id=test-id", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
h.StatusHandler(w, req)
|
||||||
|
|
||||||
|
assert.Equal(t, http.StatusOK, w.Code)
|
||||||
|
var response map[string]interface{}
|
||||||
|
err := json.Unmarshal(w.Body.Bytes(), &response)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "test-id", response["id"])
|
||||||
|
assert.Equal(t, "completed", response["status"])
|
||||||
|
|
||||||
|
// Test missing ID
|
||||||
|
req = httptest.NewRequest("GET", "/status", nil)
|
||||||
|
w = httptest.NewRecorder()
|
||||||
|
|
||||||
|
h.StatusHandler(w, req)
|
||||||
|
|
||||||
|
assert.Equal(t, http.StatusBadRequest, w.Code)
|
||||||
|
assert.Contains(t, w.Body.String(), "ID is required")
|
||||||
|
|
||||||
|
// Test non-existent ID
|
||||||
|
req = httptest.NewRequest("GET", "/status?id=nonexistent", nil)
|
||||||
|
w = httptest.NewRecorder()
|
||||||
|
|
||||||
|
h.StatusHandler(w, req)
|
||||||
|
|
||||||
|
assert.Equal(t, http.StatusNotFound, w.Code)
|
||||||
|
assert.Contains(t, w.Body.String(), "Submission not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResultHandler(t *testing.T) {
|
||||||
|
h := NewHandler()
|
||||||
|
|
||||||
|
// Create a test submission
|
||||||
|
submission := &model.CodeSubmission{
|
||||||
|
ID: "test-id",
|
||||||
|
Language: "python",
|
||||||
|
Code: "print('Hello')",
|
||||||
|
Status: "completed",
|
||||||
|
QueuedAt: time.Now().Add(-2 * time.Second),
|
||||||
|
StartedAt: time.Now().Add(-1 * time.Second),
|
||||||
|
CompletedAt: time.Now(),
|
||||||
|
Output: "Hello",
|
||||||
|
}
|
||||||
|
|
||||||
|
h.submissions["test-id"] = submission
|
||||||
|
|
||||||
|
// Test valid result request
|
||||||
|
req := httptest.NewRequest("GET", "/result?id=test-id", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
h.ResultHandler(w, req)
|
||||||
|
|
||||||
|
assert.Equal(t, http.StatusOK, w.Code)
|
||||||
|
var response map[string]interface{}
|
||||||
|
err := json.Unmarshal(w.Body.Bytes(), &response)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "test-id", response["id"])
|
||||||
|
assert.Equal(t, "completed", response["status"])
|
||||||
|
assert.Equal(t, "Hello", response["output"])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQueueStatsHandler(t *testing.T) {
|
||||||
|
h := NewHandler()
|
||||||
|
|
||||||
|
// Add some test submissions
|
||||||
|
h.submissions["test-id1"] = &model.CodeSubmission{ID: "test-id1"}
|
||||||
|
h.submissions["test-id2"] = &model.CodeSubmission{ID: "test-id2"}
|
||||||
|
|
||||||
|
req := httptest.NewRequest("GET", "/queue-stats", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
h.QueueStatsHandler(w, req)
|
||||||
|
|
||||||
|
assert.Equal(t, http.StatusOK, w.Code)
|
||||||
|
var response map[string]interface{}
|
||||||
|
err := json.Unmarshal(w.Body.Bytes(), &response)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
stats, ok := response["queue_stats"].(map[string]interface{})
|
||||||
|
assert.True(t, ok)
|
||||||
|
assert.Contains(t, stats, "queue_length")
|
||||||
|
assert.Contains(t, stats, "max_workers")
|
||||||
|
assert.Contains(t, stats, "running_jobs")
|
||||||
|
|
||||||
|
assert.Equal(t, float64(2), response["submissions"])
|
||||||
|
}
|
||||||
71
backend/model/submission_test.go
Normal file
71
backend/model/submission_test.go
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCodeSubmissionSerialization(t *testing.T) {
|
||||||
|
// Create a submission
|
||||||
|
now := time.Now()
|
||||||
|
submission := CodeSubmission{
|
||||||
|
ID: "test-id",
|
||||||
|
Code: "print('Hello, World!')",
|
||||||
|
Language: "python",
|
||||||
|
Input: "test input",
|
||||||
|
Status: "completed",
|
||||||
|
QueuedAt: now.Add(-2 * time.Second),
|
||||||
|
StartedAt: now.Add(-1 * time.Second),
|
||||||
|
CompletedAt: now,
|
||||||
|
Output: "Hello, World!",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serialize to JSON
|
||||||
|
jsonBytes, err := json.Marshal(submission)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, jsonBytes)
|
||||||
|
|
||||||
|
// Deserialize back
|
||||||
|
var decoded CodeSubmission
|
||||||
|
err = json.Unmarshal(jsonBytes, &decoded)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// Verify fields match
|
||||||
|
assert.Equal(t, submission.ID, decoded.ID)
|
||||||
|
assert.Equal(t, submission.Code, decoded.Code)
|
||||||
|
assert.Equal(t, submission.Language, decoded.Language)
|
||||||
|
assert.Equal(t, submission.Input, decoded.Input)
|
||||||
|
assert.Equal(t, submission.Status, decoded.Status)
|
||||||
|
assert.Equal(t, submission.Output, decoded.Output)
|
||||||
|
|
||||||
|
// Time fields need special handling due to JSON serialization
|
||||||
|
assert.Equal(t, submission.QueuedAt.Format(time.RFC3339), decoded.QueuedAt.Format(time.RFC3339))
|
||||||
|
assert.Equal(t, submission.StartedAt.Format(time.RFC3339), decoded.StartedAt.Format(time.RFC3339))
|
||||||
|
assert.Equal(t, submission.CompletedAt.Format(time.RFC3339), decoded.CompletedAt.Format(time.RFC3339))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCodeSubmissionDefaults(t *testing.T) {
|
||||||
|
// Test that zero time values work correctly
|
||||||
|
submission := CodeSubmission{
|
||||||
|
ID: "test-id",
|
||||||
|
Code: "print('Hello')",
|
||||||
|
Language: "python",
|
||||||
|
Status: "pending",
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.True(t, submission.QueuedAt.IsZero())
|
||||||
|
assert.True(t, submission.StartedAt.IsZero())
|
||||||
|
assert.True(t, submission.CompletedAt.IsZero())
|
||||||
|
|
||||||
|
// Test JSON marshaling with zero time values
|
||||||
|
jsonBytes, err := json.Marshal(submission)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
// The zero time values should still be included in the JSON
|
||||||
|
jsonStr := string(jsonBytes)
|
||||||
|
assert.Contains(t, jsonStr, `"id":"test-id"`)
|
||||||
|
assert.Contains(t, jsonStr, `"status":"pending"`)
|
||||||
|
}
|
||||||
112
backend/queue/queue_test.go
Normal file
112
backend/queue/queue_test.go
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
package queue
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Mock job for testing
|
||||||
|
type MockJob struct {
|
||||||
|
executed bool
|
||||||
|
executeTime time.Duration
|
||||||
|
mu sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *MockJob) Execute() {
|
||||||
|
j.mu.Lock()
|
||||||
|
defer j.mu.Unlock()
|
||||||
|
|
||||||
|
time.Sleep(j.executeTime)
|
||||||
|
j.executed = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *MockJob) IsExecuted() bool {
|
||||||
|
j.mu.Lock()
|
||||||
|
defer j.mu.Unlock()
|
||||||
|
return j.executed
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestJobQueueCreation(t *testing.T) {
|
||||||
|
// Test with different numbers of workers
|
||||||
|
jq := NewJobQueue(5)
|
||||||
|
assert.NotNil(t, jq)
|
||||||
|
assert.Equal(t, 5, jq.maxWorkers)
|
||||||
|
|
||||||
|
stats := jq.QueueStats()
|
||||||
|
assert.Equal(t, 0, stats["queue_length"])
|
||||||
|
assert.Equal(t, 5, stats["max_workers"])
|
||||||
|
assert.Equal(t, 0, stats["running_jobs"])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestJobExecution(t *testing.T) {
|
||||||
|
jq := NewJobQueue(2)
|
||||||
|
|
||||||
|
// Create test jobs
|
||||||
|
job1 := &MockJob{executeTime: 10 * time.Millisecond}
|
||||||
|
job2 := &MockJob{executeTime: 10 * time.Millisecond}
|
||||||
|
|
||||||
|
// Enqueue jobs
|
||||||
|
jq.Enqueue(job1)
|
||||||
|
jq.Enqueue(job2)
|
||||||
|
|
||||||
|
// Wait for execution
|
||||||
|
time.Sleep(50 * time.Millisecond)
|
||||||
|
|
||||||
|
// Verify both jobs executed
|
||||||
|
assert.True(t, job1.IsExecuted())
|
||||||
|
assert.True(t, job2.IsExecuted())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConcurrentJobsExecution(t *testing.T) {
|
||||||
|
// Test that only maxWorkers jobs run concurrently
|
||||||
|
jq := NewJobQueue(2)
|
||||||
|
|
||||||
|
var mu sync.Mutex
|
||||||
|
runningCount := 0
|
||||||
|
maxObservedRunning := 0
|
||||||
|
|
||||||
|
wg := sync.WaitGroup{}
|
||||||
|
|
||||||
|
// Create long running jobs to test concurrency
|
||||||
|
for i := 0; i < 5; i++ {
|
||||||
|
wg.Add(1)
|
||||||
|
job := &MockJob{
|
||||||
|
executeTime: 100 * time.Millisecond,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrap the job to monitor concurrency
|
||||||
|
wrappedJob := JobFunc(func() {
|
||||||
|
mu.Lock()
|
||||||
|
runningCount++
|
||||||
|
if runningCount > maxObservedRunning {
|
||||||
|
maxObservedRunning = runningCount
|
||||||
|
}
|
||||||
|
mu.Unlock()
|
||||||
|
|
||||||
|
job.Execute()
|
||||||
|
|
||||||
|
mu.Lock()
|
||||||
|
runningCount--
|
||||||
|
mu.Unlock()
|
||||||
|
wg.Done()
|
||||||
|
})
|
||||||
|
|
||||||
|
jq.Enqueue(wrappedJob)
|
||||||
|
}
|
||||||
|
|
||||||
|
wg.Wait()
|
||||||
|
jq.Stop()
|
||||||
|
|
||||||
|
// Verify max concurrent jobs is respected
|
||||||
|
assert.LessOrEqual(t, maxObservedRunning, 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Define JobFunc type for easier job creation in tests
|
||||||
|
type JobFunc func()
|
||||||
|
|
||||||
|
func (f JobFunc) Execute() {
|
||||||
|
f()
|
||||||
|
}
|
||||||
115
backend/service/execution_test.go
Normal file
115
backend/service/execution_test.go
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/arnab-afk/monaco/model"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestExecutionServiceCreation tests that the service is created properly
|
||||||
|
func TestExecutionServiceCreation(t *testing.T) {
|
||||||
|
service := NewExecutionService()
|
||||||
|
assert.NotNil(t, service)
|
||||||
|
assert.NotNil(t, service.queue)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestExtractClassName tests the class name extraction for Java code
|
||||||
|
func TestExtractClassName(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
code string
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Public class",
|
||||||
|
code: "public class MyClass { public static void main(String[] args) {} }",
|
||||||
|
expected: "MyClass",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Regular class",
|
||||||
|
code: "class RegularClass { public static void main(String[] args) {} }",
|
||||||
|
expected: "RegularClass",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Multiple classes",
|
||||||
|
code: "class Class1 {} public class MainClass {} class Class2 {}",
|
||||||
|
expected: "MainClass",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "No class",
|
||||||
|
code: "// Just a comment",
|
||||||
|
expected: "Solution", // Default class name
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
result := extractClassName(tt.code)
|
||||||
|
assert.Equal(t, tt.expected, result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockDockerExec is a function that can be used to mock Docker exec commands
|
||||||
|
type MockDockerExec func(cmd string, args ...string) ([]byte, error)
|
||||||
|
|
||||||
|
// TestUpdateSubmissionResult tests the submission result update logic
|
||||||
|
func TestUpdateSubmissionResult(t *testing.T) {
|
||||||
|
service := NewExecutionService()
|
||||||
|
|
||||||
|
// Test successful execution
|
||||||
|
submission := &model.CodeSubmission{
|
||||||
|
ID: "test-id",
|
||||||
|
Status: "running",
|
||||||
|
StartedAt: time.Now().Add(-500 * time.Millisecond),
|
||||||
|
QueuedAt: time.Now().Add(-1 * time.Second),
|
||||||
|
}
|
||||||
|
|
||||||
|
output := []byte("Hello, World!")
|
||||||
|
service.updateSubmissionResult(submission, output, nil)
|
||||||
|
|
||||||
|
assert.Equal(t, "completed", submission.Status)
|
||||||
|
assert.Equal(t, "Hello, World!", submission.Output)
|
||||||
|
assert.False(t, submission.CompletedAt.IsZero())
|
||||||
|
|
||||||
|
// Test failed execution
|
||||||
|
submission = &model.CodeSubmission{
|
||||||
|
ID: "test-id-2",
|
||||||
|
Status: "running",
|
||||||
|
StartedAt: time.Now().Add(-500 * time.Millisecond),
|
||||||
|
QueuedAt: time.Now().Add(-1 * time.Second),
|
||||||
|
}
|
||||||
|
|
||||||
|
output = []byte("Compilation error")
|
||||||
|
err := os.ErrInvalid // Any error will do for testing
|
||||||
|
service.updateSubmissionResult(submission, output, err)
|
||||||
|
|
||||||
|
assert.Equal(t, "failed", submission.Status)
|
||||||
|
assert.Contains(t, submission.Output, "Compilation error")
|
||||||
|
assert.Contains(t, submission.Output, err.Error())
|
||||||
|
assert.False(t, submission.CompletedAt.IsZero())
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestCodeExecutionJob tests the job execution logic
|
||||||
|
func TestCodeExecutionJob(t *testing.T) {
|
||||||
|
service := NewExecutionService()
|
||||||
|
|
||||||
|
submission := &model.CodeSubmission{
|
||||||
|
ID: "test-id",
|
||||||
|
Language: "python",
|
||||||
|
Code: "print('test')",
|
||||||
|
Status: "queued",
|
||||||
|
QueuedAt: time.Now(),
|
||||||
|
}
|
||||||
|
|
||||||
|
job := NewCodeExecutionJob(service, submission)
|
||||||
|
assert.NotNil(t, job)
|
||||||
|
assert.Equal(t, submission, job.submission)
|
||||||
|
assert.Equal(t, service, job.service)
|
||||||
|
|
||||||
|
// We can't easily test the actual execution because it depends on Docker
|
||||||
|
// In a real test environment, you would mock the Docker calls
|
||||||
|
}
|
||||||
195
backend/tests/api_integration_test.go
Normal file
195
backend/tests/api_integration_test.go
Normal file
@@ -0,0 +1,195 @@
|
|||||||
|
package tests
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/arnab-afk/monaco/handler"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func setupTestServer() *httptest.Server {
|
||||||
|
h := handler.NewHandler()
|
||||||
|
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
mux.HandleFunc("/submit", h.SubmitHandler)
|
||||||
|
mux.HandleFunc("/status", h.StatusHandler)
|
||||||
|
mux.HandleFunc("/result", h.ResultHandler)
|
||||||
|
mux.HandleFunc("/queue-stats", h.QueueStatsHandler)
|
||||||
|
|
||||||
|
return httptest.NewServer(mux)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAPIIntegration(t *testing.T) {
|
||||||
|
server := setupTestServer()
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
// Test: Submit code, check status, and get results
|
||||||
|
// 1. Submit a Python job
|
||||||
|
submitURL := server.URL + "/submit"
|
||||||
|
body := map[string]string{
|
||||||
|
"language": "python",
|
||||||
|
"code": "print('Hello, Integration Test!')",
|
||||||
|
}
|
||||||
|
|
||||||
|
bodyBytes, _ := json.Marshal(body)
|
||||||
|
resp, err := http.Post(submitURL, "application/json", bytes.NewReader(bodyBytes))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, http.StatusAccepted, resp.StatusCode)
|
||||||
|
|
||||||
|
// Get the job ID
|
||||||
|
var submitResp map[string]string
|
||||||
|
json.NewDecoder(resp.Body).Decode(&submitResp)
|
||||||
|
resp.Body.Close()
|
||||||
|
|
||||||
|
jobID := submitResp["id"]
|
||||||
|
assert.NotEmpty(t, jobID)
|
||||||
|
|
||||||
|
// 2. Check status
|
||||||
|
statusURL := server.URL + "/status?id=" + jobID
|
||||||
|
|
||||||
|
// Wait for job to complete (try multiple times)
|
||||||
|
var statusResp map[string]interface{}
|
||||||
|
maxRetries := 10
|
||||||
|
|
||||||
|
for i := 0; i < maxRetries; i++ {
|
||||||
|
resp, err = http.Get(statusURL)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||||
|
|
||||||
|
json.NewDecoder(resp.Body).Decode(&statusResp)
|
||||||
|
resp.Body.Close()
|
||||||
|
|
||||||
|
// If job completed or failed, break
|
||||||
|
status, _ := statusResp["status"].(string)
|
||||||
|
if status == "completed" || status == "failed" {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait before next retry
|
||||||
|
time.Sleep(200 * time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Get results
|
||||||
|
resultURL := server.URL + "/result?id=" + jobID
|
||||||
|
resp, err = http.Get(resultURL)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||||
|
|
||||||
|
var resultResp map[string]interface{}
|
||||||
|
json.NewDecoder(resp.Body).Decode(&resultResp)
|
||||||
|
resp.Body.Close()
|
||||||
|
|
||||||
|
assert.Equal(t, jobID, resultResp["id"])
|
||||||
|
assert.Contains(t, resultResp["output"], "Hello, Integration Test!")
|
||||||
|
|
||||||
|
// 4. Check queue stats
|
||||||
|
statsURL := server.URL + "/queue-stats"
|
||||||
|
resp, err = http.Get(statsURL)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
||||||
|
|
||||||
|
var statsResp map[string]interface{}
|
||||||
|
json.NewDecoder(resp.Body).Decode(&statsResp)
|
||||||
|
resp.Body.Close()
|
||||||
|
|
||||||
|
assert.Contains(t, statsResp, "queue_stats")
|
||||||
|
assert.Contains(t, statsResp, "submissions")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMultipleLanguageSubmissions(t *testing.T) {
|
||||||
|
server := setupTestServer()
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
// Test submissions for different languages
|
||||||
|
languages := []string{"python", "java", "c", "cpp"}
|
||||||
|
codes := map[string]string{
|
||||||
|
"python": "print('Hello from Python')",
|
||||||
|
"java": "public class Solution { public static void main(String[] args) { System.out.println(\"Hello from Java\"); } }",
|
||||||
|
"c": "#include <stdio.h>\nint main() { printf(\"Hello from C\\n\"); return 0; }",
|
||||||
|
"cpp": "#include <iostream>\nint main() { std::cout << \"Hello from C++\" << std::endl; return 0; }",
|
||||||
|
}
|
||||||
|
|
||||||
|
submitURL := server.URL + "/submit"
|
||||||
|
|
||||||
|
for _, lang := range languages {
|
||||||
|
body := map[string]string{
|
||||||
|
"language": lang,
|
||||||
|
"code": codes[lang],
|
||||||
|
}
|
||||||
|
|
||||||
|
bodyBytes, _ := json.Marshal(body)
|
||||||
|
resp, err := http.Post(submitURL, "application/json", bytes.NewReader(bodyBytes))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, http.StatusAccepted, resp.StatusCode)
|
||||||
|
|
||||||
|
var submitResp map[string]string
|
||||||
|
json.NewDecoder(resp.Body).Decode(&submitResp)
|
||||||
|
resp.Body.Close()
|
||||||
|
|
||||||
|
jobID := submitResp["id"]
|
||||||
|
assert.NotEmpty(t, jobID)
|
||||||
|
|
||||||
|
// We don't wait for completion in this test
|
||||||
|
// This is just to verify submission acceptance for all languages
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInputHandling(t *testing.T) {
|
||||||
|
server := setupTestServer()
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
// Test code submission with input
|
||||||
|
submitURL := server.URL + "/submit"
|
||||||
|
body := map[string]string{
|
||||||
|
"language": "python",
|
||||||
|
"code": "name = input('Enter name: ')\nprint('Hello, ' + name + '!')",
|
||||||
|
"input": "Integration Test",
|
||||||
|
}
|
||||||
|
|
||||||
|
bodyBytes, _ := json.Marshal(body)
|
||||||
|
resp, err := http.Post(submitURL, "application/json", bytes.NewReader(bodyBytes))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, http.StatusAccepted, resp.StatusCode)
|
||||||
|
|
||||||
|
var submitResp map[string]string
|
||||||
|
json.NewDecoder(resp.Body).Decode(&submitResp)
|
||||||
|
resp.Body.Close()
|
||||||
|
|
||||||
|
jobID := submitResp["id"]
|
||||||
|
assert.NotEmpty(t, jobID)
|
||||||
|
|
||||||
|
// Wait for job to complete and check result
|
||||||
|
resultURL := server.URL + "/result?id=" + jobID
|
||||||
|
|
||||||
|
// Poll for results
|
||||||
|
var resultResp map[string]interface{}
|
||||||
|
maxRetries := 10
|
||||||
|
|
||||||
|
for i := 0; i < maxRetries; i++ {
|
||||||
|
time.Sleep(300 * time.Millisecond)
|
||||||
|
|
||||||
|
resp, err = http.Get(resultURL)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
resp.Body.Close()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
json.NewDecoder(resp.Body).Decode(&resultResp)
|
||||||
|
resp.Body.Close()
|
||||||
|
|
||||||
|
status, _ := resultResp["status"].(string)
|
||||||
|
if status == "completed" || status == "failed" {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify output contains the greeting with input
|
||||||
|
assert.Contains(t, resultResp["output"], "Hello, Integration Test!")
|
||||||
|
}
|
||||||
278
backend/tests/load_test.py
Normal file
278
backend/tests/load_test.py
Normal file
@@ -0,0 +1,278 @@
|
|||||||
|
import requests
|
||||||
|
import concurrent.futures
|
||||||
|
import time
|
||||||
|
import statistics
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
# Define the endpoint URLs
|
||||||
|
POST_URL = "http://localhost:8080/submit"
|
||||||
|
GET_URL_STATUS = "http://localhost:8080/status?id={}"
|
||||||
|
GET_URL_RESULT = "http://localhost:8080/result?id={}"
|
||||||
|
GET_URL_STATS = "http://localhost:8080/queue-stats"
|
||||||
|
|
||||||
|
# Test payloads for different languages
|
||||||
|
PAYLOADS = {
|
||||||
|
"python": {
|
||||||
|
"language": "python",
|
||||||
|
"code": "print('Hello, Load Test!')",
|
||||||
|
},
|
||||||
|
"java": {
|
||||||
|
"language": "java",
|
||||||
|
"code": "public class Solution { public static void main(String[] args) { System.out.println(\"Hello, Load Test!\"); } }",
|
||||||
|
},
|
||||||
|
"c": {
|
||||||
|
"language": "c",
|
||||||
|
"code": "#include <stdio.h>\nint main() { printf(\"Hello, Load Test!\\n\"); return 0; }",
|
||||||
|
},
|
||||||
|
"cpp": {
|
||||||
|
"language": "cpp",
|
||||||
|
"code": "#include <iostream>\nint main() { std::cout << \"Hello, Load Test!\" << std::endl; return 0; }",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def send_request(language, index):
|
||||||
|
"""Sends a POST request and returns (task_id, time_taken)."""
|
||||||
|
payload = PAYLOADS[language]
|
||||||
|
start_time = time.time()
|
||||||
|
try:
|
||||||
|
response = requests.post(POST_URL, json=payload, timeout=10)
|
||||||
|
end_time = time.time()
|
||||||
|
if response.status_code == 202:
|
||||||
|
return response.json().get("id"), end_time - start_time
|
||||||
|
else:
|
||||||
|
print(f"Request {index} failed with status {response.status_code}")
|
||||||
|
return None, end_time - start_time
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
end_time = time.time()
|
||||||
|
print(f"Request {index} error: {e}")
|
||||||
|
return None, end_time - start_time
|
||||||
|
|
||||||
|
def wait_for_result(task_id, index):
|
||||||
|
"""Waits for a result and returns (result, time_taken)."""
|
||||||
|
if not task_id:
|
||||||
|
return None, 0
|
||||||
|
|
||||||
|
start_time = time.time()
|
||||||
|
max_retries = 30
|
||||||
|
retry_interval = 0.5 # seconds
|
||||||
|
|
||||||
|
for _ in range(max_retries):
|
||||||
|
try:
|
||||||
|
response = requests.get(GET_URL_RESULT.format(task_id), timeout=10)
|
||||||
|
if response.status_code == 200:
|
||||||
|
result = response.json()
|
||||||
|
if result.get("status") in ["completed", "failed"]:
|
||||||
|
end_time = time.time()
|
||||||
|
return result, end_time - start_time
|
||||||
|
time.sleep(retry_interval)
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
print(f"Error checking result for task {index}: {e}")
|
||||||
|
|
||||||
|
end_time = time.time()
|
||||||
|
print(f"Timed out waiting for result of task {index}")
|
||||||
|
return None, end_time - start_time
|
||||||
|
|
||||||
|
def run_test(concurrency, requests_per_language):
|
||||||
|
"""Runs a load test with the specified parameters."""
|
||||||
|
languages = list(PAYLOADS.keys())
|
||||||
|
all_results = {lang: [] for lang in languages}
|
||||||
|
submit_times = {lang: [] for lang in languages}
|
||||||
|
wait_times = {lang: [] for lang in languages}
|
||||||
|
success_rates = {lang: 0 for lang in languages}
|
||||||
|
|
||||||
|
# Keep track of all submissions for each language
|
||||||
|
total_per_language = {lang: 0 for lang in languages}
|
||||||
|
successful_per_language = {lang: 0 for lang in languages}
|
||||||
|
|
||||||
|
start_time = time.time()
|
||||||
|
|
||||||
|
# Create a list of tasks
|
||||||
|
tasks = []
|
||||||
|
for lang in languages:
|
||||||
|
for i in range(requests_per_language):
|
||||||
|
tasks.append((lang, i))
|
||||||
|
|
||||||
|
print(f"Running load test with {concurrency} concurrent connections")
|
||||||
|
print(f"Sending {requests_per_language} requests per language ({len(languages)} languages)")
|
||||||
|
|
||||||
|
# Submit all tasks
|
||||||
|
task_ids = {}
|
||||||
|
with concurrent.futures.ThreadPoolExecutor(max_workers=concurrency) as executor:
|
||||||
|
future_to_task = {executor.submit(send_request, lang, i): (lang, i) for lang, i in tasks}
|
||||||
|
for future in concurrent.futures.as_completed(future_to_task):
|
||||||
|
lang, i = future_to_task[future]
|
||||||
|
total_per_language[lang] += 1
|
||||||
|
try:
|
||||||
|
task_id, submit_time = future.result()
|
||||||
|
if task_id:
|
||||||
|
task_ids[(lang, i)] = task_id
|
||||||
|
submit_times[lang].append(submit_time)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error submitting {lang} task {i}: {e}")
|
||||||
|
|
||||||
|
print(f"Submitted {len(task_ids)} tasks successfully")
|
||||||
|
|
||||||
|
# Wait for all results
|
||||||
|
with concurrent.futures.ThreadPoolExecutor(max_workers=concurrency) as executor:
|
||||||
|
future_to_task = {executor.submit(wait_for_result, task_ids.get((lang, i)), i): (lang, i)
|
||||||
|
for lang, i in tasks if (lang, i) in task_ids}
|
||||||
|
for future in concurrent.futures.as_completed(future_to_task):
|
||||||
|
lang, i = future_to_task[future]
|
||||||
|
try:
|
||||||
|
result, wait_time = future.result()
|
||||||
|
if result and result.get("status") == "completed":
|
||||||
|
successful_per_language[lang] += 1
|
||||||
|
all_results[lang].append(result)
|
||||||
|
wait_times[lang].append(wait_time)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error waiting for {lang} task {i}: {e}")
|
||||||
|
|
||||||
|
end_time = time.time()
|
||||||
|
total_time = end_time - start_time
|
||||||
|
|
||||||
|
# Calculate success rates
|
||||||
|
for lang in languages:
|
||||||
|
if total_per_language[lang] > 0:
|
||||||
|
success_rates[lang] = (successful_per_language[lang] / total_per_language[lang]) * 100
|
||||||
|
else:
|
||||||
|
success_rates[lang] = 0
|
||||||
|
|
||||||
|
# Calculate statistics
|
||||||
|
stats = {
|
||||||
|
"total_time": total_time,
|
||||||
|
"requests_per_second": len(task_ids) / total_time if total_time > 0 else 0,
|
||||||
|
"success_rate": sum(success_rates.values()) / len(success_rates) if success_rates else 0,
|
||||||
|
"submit_times": {
|
||||||
|
lang: {
|
||||||
|
"avg": statistics.mean(times) if times else 0,
|
||||||
|
"min": min(times) if times else 0,
|
||||||
|
"max": max(times) if times else 0,
|
||||||
|
"p95": np.percentile(times, 95) if times else 0
|
||||||
|
} for lang, times in submit_times.items()
|
||||||
|
},
|
||||||
|
"wait_times": {
|
||||||
|
lang: {
|
||||||
|
"avg": statistics.mean(times) if times else 0,
|
||||||
|
"min": min(times) if times else 0,
|
||||||
|
"max": max(times) if times else 0,
|
||||||
|
"p95": np.percentile(times, 95) if times else 0
|
||||||
|
} for lang, times in wait_times.items()
|
||||||
|
},
|
||||||
|
"success_rates": success_rates
|
||||||
|
}
|
||||||
|
|
||||||
|
return stats, all_results
|
||||||
|
|
||||||
|
def print_stats(stats):
|
||||||
|
"""Prints test statistics."""
|
||||||
|
print("\n=== Load Test Results ===")
|
||||||
|
print(f"Total time: {stats['total_time']:.2f}s")
|
||||||
|
print(f"Requests per second: {stats['requests_per_second']:.2f}")
|
||||||
|
print(f"Overall success rate: {stats['success_rate']:.2f}%")
|
||||||
|
|
||||||
|
print("\n== Submit Times (seconds) ==")
|
||||||
|
for lang, times in stats["submit_times"].items():
|
||||||
|
print(f"{lang:<6}: avg={times['avg']:.4f}, min={times['min']:.4f}, max={times['max']:.4f}, p95={times['p95']:.4f}")
|
||||||
|
|
||||||
|
print("\n== Wait Times (seconds) ==")
|
||||||
|
for lang, times in stats["wait_times"].items():
|
||||||
|
print(f"{lang:<6}: avg={times['avg']:.4f}, min={times['min']:.4f}, max={times['max']:.4f}, p95={times['p95']:.4f}")
|
||||||
|
|
||||||
|
print("\n== Success Rates ==")
|
||||||
|
for lang, rate in stats["success_rates"].items():
|
||||||
|
print(f"{lang:<6}: {rate:.2f}%")
|
||||||
|
|
||||||
|
def plot_results(stats):
|
||||||
|
"""Creates visualizations of test results."""
|
||||||
|
languages = list(stats["submit_times"].keys())
|
||||||
|
|
||||||
|
# Plot submit times
|
||||||
|
plt.figure(figsize=(12, 10))
|
||||||
|
|
||||||
|
plt.subplot(2, 2, 1)
|
||||||
|
plt.title("Average Submit Time by Language")
|
||||||
|
avg_times = [stats["submit_times"][lang]["avg"] for lang in languages]
|
||||||
|
plt.bar(languages, avg_times)
|
||||||
|
plt.ylabel("Time (seconds)")
|
||||||
|
|
||||||
|
plt.subplot(2, 2, 2)
|
||||||
|
plt.title("Average Wait Time by Language")
|
||||||
|
avg_wait_times = [stats["wait_times"][lang]["avg"] for lang in languages]
|
||||||
|
plt.bar(languages, avg_wait_times)
|
||||||
|
plt.ylabel("Time (seconds)")
|
||||||
|
|
||||||
|
plt.subplot(2, 2, 3)
|
||||||
|
plt.title("Success Rate by Language")
|
||||||
|
success_rates = [stats["success_rates"][lang] for lang in languages]
|
||||||
|
plt.bar(languages, success_rates)
|
||||||
|
plt.ylabel("Success Rate (%)")
|
||||||
|
plt.ylim(0, 100)
|
||||||
|
|
||||||
|
plt.subplot(2, 2, 4)
|
||||||
|
plt.title("95th Percentile Wait Time by Language")
|
||||||
|
p95_times = [stats["wait_times"][lang]["p95"] for lang in languages]
|
||||||
|
plt.bar(languages, p95_times)
|
||||||
|
plt.ylabel("Time (seconds)")
|
||||||
|
|
||||||
|
plt.tight_layout()
|
||||||
|
plt.savefig("load_test_results.png")
|
||||||
|
print("Results saved to load_test_results.png")
|
||||||
|
|
||||||
|
def main():
|
||||||
|
# Run tests with different concurrency levels
|
||||||
|
concurrency_levels = [10, 20, 30]
|
||||||
|
requests_per_language = 10
|
||||||
|
|
||||||
|
all_stats = []
|
||||||
|
|
||||||
|
for concurrency in concurrency_levels:
|
||||||
|
stats, results = run_test(concurrency, requests_per_language)
|
||||||
|
all_stats.append((concurrency, stats))
|
||||||
|
print_stats(stats)
|
||||||
|
|
||||||
|
# Create comparison visualization
|
||||||
|
plt.figure(figsize=(12, 8))
|
||||||
|
|
||||||
|
plt.subplot(2, 2, 1)
|
||||||
|
plt.title("Requests per Second vs Concurrency")
|
||||||
|
plt.plot([s[0] for s in all_stats], [s[1]["requests_per_second"] for s in all_stats], "o-")
|
||||||
|
plt.xlabel("Concurrency Level")
|
||||||
|
plt.ylabel("Requests per Second")
|
||||||
|
|
||||||
|
plt.subplot(2, 2, 2)
|
||||||
|
plt.title("Success Rate vs Concurrency")
|
||||||
|
plt.plot([s[0] for s in all_stats], [s[1]["success_rate"] for s in all_stats], "o-")
|
||||||
|
plt.xlabel("Concurrency Level")
|
||||||
|
plt.ylabel("Success Rate (%)")
|
||||||
|
plt.ylim(0, 100)
|
||||||
|
|
||||||
|
plt.subplot(2, 2, 3)
|
||||||
|
plt.title("Average Submit Time vs Concurrency")
|
||||||
|
for lang in PAYLOADS.keys():
|
||||||
|
plt.plot([s[0] for s in all_stats],
|
||||||
|
[s[1]["submit_times"][lang]["avg"] for s in all_stats],
|
||||||
|
"o-", label=lang)
|
||||||
|
plt.xlabel("Concurrency Level")
|
||||||
|
plt.ylabel("Average Submit Time (s)")
|
||||||
|
plt.legend()
|
||||||
|
|
||||||
|
plt.subplot(2, 2, 4)
|
||||||
|
plt.title("Average Wait Time vs Concurrency")
|
||||||
|
for lang in PAYLOADS.keys():
|
||||||
|
plt.plot([s[0] for s in all_stats],
|
||||||
|
[s[1]["wait_times"][lang]["avg"] for s in all_stats],
|
||||||
|
"o-", label=lang)
|
||||||
|
plt.xlabel("Concurrency Level")
|
||||||
|
plt.ylabel("Average Wait Time (s)")
|
||||||
|
plt.legend()
|
||||||
|
|
||||||
|
plt.tight_layout()
|
||||||
|
plt.savefig("concurrency_comparison.png")
|
||||||
|
print("Concurrency comparison saved to concurrency_comparison.png")
|
||||||
|
|
||||||
|
# Plot detailed results for the highest concurrency test
|
||||||
|
plot_results(all_stats[-1][1])
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Binary file not shown.
Reference in New Issue
Block a user