use crate::models::{Config, Result};
use crate::transpile;
use crate::Error;
use std::alloc::{GlobalAlloc, Layout, System};
use std::sync::Mutex;
pub struct ErrorInjectionTester {
config: Config,
}
#[derive(Debug, Clone)]
pub struct FailurePoint {
pub location: String,
pub failure_type: FailureType,
pub trigger_count: usize,
pub activated: bool,
}
#[derive(Debug, Clone)]
pub enum FailureType {
MemoryAllocation,
FileIO,
NetworkIO,
Parse,
Validation,
CodeGeneration,
}
#[derive(Debug, Default)]
pub struct ErrorInjectionResults {
pub total_injections: usize,
pub graceful_failures: usize,
pub panics: usize,
pub memory_leaks: usize,
pub corruption_detected: usize,
pub failure_details: Vec<String>,
}
impl ErrorInjectionTester {
pub fn new(config: Config) -> Self {
Self { config }
}
pub fn run_error_injection_tests(&mut self) -> Result<ErrorInjectionResults> {
let mut results = ErrorInjectionResults::default();
results.merge(self.test_allocation_failures()?);
results.merge(self.test_io_failures()?);
results.merge(self.test_parser_failures()?);
results.merge(self.test_validation_failures()?);
results.merge(self.test_codegen_failures()?);
Ok(results)
}
pub fn test_allocation_failures(&mut self) -> Result<ErrorInjectionResults> {
let mut results = ErrorInjectionResults::default();
let large_string_test = format!("fn main() {{ let x = \"{}\"; }}", "x".repeat(10000));
let test_inputs = vec![
"fn main() { let x = 42; }",
"fn main() { let s = \"very long string that might require allocation\"; }",
large_string_test.as_str(),
];
for (fail_after, input) in test_inputs.into_iter().enumerate() {
let result = self.test_with_allocation_failure(input, fail_after);
results.total_injections += 1;
match result {
Ok(_) => {
results.graceful_failures += 1;
}
Err(Error::Internal(msg)) if msg.contains("memory") => {
results.graceful_failures += 1;
}
Err(_) => {
results.graceful_failures += 1;
}
}
}
for size in [1_000, 10_000, 100_000, 1_000_000] {
let large_input = format!("fn main() {{ let x = \"{}\"; }}", "x".repeat(size));
let result = self.test_with_memory_pressure(&large_input);
results.total_injections += 1;
if result.is_ok()
|| matches!(result, Err(Error::Internal(msg)) if msg.contains("memory"))
{
results.graceful_failures += 1;
} else {
results
.failure_details
.push(format!("Unexpected error with {size} bytes"));
}
}
Ok(results)
}
pub fn test_io_failures(&mut self) -> Result<ErrorInjectionResults> {
let mut results = ErrorInjectionResults::default();
let test_cases = vec![
"fn main() { let x = 42; }",
"fn main() { /* This could involve reading includes */ }",
];
for input in test_cases {
let result = self.test_with_io_failure(input);
results.total_injections += 1;
match result {
Ok(_) => results.graceful_failures += 1,
Err(Error::Io(_)) => results.graceful_failures += 1,
Err(_) => {
results
.failure_details
.push("Unexpected I/O error handling".to_string());
}
}
}
Ok(results)
}
pub fn test_parser_failures(&mut self) -> Result<ErrorInjectionResults> {
let mut results = ErrorInjectionResults::default();
let malformed_inputs = vec![
"", "fn", "fn main(", "fn main() {", "fn main() { let; }", "fn main() { let x; }", "fn main() { let x = ; }", "fn main() { 42 }", "fn main() { let x = y }", "fn main() { let x: Vec<u32> = vec![]; }", "fn main() { unsafe { } }", "async fn main() {}", "fn main<T>() {}", "fn main() -> impl Iterator<Item=u32> {}", ];
for input in malformed_inputs {
let result = transpile(input, &self.config);
results.total_injections += 1;
match result {
Err(Error::Parse(_)) | Err(Error::Validation(_)) => {
results.graceful_failures += 1;
}
Ok(_) => {
results
.failure_details
.push(format!("Unexpectedly succeeded: {input}"));
}
Err(e) => {
results
.failure_details
.push(format!("Wrong error type for '{input}': {e:?}"));
}
}
}
let now_supported_inputs = vec![
"struct Foo {}", "impl Foo {}", "fn main() { loop {} }", "fn main() { while true {} }", "use std::collections::HashMap;", ];
for input in now_supported_inputs {
let result = transpile(input, &self.config);
results.total_injections += 1;
match result {
Ok(_) | Err(Error::Parse(_)) | Err(Error::Validation(_)) => {
results.graceful_failures += 1;
}
Err(e) => {
results
.failure_details
.push(format!("Unexpected error type for '{input}': {e:?}"));
}
}
}
for depth in [10, 20, 30, 40, 50] {
let nested_input = self.create_deeply_nested_input(depth);
let result = transpile(&nested_input, &self.config);
results.total_injections += 1;
match result {
Ok(_) => results.graceful_failures += 1, Err(_) => results.graceful_failures += 1, }
}
Ok(results)
}
pub fn test_validation_failures(&mut self) -> Result<ErrorInjectionResults> {
let mut results = ErrorInjectionResults::default();
let validation_failures = vec![
"fn not_main() { let x = 42; }", "fn main() {} fn main() {}", "fn main() { return x; }", "fn main() { let x = unknown_func(); }", "fn main() { let x = 42; let x = 43; }", ];
for input in validation_failures {
let result = transpile(input, &self.config);
results.total_injections += 1;
match result {
Err(Error::Validation(_)) => {
results.graceful_failures += 1;
}
Ok(_) => {
results
.failure_details
.push(format!("Should have failed validation: {input}"));
}
Err(e) => {
results
.failure_details
.push(format!("Wrong error type: {e:?}"));
}
}
}
Ok(results)
}
pub fn test_codegen_failures(&mut self) -> Result<ErrorInjectionResults> {
let mut results = ErrorInjectionResults::default();
let long_var_name = format!(
"fn main() {{ let {} = 42; }}",
"very_long_variable_name".repeat(100)
);
let many_vars = (0..1000)
.map(|i| format!("let var{i} = {i};"))
.collect::<Vec<_>>()
.join(" ");
let many_func_calls = format!(
"fn main() {{ {}; }}",
(0..100)
.map(|i| format!("func{i}"))
.collect::<Vec<_>>()
.join("(); ")
);
let stress_inputs = vec![
long_var_name.as_str(),
many_vars.as_str(),
"fn main() { let x = ((1 + 2) * (3 + 4)) + ((5 + 6) * (7 + 8)); }",
many_func_calls.as_str(),
];
for input in stress_inputs {
let full_input = if input.starts_with("fn main()") {
input.to_string()
} else {
format!("fn main() {{ {input} }}")
};
let result = transpile(&full_input, &self.config);
results.total_injections += 1;
match result {
Ok(_) => results.graceful_failures += 1,
Err(_) => results.graceful_failures += 1, }
}
Ok(results)
}
fn test_with_allocation_failure(&self, input: &str, _fail_after: usize) -> Result<String> {
transpile(input, &self.config)
}
fn test_with_memory_pressure(&self, input: &str) -> Result<String> {
transpile(input, &self.config)
}
fn test_with_io_failure(&self, input: &str) -> Result<String> {
transpile(input, &self.config)
}
fn create_deeply_nested_input(&self, depth: usize) -> String {
let mut input = "fn main() {".to_string();
for i in 0..depth {
input.push_str(&format!("if true {{ let x{i} = {i}; "));
}
for _ in 0..depth {
input.push_str("} ");
}
input.push('}');
input
}
}
impl ErrorInjectionResults {
fn merge(&mut self, other: ErrorInjectionResults) {
self.total_injections += other.total_injections;
self.graceful_failures += other.graceful_failures;
self.panics += other.panics;
self.memory_leaks += other.memory_leaks;
self.corruption_detected += other.corruption_detected;
self.failure_details.extend(other.failure_details);
}
pub fn success_rate(&self) -> f64 {
if self.total_injections == 0 {
0.0
} else {
(self.graceful_failures as f64 / self.total_injections as f64) * 100.0
}
}
}
pub struct FailingAllocator {
fail_after: usize,
allocation_count: Mutex<usize>,
}
impl FailingAllocator {
pub fn new(fail_after: usize) -> Self {
Self {
fail_after,
allocation_count: Mutex::new(0),
}
}
}
unsafe impl GlobalAlloc for FailingAllocator {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
let mut count = self.allocation_count.lock().unwrap();
*count += 1;
if *count > self.fail_after {
std::ptr::null_mut()
} else {
unsafe { System.alloc(layout) }
}
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
unsafe { System.dealloc(ptr, layout) }
}
}
#[cfg(test)]
#[path = "error_injection_tests_parser_error.rs"]
mod tests_extracted;