use std::fmt::Write;
use monty::MontyRun;
use monty_types::{CompileOptions, ExcType, MontyException};
fn generate_many_locals(count: usize) -> String {
let mut code = String::from("def f():\n");
for i in 0..count {
writeln!(code, " v{i} = {i}").unwrap();
}
writeln!(code, " return v{}", count - 1).unwrap();
code.push_str("f()");
code
}
fn generate_many_positional_args(count: usize) -> String {
let mut code = String::from("def f(*args): return len(args)\nf(");
for i in 0..count {
if i > 0 {
code.push_str(", ");
}
code.push_str(&i.to_string());
}
code.push(')');
code
}
fn generate_many_keyword_args(count: usize) -> String {
let mut code = String::from("def f(**kw): return len(kw)\nf(");
for i in 0..count {
if i > 0 {
code.push_str(", ");
}
write!(code, "k{i}={i}").unwrap();
}
code.push(')');
code
}
fn generate_many_parameters(count: usize) -> String {
let mut code = String::from("def f(");
for i in 0..count {
if i > 0 {
code.push_str(", ");
}
write!(code, "p{i}").unwrap();
}
code.push_str("):\n");
writeln!(code, " return p{}", count - 1).unwrap();
code.push_str("f(");
for i in 0..count {
if i > 0 {
code.push_str(", ");
}
code.push_str(&i.to_string());
}
code.push(')');
code
}
fn assert_syntax_error(result: Result<MontyRun, MontyException>, expected_msg: &str) {
let err = result.expect_err("expected SyntaxError");
assert_eq!(
err.exc_type(),
ExcType::SyntaxError,
"expected SyntaxError, got {:?}: {:?}",
err.exc_type(),
err.message()
);
let msg = err.message().expect("SyntaxError should have message");
assert!(
msg.contains(expected_msg),
"expected message containing '{expected_msg}', got: {msg}"
);
}
mod local_variable_limits {
use super::*;
#[test]
fn locals_under_u8_limit_succeeds() {
let code = generate_many_locals(255);
let result = MontyRun::new(code, "test.py", vec![], CompileOptions::default());
assert!(result.is_ok(), "255 locals should compile successfully");
let run = result.unwrap();
let result = run.run_no_limits(vec![]);
assert!(result.is_ok(), "255 locals should run successfully");
}
#[test]
fn locals_at_u8_boundary_succeeds() {
let code = generate_many_locals(256);
let result = MontyRun::new(code, "test.py", vec![], CompileOptions::default());
assert!(
result.is_ok(),
"256 locals should compile successfully (wide instructions)"
);
let run = result.unwrap();
let result = run.run_no_limits(vec![]);
assert!(result.is_ok(), "256 locals should run successfully");
}
#[test]
fn locals_exceeding_u8_uses_wide_instructions() {
let code = generate_many_locals(257);
let result = MontyRun::new(code, "test.py", vec![], CompileOptions::default());
assert!(result.is_ok(), "257 locals should compile (using wide instructions)");
let run = result.unwrap();
let result = run.run_no_limits(vec![]);
assert!(result.is_ok(), "257 locals should run correctly with wide instructions");
}
#[test]
fn locals_well_over_u8_limit() {
let code = generate_many_locals(300);
let result = MontyRun::new(code, "test.py", vec![], CompileOptions::default());
assert!(result.is_ok(), "300 locals should compile successfully");
let run = result.unwrap();
let result = run.run_no_limits(vec![]);
assert!(result.is_ok(), "300 locals should run successfully");
}
}
mod function_argument_limits {
use super::*;
#[test]
fn positional_args_under_u8_limit_succeeds() {
let code = generate_many_positional_args(255);
let result = MontyRun::new(code, "test.py", vec![], CompileOptions::default());
assert!(result.is_ok(), "255 positional args should compile successfully");
let run = result.unwrap();
let result = run.run_no_limits(vec![]);
assert!(result.is_ok(), "255 positional args should run successfully");
}
#[test]
fn positional_args_at_u8_boundary_returns_syntax_error() {
let code = generate_many_positional_args(256);
let result = MontyRun::new(code, "test.py", vec![], CompileOptions::default());
assert_syntax_error(result, "more than 255 positional arguments");
}
#[test]
fn positional_args_exceeding_u8_limit_returns_syntax_error() {
let code = generate_many_positional_args(257);
let result = MontyRun::new(code, "test.py", vec![], CompileOptions::default());
assert_syntax_error(result, "more than 255 positional arguments");
}
}
mod keyword_argument_limits {
use super::*;
#[test]
fn keyword_args_under_u8_limit_succeeds() {
let code = generate_many_keyword_args(255);
let result = MontyRun::new(code, "test.py", vec![], CompileOptions::default());
assert!(result.is_ok(), "255 keyword args should compile successfully");
let run = result.unwrap();
let result = run.run_no_limits(vec![]);
assert!(result.is_ok(), "255 keyword args should run successfully");
}
#[test]
fn keyword_args_at_u8_boundary_returns_syntax_error() {
let code = generate_many_keyword_args(256);
let result = MontyRun::new(code, "test.py", vec![], CompileOptions::default());
assert_syntax_error(result, "more than 255 keyword arguments");
}
#[test]
fn keyword_args_exceeding_u8_limit_returns_syntax_error() {
let code = generate_many_keyword_args(257);
let result = MontyRun::new(code, "test.py", vec![], CompileOptions::default());
assert_syntax_error(result, "more than 255 keyword arguments");
}
}
fn generate_comprehension_with_generators(count: usize) -> String {
let mut code = String::from("x = [0");
for i in 0..count {
write!(code, " for x{i} in [0]").unwrap();
}
code.push(']');
code
}
mod comprehension_generator_limits {
use super::*;
#[test]
fn small_comprehension_succeeds() {
let code = generate_comprehension_with_generators(50);
let result = MontyRun::new(code, "test.py", vec![], CompileOptions::default());
assert!(
result.is_ok(),
"50 comprehension generators should compile successfully"
);
}
#[test]
fn generators_exceeding_max_returns_syntax_error() {
let code = generate_comprehension_with_generators(256);
let result = MontyRun::new(code, "test.py", vec![], CompileOptions::default());
assert_syntax_error(result, "comprehension has too many nested clauses (256)");
}
#[test]
fn many_generators_returns_syntax_error_not_stack_overflow() {
let code = generate_comprehension_with_generators(5000);
let result = MontyRun::new(code, "test.py", vec![], CompileOptions::default());
assert_syntax_error(result, "comprehension has too many nested clauses (5000)");
}
}
mod function_parameter_limits {
use super::*;
#[test]
fn parameters_under_u8_limit_succeeds() {
let code = generate_many_parameters(255);
let result = MontyRun::new(code, "test.py", vec![], CompileOptions::default());
assert!(result.is_ok(), "255 parameters should compile successfully");
let run = result.unwrap();
let result = run.run_no_limits(vec![]);
assert!(result.is_ok(), "255 parameters should run successfully");
}
#[test]
fn parameters_at_u8_boundary_returns_syntax_error_for_call() {
let code = generate_many_parameters(256);
let result = MontyRun::new(code, "test.py", vec![], CompileOptions::default());
assert_syntax_error(result, "more than 255 positional arguments");
}
#[test]
fn parameters_exceeding_u8_limit_returns_syntax_error_for_call() {
let code = generate_many_parameters(257);
let result = MontyRun::new(code, "test.py", vec![], CompileOptions::default());
assert_syntax_error(result, "more than 255 positional arguments");
}
}
fn generate_many_class_members(count: usize) -> String {
let mut code = String::from("class C:\n");
for i in 0..count {
writeln!(code, " a{i} = {i}").unwrap();
}
writeln!(code, "assert C.a{} == {}", count - 1, count - 1).unwrap();
code
}
fn generate_large_dict_literal(count: usize) -> String {
let mut code = String::from("x = {");
for i in 0..count {
if i > 0 {
code.push_str(", ");
}
write!(code, "{i}: {i}").unwrap();
}
code.push('}');
writeln!(code, "\nassert x[{}] == {}", count - 1, count - 1).unwrap();
code
}
fn generate_many_finally_return_sites(count: usize) -> String {
let mut code = String::from("def f(x):\n try:\n");
for i in 0..count {
writeln!(code, " if x == {i}:\n return {i}").unwrap();
}
code.push_str(" finally:\n x = 0\n");
code
}
fn generate_nested_finally_suites(depth: usize) -> String {
let mut code = String::from("def f():\n try:\n return 1\n finally:\n");
for level in 0..depth {
let indent = " ".repeat(level + 2);
writeln!(code, "{indent}try:\n{indent} pass\n{indent}finally:").unwrap();
}
writeln!(code, "{}pass\n\nassert f() == 1", " ".repeat(depth + 2)).unwrap();
code
}
mod stack_effect_limits {
use super::*;
#[test]
fn class_members_above_i16_stack_effect() {
let code = generate_many_class_members(16384);
let run = MontyRun::new(code, "test.py", vec![], CompileOptions::default())
.expect("16384 class members should compile");
let result = run.run_no_limits(vec![]);
assert!(result.is_ok(), "16384 class members should run: {result:?}");
}
#[test]
fn dict_literal_above_i16_stack_effect() {
let code = generate_large_dict_literal(20000);
let run = MontyRun::new(code, "test.py", vec![], CompileOptions::default())
.expect("20000-entry dict literal should compile");
let result = run.run_no_limits(vec![]);
assert!(result.is_ok(), "20000-entry dict literal should run: {result:?}");
}
}
mod finally_copy_limits {
use super::*;
#[test]
fn excessive_inline_finally_copies_return_syntax_error() {
let code = generate_many_finally_return_sites(1023);
let result = MontyRun::new(code, "test.py", vec![], CompileOptions::default());
assert_syntax_error(result, "too many inline finally copies; maximum is 1024");
}
#[test]
fn max_inline_finally_copies_compile_and_run() {
let code = generate_many_finally_return_sites(1022);
let run = MontyRun::new(code, "test.py", vec![], CompileOptions::default())
.expect("1024 inline finally copies should compile");
let result = run.run_no_limits(vec![]);
assert!(result.is_ok(), "1024 inline finally copies should run: {result:?}");
}
#[test]
fn nested_finally_amplification_returns_syntax_error() {
let code = generate_nested_finally_suites(10);
let result = MontyRun::new(code, "test.py", vec![], CompileOptions::default());
assert_syntax_error(result, "too many inline finally copies; maximum is 1024");
}
#[test]
fn nested_finally_amplification_below_limit_runs() {
let code = generate_nested_finally_suites(9);
let run = MontyRun::new(code, "test.py", vec![], CompileOptions::default())
.expect("nested finally expansion below the copy limit should compile");
let result = run.run_no_limits(vec![]);
assert!(result.is_ok(), "nested finally expansion should run: {result:?}");
}
}