pub mod capabilities;
pub mod compilation;
pub mod disassembly;
pub mod execution;
pub mod reflection;
pub mod runner;
pub use capabilities::{Architecture, TestCapabilities};
pub use reflection::MethodTest;
pub use runner::TestRunner;
use crate::prelude::*;
use std::path::Path;
#[derive(Debug)]
pub struct CompleteTestResult {
pub architecture: Architecture,
pub compilation_success: bool,
pub modification_success: bool,
pub execution_success: bool,
pub disassembly_success: bool,
pub reflection_success: bool,
pub errors: Vec<String>,
}
impl CompleteTestResult {
pub fn is_fully_successful(&self) -> bool {
self.compilation_success
&& self.modification_success
&& self.execution_success
&& self.disassembly_success
&& self.reflection_success
&& self.errors.is_empty()
}
pub fn is_buildable(&self) -> bool {
self.compilation_success && self.modification_success
}
pub fn summary(&self) -> String {
if self.is_fully_successful() {
format!("{}: All tests passed", self.architecture.name)
} else {
format!(
"{}: compile={}, modify={}, exec={}, disasm={}, reflect={}, errors={}",
self.architecture.name,
self.compilation_success,
self.modification_success,
self.execution_success,
self.disassembly_success,
self.reflection_success,
self.errors.len()
)
}
}
}
pub fn run_complete_test<M>(
runner: &TestRunner,
source_code: &str,
modify_fn: M,
) -> Result<Vec<CompleteTestResult>>
where
M: Fn(&mut crate::CilAssembly) -> Result<()>,
{
let caps = runner.capabilities();
let mut results = Vec::new();
for arch in runner.architectures() {
let mut result = CompleteTestResult {
architecture: arch.clone(),
compilation_success: false,
modification_success: false,
execution_success: false,
disassembly_success: false,
reflection_success: false,
errors: Vec::new(),
};
let arch_dir = match runner.arch_dir(arch) {
Ok(d) => d,
Err(e) => {
result
.errors
.push(format!("Failed to create directory: {}", e));
results.push(result);
continue;
}
};
let compile_result = match compilation::compile(caps, source_code, &arch_dir, "test", arch)
{
Ok(r) => r,
Err(e) => {
result.errors.push(format!("Compilation error: {}", e));
results.push(result);
continue;
}
};
if !compile_result.is_success() {
result
.errors
.push(format!("Compilation failed: {:?}", compile_result.error));
results.push(result);
continue;
}
result.compilation_success = true;
let original_path = compile_result.assembly_path();
let modified_dir = arch_dir.join("modified");
std::fs::create_dir_all(&modified_dir).ok();
let modified_path = modified_dir.join(
original_path
.file_name()
.unwrap_or_else(|| std::ffi::OsStr::new("modified.dll")),
);
match modify_assembly(original_path, &modified_path, &modify_fn) {
Ok(_) => {
result.modification_success = true;
if let Some(stem) = original_path.file_stem().and_then(|s| s.to_str()) {
if let Some(parent) = original_path.parent() {
let config_src = parent.join(format!("{}.runtimeconfig.json", stem));
if config_src.exists() {
let config_dst =
modified_dir.join(format!("{}.runtimeconfig.json", stem));
std::fs::copy(&config_src, &config_dst).ok();
}
}
}
}
Err(e) => {
result.errors.push(format!("Modification failed: {}", e));
results.push(result);
continue;
}
}
match execution::execute(caps, &modified_path) {
Ok(exec_result) => {
if exec_result.is_success() {
result.execution_success = true;
} else {
result
.errors
.push(format!("Execution failed: {}", exec_result.error_summary()));
}
}
Err(e) => {
result.errors.push(format!("Execution error: {}", e));
}
}
if caps.can_disassemble() {
match disassembly::disassemble(caps, &modified_path) {
Ok(disasm_result) => {
if disasm_result.is_success() {
result.disassembly_success = true;
} else {
result
.errors
.push(format!("Disassembly failed: {:?}", disasm_result.error));
}
}
Err(e) => {
result.errors.push(format!("Disassembly error: {}", e));
}
}
} else {
result.disassembly_success = true;
}
match reflection::verify_assembly_loadable(caps, &modified_path, &modified_dir, arch) {
Ok(refl_result) => {
if refl_result.is_success() {
result.reflection_success = true;
} else {
result.errors.push(format!(
"Reflection failed: {}",
refl_result.error_summary()
));
}
}
Err(e) => {
result.errors.push(format!("Reflection error: {}", e));
}
}
results.push(result);
}
Ok(results)
}
pub fn run_complete_test_with_reflection<M, T>(
runner: &TestRunner,
source_code: &str,
modify_fn: M,
create_tests_fn: T,
) -> Result<Vec<CompleteTestResult>>
where
M: Fn(&mut crate::CilAssembly) -> Result<()>,
T: Fn(&Path) -> Vec<reflection::MethodTest>,
{
let caps = runner.capabilities();
let mut results = Vec::new();
for arch in runner.architectures() {
let mut result = CompleteTestResult {
architecture: arch.clone(),
compilation_success: false,
modification_success: false,
execution_success: false,
disassembly_success: false,
reflection_success: false,
errors: Vec::new(),
};
let arch_dir = match runner.arch_dir(arch) {
Ok(d) => d,
Err(e) => {
result
.errors
.push(format!("Failed to create directory: {}", e));
results.push(result);
continue;
}
};
let compile_result = match compilation::compile(caps, source_code, &arch_dir, "test", arch)
{
Ok(r) => r,
Err(e) => {
result.errors.push(format!("Compilation error: {}", e));
results.push(result);
continue;
}
};
if !compile_result.is_success() {
result
.errors
.push(format!("Compilation failed: {:?}", compile_result.error));
results.push(result);
continue;
}
result.compilation_success = true;
let original_path = compile_result.assembly_path();
let modified_dir = arch_dir.join("modified");
std::fs::create_dir_all(&modified_dir).ok();
let modified_path = modified_dir.join(
original_path
.file_name()
.unwrap_or_else(|| std::ffi::OsStr::new("modified.dll")),
);
match modify_assembly(original_path, &modified_path, &modify_fn) {
Ok(_) => {
result.modification_success = true;
if let Some(stem) = original_path.file_stem().and_then(|s| s.to_str()) {
if let Some(parent) = original_path.parent() {
let config_src = parent.join(format!("{}.runtimeconfig.json", stem));
if config_src.exists() {
let config_dst =
modified_dir.join(format!("{}.runtimeconfig.json", stem));
std::fs::copy(&config_src, &config_dst).ok();
}
}
}
}
Err(e) => {
result.errors.push(format!("Modification failed: {}", e));
results.push(result);
continue;
}
}
match execution::execute(caps, &modified_path) {
Ok(exec_result) => {
if exec_result.is_success() {
result.execution_success = true;
} else {
result
.errors
.push(format!("Execution failed: {}", exec_result.error_summary()));
}
}
Err(e) => {
result.errors.push(format!("Execution error: {}", e));
}
}
if caps.can_disassemble() {
match disassembly::disassemble(caps, &modified_path) {
Ok(disasm_result) => {
if disasm_result.is_success() {
result.disassembly_success = true;
} else {
result
.errors
.push(format!("Disassembly failed: {:?}", disasm_result.error));
}
}
Err(e) => {
result.errors.push(format!("Disassembly error: {}", e));
}
}
} else {
result.disassembly_success = true;
}
let method_tests = create_tests_fn(&modified_path);
if method_tests.is_empty() {
match reflection::verify_assembly_loadable(caps, &modified_path, &modified_dir, arch) {
Ok(refl_result) => {
if refl_result.is_success() {
result.reflection_success = true;
} else {
result.errors.push(format!(
"Reflection failed: {}",
refl_result.error_summary()
));
}
}
Err(e) => {
result.errors.push(format!("Reflection error: {}", e));
}
}
} else {
match reflection::run_reflection_test(
caps,
&modified_path,
&method_tests,
&modified_dir,
arch,
) {
Ok(refl_result) => {
if refl_result.is_success() {
result.reflection_success = true;
} else {
result.errors.push(format!(
"Reflection test failed: {}",
refl_result.error_summary()
));
}
}
Err(e) => {
result.errors.push(format!("Reflection error: {}", e));
}
}
}
results.push(result);
}
Ok(results)
}
fn modify_assembly<M>(original_path: &Path, modified_path: &Path, modify_fn: &M) -> Result<()>
where
M: Fn(&mut crate::CilAssembly) -> Result<()>,
{
use crate::prelude::*;
let mut assembly = CilAssembly::from_path(original_path)?;
modify_fn(&mut assembly)?;
assembly.to_file(modified_path)?;
Ok(())
}
pub fn all_successful(results: &[CompleteTestResult]) -> bool {
results.iter().all(|r| r.is_fully_successful())
}
pub fn error_summary(results: &[CompleteTestResult]) -> String {
let failed: Vec<_> = results
.iter()
.filter(|r| !r.is_fully_successful())
.collect();
if failed.is_empty() {
"All tests passed".to_string()
} else {
failed
.iter()
.map(|r| format!("{}: {}", r.architecture.name, r.errors.join(", ")))
.collect::<Vec<_>>()
.join("\n")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_capabilities_detection() {
let caps = TestCapabilities::detect();
println!("Detected: {}", caps.summary());
}
#[test]
fn test_runner_creation() -> Result<()> {
let runner = TestRunner::new()?;
println!("Runner capabilities: {}", runner.capabilities().summary());
println!(
"Supported architectures: {:?}",
runner
.architectures()
.iter()
.map(|a| a.name)
.collect::<Vec<_>>()
);
Ok(())
}
#[test]
fn test_complete_workflow() -> Result<()> {
let runner = TestRunner::new()?;
let results =
run_complete_test(&runner, compilation::templates::HELLO_WORLD, |_assembly| {
Ok(())
})?;
for result in &results {
println!("{}", result.summary());
}
assert!(
results.iter().all(|r| r.compilation_success),
"Compilation should succeed"
);
Ok(())
}
}