use std::ffi::CString; use pyo3::prelude::*; use pyo3::types::{PyList, PyModule}; const VALIDATOR_CODE: &str = include_str!("../shared_state_validator.py"); fn validator_module<'py>(py: Python<'py>) -> PyResult> { let code = CString::new(VALIDATOR_CODE).expect("validator source contains a null byte"); PyModule::from_code( py, &code, c"shared_state_validator.py", c"shared_state_validator", ) } #[cfg(test)] fn validate_source( source: &str, filename: &str, class_names: &[&str], ) -> Result, String> { Python::attach(|py| { let module = validator_module(py).map_err(|error| error.to_string())?; let class_names = PyList::new(py, class_names).map_err(|error| error.to_string())?; module .getattr("validate_source") .and_then(|function| function.call1((source, filename, class_names))) .and_then(|result| result.extract::>()) .map_err(|error| error.to_string()) }) } fn validate_solution(path: &str, class_names: &[&str]) -> Result, String> { Python::attach(|py| { let module = validator_module(py).map_err(|error| error.to_string())?; let class_names = PyList::new(py, class_names).map_err(|error| error.to_string())?; module .getattr("validate_solution") .and_then(|function| function.call1((path, class_names))) .and_then(|result| result.extract::>()) .map_err(|error| error.to_string()) }) } pub fn validate_or_report(path: &str, class_names: &[&str]) -> bool { match validate_solution(path, class_names) { Ok(violations) if violations.is_empty() => true, Ok(violations) => { println!("ERROR: shared state between AnySystem processes is forbidden"); for violation in violations { println!("{violation}"); } println!("\nSCORE: 0\n"); false } Err(error) => panic!("failed to validate solution for shared state: {error}"), } } #[cfg(test)] mod tests { use super::validate_source; #[test] fn embeds_validator_and_rejects_shared_state() { let violations = validate_source( "class ProcessImpl:\n shared = {}\n", "solution.py", &["ProcessImpl"], ) .unwrap(); assert_eq!(violations.len(), 1); assert!(violations[0].contains("ProcessImpl.shared")); } }