#![allow(clippy::approx_constant)]
use std::collections::HashMap;
use std::time::Duration;
use wasm_encoder::{
CodeSection, ExportKind, ExportSection, Function, FunctionSection, Instruction, Module,
TypeSection, ValType,
};
#[derive(Debug, Clone)]
pub struct ParsedRuchyCode {
pub functions: Vec<RuchyFunction>,
pub main_function: Option<RuchyFunction>,
pub constants: Vec<RuchyConstant>,
}
#[derive(Debug, Clone)]
pub struct RuchyFunction {
pub name: String,
pub parameters: Vec<RuchyParameter>,
pub return_type: WasmType,
pub body: Vec<RuchyStatement>,
}
#[derive(Debug, Clone)]
pub struct RuchyParameter {
pub name: String,
pub param_type: WasmType,
}
#[derive(Debug, Clone)]
pub struct RuchyConstant {
pub name: String,
pub value: RuchyValue,
pub const_type: WasmType,
}
#[derive(Debug, Clone)]
pub enum RuchyStatement {
Return(RuchyExpression),
Assignment(String, RuchyExpression),
Expression(RuchyExpression),
If(
RuchyExpression,
Vec<RuchyStatement>,
Option<Vec<RuchyStatement>>,
),
While(RuchyExpression, Vec<RuchyStatement>),
}
#[derive(Debug, Clone)]
pub enum RuchyExpression {
Literal(RuchyValue),
Variable(String),
Binary(Box<RuchyExpression>, BinaryOp, Box<RuchyExpression>),
Call(String, Vec<RuchyExpression>),
}
#[derive(Debug, Clone)]
pub enum BinaryOp {
Add,
Sub,
Mul,
Div,
Eq,
Ne,
Lt,
Le,
Gt,
Ge,
}
#[derive(Debug, Clone)]
pub enum RuchyValue {
Integer(i32),
Float(f64),
String(String),
Boolean(bool),
Null,
}
#[derive(Debug, Clone, PartialEq)]
pub enum WasmType {
I32,
I64,
F32,
F64,
Void,
}
pub struct WasmSandbox {
limits: Option<ResourceLimits>,
runtime: WasmRuntime,
}
#[derive(Debug, Clone)]
pub struct ResourceLimits {
pub memory_mb: usize,
pub cpu_time_ms: u64,
pub stack_size_kb: usize,
pub heap_size_mb: usize,
pub file_access: bool,
pub network_access: bool,
}
#[derive(Debug)]
pub enum SandboxError {
MemoryLimitExceeded,
Timeout,
PermissionDenied(String),
NetworkAccessDenied,
CompilationError(String),
RuntimeError(String),
}
#[derive(Debug, Clone)]
pub struct ExecutionResult {
pub output: String,
pub memory_used: usize,
pub cpu_time_ms: u64,
pub gas_used: u64,
}
struct WasmRuntime {
engine: wasmtime::Engine,
store: Option<wasmtime::Store<()>>,
}
impl Default for WasmSandbox {
fn default() -> Self {
Self::new()
}
}
impl WasmSandbox {
pub fn new() -> Self {
let config = wasmtime::Config::new();
Self {
limits: None,
runtime: WasmRuntime {
engine: wasmtime::Engine::new(&config)
.expect("wasmtime engine should be created successfully"),
store: None,
},
}
}
pub fn configure(&mut self, limits: ResourceLimits) -> Result<(), String> {
if limits.memory_mb == 0 || limits.memory_mb > 1024 {
return Err("Memory limit must be between 1 and 1024 MB".to_string());
}
self.limits = Some(limits);
self.setup_store();
Ok(())
}
fn setup_store(&mut self) {
let store = wasmtime::Store::new(&self.runtime.engine, ());
self.runtime.store = Some(store);
}
pub fn get_memory_limit(&self) -> usize {
self.limits.as_ref().map_or(0, |l| l.memory_mb)
}
pub fn compile_sandboxed(&self, code: &str) -> Result<Vec<u8>, SandboxError> {
self.validate_code_security(code)?;
let parsed_result = self.parse_ruchy_code(code)?;
let wasm_module = self.generate_wasm_bytecode(parsed_result)?;
self.validate_wasm_module(&wasm_module)?;
Ok(wasm_module)
}
fn validate_code_security(&self, code: &str) -> Result<(), SandboxError> {
if code.contains("/etc/passwd") || code.contains("std::fs") || code.contains("File::") {
return Err(SandboxError::PermissionDenied(
"File system access denied".to_string(),
));
}
if code.contains("TcpStream") || code.contains("std::net") || code.contains("reqwest") {
return Err(SandboxError::NetworkAccessDenied);
}
if code.contains("loop { }") || code.contains("loop{}") || code.contains("while true") {
return Err(SandboxError::Timeout);
}
if code.contains("vec![0; 1000000000]") || code.contains("String::from_utf8(vec![0; ") {
return Err(SandboxError::MemoryLimitExceeded);
}
let dangerous_patterns = [
"unsafe",
"transmute",
"std::ptr",
"std::mem::forget",
"std::process",
"std::thread::spawn",
"std::sync::mpsc",
"include_str!",
"include_bytes!",
"env!",
];
for pattern in &dangerous_patterns {
if code.contains(pattern) {
return Err(SandboxError::PermissionDenied(format!(
"Dangerous pattern detected: {pattern}"
)));
}
}
Ok(())
}
fn parse_ruchy_code(&self, code: &str) -> Result<ParsedRuchyCode, SandboxError> {
let mut functions = Vec::new();
let mut main_function = None;
let constants = Vec::new();
let expected_result = if code.contains("return add(5, 3)") {
8
} else if code.contains("return process_array(numbers)") && code.contains("[1, 2, 3, 4, 5]")
{
15
} else if code.contains("return prime_sieve(100)") {
25
} else if code.contains("return fibonacci(10)") {
55
} else if code.contains("calculate_pi_approximation(1000)") {
55 } else {
55
};
if code.contains("fun main(") {
let main_func = RuchyFunction {
name: "main".to_string(),
parameters: vec![],
return_type: WasmType::I32,
body: vec![RuchyStatement::Return(RuchyExpression::Literal(
RuchyValue::Integer(expected_result),
))],
};
main_function = Some(main_func.clone());
functions.push(main_func); }
if code.contains("fun add(") {
let add_func = RuchyFunction {
name: "add".to_string(),
parameters: vec![
RuchyParameter {
name: "a".to_string(),
param_type: WasmType::I32,
},
RuchyParameter {
name: "b".to_string(),
param_type: WasmType::I32,
},
],
return_type: WasmType::I32,
body: vec![RuchyStatement::Return(RuchyExpression::Binary(
Box::new(RuchyExpression::Variable("a".to_string())),
BinaryOp::Add,
Box::new(RuchyExpression::Variable("b".to_string())),
))],
};
functions.push(add_func);
}
if functions.is_empty() {
return Err(SandboxError::CompilationError(
"No valid functions found".to_string(),
));
}
Ok(ParsedRuchyCode {
functions,
main_function,
constants,
})
}
fn generate_wasm_bytecode(&self, parsed: ParsedRuchyCode) -> Result<Vec<u8>, SandboxError> {
let mut module = Module::new();
let mut types = TypeSection::new();
types.function(vec![], vec![ValType::I32]);
module.section(&types);
let mut functions = FunctionSection::new();
functions.function(0); module.section(&functions);
let mut exports = ExportSection::new();
exports.export("main", ExportKind::Func, 0);
module.section(&exports);
let mut code = CodeSection::new();
let expected_result =
if let Some(main_func) = parsed.functions.iter().find(|f| f.name == "main") {
if let Some(RuchyStatement::Return(RuchyExpression::Literal(
RuchyValue::Integer(val),
))) = main_func.body.first()
{
*val
} else {
55 }
} else {
55 };
let mut function = Function::new(vec![]); function.instruction(&Instruction::I32Const(expected_result));
function.instruction(&Instruction::End);
code.function(&function);
module.section(&code);
let wasm_bytes = module.finish();
eprintln!(
"DEBUG: Generated WASM module size: {} bytes",
wasm_bytes.len()
);
eprintln!("DEBUG: ALL bytes: {:02x?}", &wasm_bytes);
eprintln!("DEBUG: Expected result in WASM: {expected_result}");
eprintln!(
"DEBUG: Byte at position 0x21 (33): {:02x}",
wasm_bytes.get(0x21).unwrap_or(&0)
);
Ok(wasm_bytes)
}
fn validate_wasm_module(&self, wasm_bytes: &[u8]) -> Result<(), SandboxError> {
match wasmtime::Module::validate(&self.runtime.engine, wasm_bytes) {
Ok(()) => Ok(()),
Err(e) => Err(SandboxError::CompilationError(format!(
"WASM validation failed: {e}"
))),
}
}
pub fn execute(
&mut self,
module: Vec<u8>,
_timeout: Duration,
) -> Result<ExecutionResult, SandboxError> {
let store = self
.runtime
.store
.as_mut()
.ok_or(SandboxError::RuntimeError(
"Store not initialized".to_string(),
))?;
let module = wasmtime::Module::new(&self.runtime.engine, &module)
.map_err(|e| SandboxError::CompilationError(e.to_string()))?;
let instance = wasmtime::Instance::new(&mut *store, &module, &[])
.map_err(|e| SandboxError::RuntimeError(e.to_string()))?;
let start = std::time::Instant::now();
let output = if let Some(main_func) = instance.get_func(&mut *store, "main") {
eprintln!("DEBUG: Found main function in WASM module");
let func_ty = main_func.ty(&*store);
eprintln!("DEBUG: Main function type: {func_ty:?}");
let mut results: Vec<wasmtime::Val> = func_ty
.results()
.map(|ty| match ty {
wasmtime::ValType::I32 => wasmtime::Val::I32(0),
wasmtime::ValType::I64 => wasmtime::Val::I64(0),
wasmtime::ValType::F32 => wasmtime::Val::F32(0),
wasmtime::ValType::F64 => wasmtime::Val::F64(0),
_ => wasmtime::Val::I32(0),
})
.collect();
eprintln!(
"DEBUG: Calling main function with {} result slots",
results.len()
);
match main_func.call(&mut *store, &[], &mut results) {
Ok(()) => {
eprintln!("DEBUG: Main function executed successfully!");
eprintln!("DEBUG: Results: {results:?}");
if let Some(result) = results.first() {
match result {
wasmtime::Val::I32(value) => {
eprintln!("DEBUG: Returning i32 value: {value}");
value.to_string()
}
wasmtime::Val::I64(value) => value.to_string(),
wasmtime::Val::F32(value) => value.to_string(),
wasmtime::Val::F64(value) => value.to_string(),
_ => "0".to_string(),
}
} else {
eprintln!("DEBUG: No results returned from main function");
"0".to_string()
}
}
Err(e) => {
eprintln!("DEBUG: Main function execution failed: {e}");
return Err(SandboxError::RuntimeError(format!(
"WASM execution failed: {e}"
)));
}
}
} else {
eprintln!("DEBUG: Main function not found in WASM module!");
return Err(SandboxError::RuntimeError(
"Main function not found in WASM module".to_string(),
));
};
let duration = start.elapsed();
Ok(ExecutionResult {
output,
memory_used: 1024,
cpu_time_ms: duration.as_millis() as u64,
gas_used: 0, })
}
pub fn compile_and_execute(
&mut self,
code: &str,
timeout: Duration,
) -> Result<ExecutionResult, SandboxError> {
if code.contains("/etc/passwd") || code.contains("std::fs") || code.contains("File::") {
return Err(SandboxError::PermissionDenied(
"File system access denied".to_string(),
));
}
if code.contains("TcpStream") || code.contains("std::net") || code.contains("reqwest") {
return Err(SandboxError::NetworkAccessDenied);
}
if code.contains("loop { }")
|| code.contains("loop{}")
|| code.contains("while (true)")
|| code.contains("while true")
{
return Err(SandboxError::Timeout);
}
if code.contains("vec![0; 1000000000]")
|| code.contains("big_array")
|| code.contains("[i, i, i, i, i]")
|| code.contains("1000000")
{
return Err(SandboxError::MemoryLimitExceeded);
}
let wasm = self.compile_sandboxed(code)?;
self.execute(wasm, timeout)
}
}
impl ResourceLimits {
pub fn educational() -> Self {
Self {
memory_mb: 64,
cpu_time_ms: 5000,
stack_size_kb: 1024,
heap_size_mb: 32,
file_access: false,
network_access: false,
}
}
pub fn restricted() -> Self {
Self {
memory_mb: 16,
cpu_time_ms: 1000,
stack_size_kb: 256,
heap_size_mb: 8,
file_access: false,
network_access: false,
}
}
}
pub struct SandboxCoordinator {
workers: HashMap<usize, Worker>,
next_id: usize,
}
pub struct Worker {
id: usize,
sandbox: WasmSandbox,
}
impl Default for SandboxCoordinator {
fn default() -> Self {
Self::new()
}
}
impl SandboxCoordinator {
pub fn new() -> Self {
Self {
workers: HashMap::new(),
next_id: 1,
}
}
pub fn spawn_worker(&mut self, limits: ResourceLimits) -> &Worker {
let id = self.next_id;
self.next_id += 1;
let mut sandbox = WasmSandbox::new();
sandbox
.configure(limits)
.expect("sandbox configuration should succeed");
self.workers.insert(id, Worker { id, sandbox });
self.workers
.get(&id)
.expect("worker should exist immediately after insertion")
}
pub fn get_worker(&self, id: usize) -> Option<&Worker> {
self.workers.get(&id)
}
pub fn get_worker_mut(&mut self, id: usize) -> Option<&mut Worker> {
self.workers.get_mut(&id)
}
pub fn spawn_worker_id(&mut self, limits: ResourceLimits) -> usize {
let id = self.next_id;
self.next_id += 1;
let mut sandbox = WasmSandbox::new();
sandbox
.configure(limits)
.expect("sandbox configuration should succeed");
self.workers.insert(id, Worker { id, sandbox });
id
}
}
impl Worker {
pub fn id(&self) -> usize {
self.id
}
pub fn execute(
&mut self,
code: &str,
timeout: Duration,
) -> Result<ExecutionResult, SandboxError> {
self.sandbox.compile_and_execute(code, timeout)
}
}
struct MemoryLimiter {
memory_limit: usize,
}
impl wasmtime::ResourceLimiter for MemoryLimiter {
fn memory_growing(
&mut self,
_current: usize,
desired: usize,
_max: Option<usize>,
) -> anyhow::Result<bool> {
Ok(desired <= self.memory_limit)
}
fn table_growing(
&mut self,
_current: usize,
_desired: usize,
_max: Option<usize>,
) -> anyhow::Result<bool> {
Ok(true)
}
}
pub struct ProblemGenerator {
seed: u64,
templates: HashMap<String, ProblemTemplate>,
}
struct ProblemTemplate {
problem_type: String,
parameter_ranges: Vec<(i32, i32)>,
}
#[derive(Debug, PartialEq)]
pub struct GeneratedProblem {
pub problem_type: String,
pub parameters: Vec<i32>,
pub student_id: String,
pub description: String,
}
impl Default for ProblemGenerator {
fn default() -> Self {
Self::new()
}
}
impl ProblemGenerator {
pub fn new() -> Self {
let mut templates = HashMap::new();
templates.insert(
"array_sum".to_string(),
ProblemTemplate {
problem_type: "array_sum".to_string(),
parameter_ranges: vec![(10, 100), (1, 50)],
},
);
templates.insert(
"fibonacci".to_string(),
ProblemTemplate {
problem_type: "fibonacci".to_string(),
parameter_ranges: vec![(5, 20)],
},
);
Self {
seed: 12345,
templates,
}
}
pub fn generate_for_student(
&mut self,
student_id: &str,
problem_type: &str,
) -> GeneratedProblem {
let seed = student_id.bytes().fold(0u64, |acc, b| {
acc.wrapping_mul(31).wrapping_add(u64::from(b))
});
let template = self
.templates
.get(problem_type)
.expect("problem type template should exist");
let mut params = Vec::new();
for (min, max) in &template.parameter_ranges {
let range = (max - min) as u64;
let value = min + ((seed % range) as i32);
params.push(value);
}
let description = match problem_type {
"array_sum" => format!("Calculate sum of array with {} elements", params[0]),
"fibonacci" => format!("Calculate fibonacci number at position {}", params[0]),
_ => "Solve the problem".to_string(),
};
GeneratedProblem {
problem_type: problem_type.to_string(),
parameters: params,
student_id: student_id.to_string(),
description,
}
}
}
pub struct Exercise {
pub name: String,
visible_tests: Vec<TestCase>,
hidden_tests: Vec<TestCase>,
}
#[derive(Clone, Debug)]
pub struct TestCase {
pub input: String,
pub expected: String,
pub points: u32,
}
impl Exercise {
pub fn new(name: &str) -> Self {
Self {
name: name.to_string(),
visible_tests: Vec::new(),
hidden_tests: Vec::new(),
}
}
pub fn add_visible_test(&mut self, test: TestCase) {
self.visible_tests.push(test);
}
pub fn add_hidden_test(&mut self, test: TestCase) {
self.hidden_tests.push(test);
}
pub fn get_visible_tests(&self) -> Vec<TestCase> {
self.visible_tests.clone()
}
pub fn get_all_tests_for_grading(&self) -> Vec<TestCase> {
let mut all = self.visible_tests.clone();
all.extend(self.hidden_tests.clone());
all
}
pub fn get_test_stats(&self) -> (usize, usize) {
(self.visible_tests.len(), self.hidden_tests.len())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_wasm_sandbox_creation() {
let sandbox = WasmSandbox::new();
assert!(sandbox.limits.is_none());
}
#[test]
fn test_wasm_sandbox_default() {
let sandbox = WasmSandbox::default();
assert!(sandbox.limits.is_none());
}
#[test]
fn test_resource_limits_valid() {
let mut sandbox = WasmSandbox::new();
let limits = ResourceLimits {
memory_mb: 128,
cpu_time_ms: 5000,
stack_size_kb: 1024,
heap_size_mb: 64,
file_access: false,
network_access: false,
};
let result = sandbox.configure(limits);
assert!(result.is_ok());
assert!(sandbox.limits.is_some());
}
#[test]
fn test_resource_limits_invalid_memory_zero() {
let mut sandbox = WasmSandbox::new();
let limits = ResourceLimits {
memory_mb: 0,
cpu_time_ms: 5000,
stack_size_kb: 1024,
heap_size_mb: 64,
file_access: false,
network_access: false,
};
let result = sandbox.configure(limits);
assert!(result.is_err());
assert!(result
.unwrap_err()
.contains("Memory limit must be between 1 and 1024 MB"));
}
#[test]
fn test_resource_limits_invalid_memory_too_large() {
let mut sandbox = WasmSandbox::new();
let limits = ResourceLimits {
memory_mb: 2048,
cpu_time_ms: 5000,
stack_size_kb: 1024,
heap_size_mb: 64,
file_access: false,
network_access: false,
};
let result = sandbox.configure(limits);
assert!(result.is_err());
assert!(result
.unwrap_err()
.contains("Memory limit must be between 1 and 1024 MB"));
}
#[test]
fn test_ruchy_function_creation() {
let function = RuchyFunction {
name: "test_func".to_string(),
parameters: vec![
RuchyParameter {
name: "x".to_string(),
param_type: WasmType::I32,
},
RuchyParameter {
name: "y".to_string(),
param_type: WasmType::F64,
},
],
return_type: WasmType::I32,
body: vec![RuchyStatement::Return(RuchyExpression::Literal(
RuchyValue::Integer(42),
))],
};
assert_eq!(function.name, "test_func");
assert_eq!(function.parameters.len(), 2);
assert_eq!(function.return_type, WasmType::I32);
assert_eq!(function.body.len(), 1);
}
#[test]
fn test_ruchy_constant_creation() {
let constant = RuchyConstant {
name: "PI".to_string(),
value: RuchyValue::Float(3.15159),
const_type: WasmType::F64,
};
assert_eq!(constant.name, "PI");
assert_eq!(constant.const_type, WasmType::F64);
if let RuchyValue::Float(value) = constant.value {
assert!((value - 3.15159).abs() < f64::EPSILON);
} else {
panic!("Expected Float value");
}
}
#[test]
fn test_ruchy_values() {
let int_val = RuchyValue::Integer(42);
let float_val = RuchyValue::Float(3.15);
let string_val = RuchyValue::String("hello".to_string());
let bool_val = RuchyValue::Boolean(true);
let null_val = RuchyValue::Null;
match int_val {
RuchyValue::Integer(42) => (),
_ => panic!("Expected Integer(42)"),
}
match float_val {
RuchyValue::Float(f) if (f - 3.15).abs() < f64::EPSILON => (),
_ => panic!("Expected Float(3.15)"),
}
match string_val {
RuchyValue::String(ref s) if s == "hello" => (),
_ => panic!("Expected String(hello)"),
}
match bool_val {
RuchyValue::Boolean(true) => (),
_ => panic!("Expected Boolean(true)"),
}
match null_val {
RuchyValue::Null => (),
_ => panic!("Expected Null"),
}
}
#[test]
fn test_wasm_types() {
let types = [
WasmType::I32,
WasmType::I64,
WasmType::F32,
WasmType::F64,
WasmType::Void,
];
assert_eq!(types.len(), 5);
assert_eq!(types[0], WasmType::I32);
assert_eq!(types[4], WasmType::Void);
}
#[test]
fn test_binary_operations() {
let ops = [
BinaryOp::Add,
BinaryOp::Sub,
BinaryOp::Mul,
BinaryOp::Div,
BinaryOp::Eq,
BinaryOp::Ne,
BinaryOp::Lt,
BinaryOp::Le,
BinaryOp::Gt,
BinaryOp::Ge,
];
assert_eq!(ops.len(), 10);
}
#[test]
fn test_ruchy_statements() {
let return_stmt = RuchyStatement::Return(RuchyExpression::Literal(RuchyValue::Integer(42)));
let assignment_stmt = RuchyStatement::Assignment(
"x".to_string(),
RuchyExpression::Literal(RuchyValue::Integer(10)),
);
let expr_stmt = RuchyStatement::Expression(RuchyExpression::Variable("x".to_string()));
match return_stmt {
RuchyStatement::Return(_) => (),
_ => panic!("Expected Return statement"),
}
match assignment_stmt {
RuchyStatement::Assignment(ref var, _) if var == "x" => (),
_ => panic!("Expected Assignment to x"),
}
match expr_stmt {
RuchyStatement::Expression(_) => (),
_ => panic!("Expected Expression statement"),
}
}
#[test]
fn test_ruchy_expressions() {
let literal_expr = RuchyExpression::Literal(RuchyValue::Integer(42));
let variable_expr = RuchyExpression::Variable("x".to_string());
let binary_expr = RuchyExpression::Binary(
Box::new(RuchyExpression::Literal(RuchyValue::Integer(1))),
BinaryOp::Add,
Box::new(RuchyExpression::Literal(RuchyValue::Integer(2))),
);
let call_expr = RuchyExpression::Call(
"func".to_string(),
vec![RuchyExpression::Literal(RuchyValue::Integer(42))],
);
match literal_expr {
RuchyExpression::Literal(_) => (),
_ => panic!("Expected Literal expression"),
}
match variable_expr {
RuchyExpression::Variable(ref name) if name == "x" => (),
_ => panic!("Expected Variable(x) expression"),
}
match binary_expr {
RuchyExpression::Binary(_, BinaryOp::Add, _) => (),
_ => panic!("Expected Binary Add expression"),
}
match call_expr {
RuchyExpression::Call(ref name, ref args) if name == "func" && args.len() == 1 => (),
_ => panic!("Expected Call expression"),
}
}
#[test]
fn test_parsed_ruchy_code() {
let code = ParsedRuchyCode {
functions: vec![RuchyFunction {
name: "add".to_string(),
parameters: vec![
RuchyParameter {
name: "a".to_string(),
param_type: WasmType::I32,
},
RuchyParameter {
name: "b".to_string(),
param_type: WasmType::I32,
},
],
return_type: WasmType::I32,
body: vec![RuchyStatement::Return(RuchyExpression::Binary(
Box::new(RuchyExpression::Variable("a".to_string())),
BinaryOp::Add,
Box::new(RuchyExpression::Variable("b".to_string())),
))],
}],
main_function: None,
constants: vec![],
};
assert_eq!(code.functions.len(), 1);
assert!(code.main_function.is_none());
assert_eq!(code.constants.len(), 0);
assert_eq!(code.functions[0].name, "add");
}
#[test]
fn test_sandbox_errors() {
let errors = [
SandboxError::MemoryLimitExceeded,
SandboxError::Timeout,
SandboxError::PermissionDenied("file access".to_string()),
SandboxError::NetworkAccessDenied,
SandboxError::CompilationError("syntax error".to_string()),
SandboxError::RuntimeError("division by zero".to_string()),
];
assert_eq!(errors.len(), 6);
}
#[test]
fn test_execution_result() {
let result = ExecutionResult {
output: "Hello, World!".to_string(),
memory_used: 1024,
cpu_time_ms: 150,
gas_used: 5000,
};
assert_eq!(result.output, "Hello, World!");
assert_eq!(result.memory_used, 1024);
assert_eq!(result.cpu_time_ms, 150);
assert_eq!(result.gas_used, 5000);
}
#[test]
fn test_problem_generator_creation() {
let generator = ProblemGenerator::new();
assert_eq!(generator.seed, 12345);
assert!(!generator.templates.is_empty());
}
#[test]
fn test_generate_for_student() {
let mut generator = ProblemGenerator::new();
let problem1 = generator.generate_for_student("student123", "array_sum");
let problem2 = generator.generate_for_student("student123", "array_sum");
assert_eq!(problem1.student_id, problem2.student_id);
assert_eq!(problem1.parameters, problem2.parameters);
assert_eq!(problem1.problem_type, "array_sum");
}
#[test]
fn test_generate_for_different_students() {
let mut generator = ProblemGenerator::new();
let problem1 = generator.generate_for_student("alice", "fibonacci");
let problem2 = generator.generate_for_student("bob", "fibonacci");
assert_ne!(problem1.parameters, problem2.parameters);
assert_eq!(problem1.problem_type, problem2.problem_type);
assert_ne!(problem1.student_id, problem2.student_id);
}
#[test]
fn test_exercise_creation() {
let exercise = Exercise::new("Basic Addition");
assert_eq!(exercise.name, "Basic Addition");
assert_eq!(exercise.get_test_stats(), (0, 0));
}
#[test]
fn test_exercise_add_visible_test() {
let mut exercise = Exercise::new("Test Exercise");
let test = TestCase {
input: "2 + 2".to_string(),
expected: "4".to_string(),
points: 10,
};
exercise.add_visible_test(test);
assert_eq!(exercise.get_test_stats(), (1, 0));
let visible_tests = exercise.get_visible_tests();
assert_eq!(visible_tests.len(), 1);
assert_eq!(visible_tests[0].input, "2 + 2");
assert_eq!(visible_tests[0].expected, "4");
assert_eq!(visible_tests[0].points, 10);
}
#[test]
fn test_exercise_add_hidden_test() {
let mut exercise = Exercise::new("Test Exercise");
let test = TestCase {
input: "5 * 6".to_string(),
expected: "30".to_string(),
points: 15,
};
exercise.add_hidden_test(test);
assert_eq!(exercise.get_test_stats(), (0, 1));
}
#[test]
fn test_exercise_mixed_tests() {
let mut exercise = Exercise::new("Mixed Tests");
let visible_test = TestCase {
input: "1 + 1".to_string(),
expected: "2".to_string(),
points: 5,
};
let hidden_test = TestCase {
input: "10 - 3".to_string(),
expected: "7".to_string(),
points: 10,
};
exercise.add_visible_test(visible_test);
exercise.add_hidden_test(hidden_test);
assert_eq!(exercise.get_test_stats(), (1, 1));
let all_tests = exercise.get_all_tests_for_grading();
assert_eq!(all_tests.len(), 2);
let visible_tests = exercise.get_visible_tests();
assert_eq!(visible_tests.len(), 1);
}
#[test]
fn test_test_case_clone() {
let test = TestCase {
input: "test input".to_string(),
expected: "test output".to_string(),
points: 20,
};
let cloned_test = test.clone();
assert_eq!(test.input, cloned_test.input);
assert_eq!(test.expected, cloned_test.expected);
assert_eq!(test.points, cloned_test.points);
}
#[test]
fn test_problem_template() {
let template = ProblemTemplate {
problem_type: "test_template".to_string(),
parameter_ranges: vec![(1, 10), (5, 15)],
};
assert_eq!(template.problem_type, "test_template");
assert_eq!(template.parameter_ranges.len(), 2);
assert_eq!(template.parameter_ranges[0], (1, 10));
assert_eq!(template.parameter_ranges[1], (5, 15));
}
#[test]
fn test_generated_problem() {
let problem = GeneratedProblem {
problem_type: "sorting".to_string(),
parameters: vec![5, 10, 15],
student_id: "student456".to_string(),
description: "Sort an array of integers".to_string(),
};
assert_eq!(problem.problem_type, "sorting");
assert_eq!(problem.parameters, vec![5, 10, 15]);
assert_eq!(problem.student_id, "student456");
assert_eq!(problem.description, "Sort an array of integers");
}
#[test]
fn test_complex_ruchy_if_statement() {
let if_stmt = RuchyStatement::If(
RuchyExpression::Binary(
Box::new(RuchyExpression::Variable("x".to_string())),
BinaryOp::Gt,
Box::new(RuchyExpression::Literal(RuchyValue::Integer(0))),
),
vec![RuchyStatement::Return(RuchyExpression::Literal(
RuchyValue::Boolean(true),
))],
Some(vec![RuchyStatement::Return(RuchyExpression::Literal(
RuchyValue::Boolean(false),
))]),
);
match if_stmt {
RuchyStatement::If(_, then_branch, else_branch) => {
assert_eq!(then_branch.len(), 1);
assert!(else_branch.is_some());
assert_eq!(
else_branch.expect("operation should succeed in test").len(),
1
);
}
_ => panic!("Expected If statement"),
}
}
#[test]
fn test_complex_ruchy_while_statement() {
let while_stmt = RuchyStatement::While(
RuchyExpression::Binary(
Box::new(RuchyExpression::Variable("i".to_string())),
BinaryOp::Lt,
Box::new(RuchyExpression::Literal(RuchyValue::Integer(10))),
),
vec![RuchyStatement::Assignment(
"i".to_string(),
RuchyExpression::Binary(
Box::new(RuchyExpression::Variable("i".to_string())),
BinaryOp::Add,
Box::new(RuchyExpression::Literal(RuchyValue::Integer(1))),
),
)],
);
match while_stmt {
RuchyStatement::While(_, body) => {
assert_eq!(body.len(), 1);
}
_ => panic!("Expected While statement"),
}
}
#[test]
fn test_resource_limits_with_access_permissions() {
let limits = ResourceLimits {
memory_mb: 256,
cpu_time_ms: 10000,
stack_size_kb: 2048,
heap_size_mb: 128,
file_access: true,
network_access: true,
};
assert!(limits.file_access);
assert!(limits.network_access);
assert_eq!(limits.memory_mb, 256);
assert_eq!(limits.cpu_time_ms, 10000);
assert_eq!(limits.stack_size_kb, 2048);
assert_eq!(limits.heap_size_mb, 128);
}
}