Code Execution Service
Relevant source files
The following files were used as context for generating this wiki page:The Code Execution Service is a core component of the PeP5 platform responsible for running student-submitted code in a secure, sandboxed environment. It acts as a bridge to an external code execution engine, the Piston API, to compile and run code for various programming languages. Its primary functions are to execute arbitrary code with standard input and to run a suite of predefined test cases against the code to check for correctness.
This service is integral to the automated grading of programming exams. It provides immediate feedback on code execution, including output, errors, and performance metrics. It is consumed by other services, such as the ProgrammingExamService, to evaluate student attempts against the criteria defined in a ProgrammingTask.
Architecture and Core Components
The service is defined by the ICodeExecutionService interface and implemented in the CodeExecutionService class. It relies on an external, third-party service (Piston API) for the actual code execution, abstracting away the complexities of managing secure execution environments.
Sources: Services/CodeExecutionService.cs:10-23, Services/CodeExecutionService.cs:140-149
Key Components
| Component | Description | Source File |
|---|---|---|
ICodeExecutionService | The public interface defining the service's contract. | Services/CodeExecutionService.cs:10-23 |
CodeExecutionService | The concrete implementation that communicates with the Piston API. | Services/CodeExecutionService.cs:140 |
HttpClient | Used to make POST requests to the Piston API endpoint. | Services/CodeExecutionService.cs:146-149 |
| Piston API | An external service at https://emkc.org/api/v2/piston/execute that receives code and executes it. | Services/CodeExecutionService.cs:143 |
CodeExecutionResult | A data model representing the outcome of a single code execution run. | Services/CodeExecutionService.cs:25-34 |
TestCaseExecutionResult | A data model representing the outcome of running the code against a specific TestCase. | Services/CodeExecutionService.cs:36-46 |
Data Flow Diagram
The following diagram illustrates the general flow of a code execution request.
This diagram shows how the CodeExecutionService abstracts the external Piston API, taking a request and returning a standardized result.
Sources: Services/CodeExecutionService.cs:151-247
Class Diagram
The main classes and their relationships are shown below.
Core Functionality
The service's functionality is exposed through three main methods, each building upon the last.
ExecuteCodeAsync
This is the fundamental method that sends code to the Piston API. It orchestrates the entire process from request preparation to response parsing.
The sequence of operations is as follows:
This sequence shows the interaction between the service and the external Piston API to execute code.
Sources: Services/CodeExecutionService.cs:151-247
Piston API Request/Response Models
The service uses specific models to communicate with the Piston API.
PistonExecuteRequest: Contains the language, version, files, and other execution parameters.PistonExecuteResponse: Contains the result, which can include separaterunandcompilesections.
Sources: Services/CodeExecutionService.cs:49-138
Test Case Execution
The service provides higher-level methods to run code against test cases defined in ProgrammingExamModels.
Sources: Models/ProgrammingExamModels.cs:122-150
RunTestCaseAsync
This method wraps ExecuteCodeAsync to facilitate running a single TestCase.
- It calls
ExecuteCodeAsync, passing thetestCase.Inputasstdin. - It normalizes both the
result.OutputandtestCase.ExpectedOutputto ensure consistent comparison (e.g., handling different line endings). - It compares the normalized strings to determine if the test
Passed. - It populates and returns a
TestCaseExecutionResultwith detailed information.
Sources: Services/CodeExecutionService.cs:348-372
Output Normalization
To prevent failures due to trivial differences like whitespace or line endings, the NormalizeOutput method is used before comparison.
// Services/CodeExecutionService.cs:574-583
private static string NormalizeOutput(string output)
{
if (string.IsNullOrEmpty(output)) return string.Empty;
// Normalize line endings and trim
return output
.Replace("\r\n", "\n")
.Replace("\r", "\n")
.Trim();
}
Sources: Services/CodeExecutionService.cs:574-583
RunAllTestCasesAsync
This method simply iterates over a list of TestCase objects, calling RunTestCaseAsync for each and aggregating the results into a list. This is used to get a comprehensive evaluation of a student's submission against all defined tests.
Sources: Services/CodeExecutionService.cs:374-387
Language and File Handling
A key feature of the service is its ability to handle multiple programming languages and simulate a file system for code that needs to read data files.
Language Configuration
The GetPistonLanguageConfig method maps the application's internal ProgrammingLanguage enum to the specific language and version strings required by the Piston API.
ProgrammingLanguage Enum | Piston Language | Piston Version | Entry Point |
|---|---|---|---|
Java | java | 15.0.2 | Main.java |
Python | python | 3.10.0 | main.py |
CSharp | csharp | 6.12.0 | Program.cs |
CPlusPlus | c++ | 10.2.0 | main.cpp |
JavaScript | javascript | 18.15.0 | main.js |
TypeScript | typescript | 5.0.3 | main.ts |
C | c | 10.2.0 | main.c |
SQL | sqlite3 | 3.36.0 | script.sql |
Sources: Services/CodeExecutionService.cs:389-403, Models/ProgrammingExamModels.cs:81-98
Data File Injection
Since the Piston API provides a stateless execution environment without a persistent file system, the CodeExecutionService uses a clever workaround to support tasks involving file I/O. The InjectDataFiles method dynamically generates a helper file that embeds the content of data files (e.g., .csv, .txt) as hardcoded strings. This helper file also includes a function (e.g., read_data_file) that student code can call to "read" the data.
This approach allows students to write code that performs file I/O operations, which are then transparently handled by the injected helper code during execution.
Sources: Services/CodeExecutionService.cs:419-572
Below is an example of the injected helper file for Python:
// Services/CodeExecutionService.cs:429-440
# Auto-generated data file contents
DATA_FILES = {
"input.csv": """id,name,value
1,Item1,100
2,Item2,200""",
}
def read_data_file(filename):
return DATA_FILES.get(filename, '')
This snippet shows how a CSV file's content is embedded into a Python dictionary, accessible via a helper function.
Sources: Services/CodeExecutionService.cs:429-440
Conclusion
The CodeExecutionService is a vital backend component that enables automated execution and testing of student code for programming exams. By leveraging the external Piston API, it provides a secure and multi-language sandbox environment. Its sophisticated handling of data files through code injection allows for a wider range of programming tasks, including those involving file I/O, without requiring a stateful execution environment. This service is a cornerstone of the platform's ability to provide instant, objective feedback on programming assignments.