use super::*;
fn parse_source(src: &str) -> assura_parser::ast::SourceFile {
let (sf, errs) = assura_parser::parse(src);
assert!(errs.is_empty(), "parse errors: {errs:?}");
sf.unwrap()
}
#[test]
fn numerical_precision_no_annotation_produces_no_errors() {
let src = r#"contract Simple { requires { true } }"#;
let sf = parse_source(src);
let errors = run_numerical_precision_checks(&sf);
assert!(
errors.is_empty(),
"no precision annotation should produce no errors: {errors:?}"
);
}
#[test]
fn numerical_precision_cancellation_detected() {
let src = r#"contract Compute { precision x ensures { x > 0 } }"#;
let sf = parse_source(src);
let errors = run_numerical_precision_checks(&sf);
assert!(
errors.iter().any(|e| e.code == "A42003"),
"expected A42003 for catastrophic cancellation, got: {errors:?}"
);
}
#[test]
fn precomputed_table_no_annotation_produces_no_errors() {
let src = r#"contract Simple { requires { true } }"#;
let sf = parse_source(src);
let errors = run_precomputed_table_checks(&sf);
assert!(
errors.is_empty(),
"no precomputed_table annotation should produce no errors: {errors:?}"
);
}
#[test]
fn precomputed_table_no_generator_detected() {
let src = r#"contract Lookup { precomputed_table crc_table }"#;
let sf = parse_source(src);
let errors = run_precomputed_table_checks(&sf);
assert!(
errors.iter().any(|e| e.code == "A43002"),
"expected A43002 for table without generator function, got: {errors:?}"
);
}
#[test]
fn precomputed_table_also_flags_coverage() {
let src = r#"contract Lookup { precomputed_table crc_table }"#;
let sf = parse_source(src);
let errors = run_precomputed_table_checks(&sf);
assert!(
errors.iter().any(|e| e.code == "A43001"),
"expected A43001 for incomplete table coverage, got: {errors:?}"
);
}
#[test]
fn collection_no_known_operation_produces_no_errors() {
let src = r#"contract Unrelated { requires { true } ensures { true } }"#;
let sf = parse_source(src);
let errors = run_collection_contract_checks(&sf);
assert!(
errors.is_empty(),
"non-collection contract should produce no errors: {errors:?}"
);
}
#[test]
fn collection_sort_without_len_postcondition_detected() {
let src = r#"
contract Sort {
requires { true }
ensures { true }
}
"#;
let sf = parse_source(src);
let errors = run_collection_contract_checks(&sf);
assert!(
errors.iter().any(|e| e.code == "A03007"),
"sort without len postcondition should produce A03007: {errors:?}"
);
}
#[test]
fn collection_sort_with_len_postcondition_no_error() {
let src = r#"
contract Sort {
input(items: List<Int>)
ensures { len(items) == len(items) }
}
"#;
let sf = parse_source(src);
let errors = run_collection_contract_checks(&sf);
assert!(
!errors.iter().any(|e| e.code == "A03007"),
"sort with len postcondition should not produce A03007: {errors:?}"
);
}
#[test]
fn fixed_width_no_fw_params_produces_no_errors() {
let src = r#"contract Simple { requires { true } }"#;
let sf = parse_source(src);
let env = TypeEnv::new();
let errors = run_fixed_width_checks(&sf, &env);
assert!(
errors.is_empty(),
"contract without fixed-width params should produce no errors: {errors:?}"
);
}
#[test]
fn fixed_width_overflow_on_u8_addition() {
let src = r#"
extern fn add_bytes(a: U8, b: U8) -> U8
ensures { a + b > 0 }
"#;
let sf = parse_source(src);
let env = TypeEnv::new();
let errors = run_fixed_width_checks(&sf, &env);
assert!(
errors.iter().any(|e| e.code == "A10101"),
"U8 + U8 should flag potential overflow A10101: {errors:?}"
);
}
#[test]
fn precomputed_table_domain_size_nonstandard_flagged() {
let mut checker = PrecomputedTableChecker::new();
checker.declare_table("odd_table".into(), 100, "gen_fn".into(), 0..10);
let errors = checker.check_domain_size(None);
assert_eq!(errors.len(), 1);
assert_eq!(errors[0].code, "A43005");
assert!(
errors[0].message.contains("100") && errors[0].message.contains("domain"),
"A43005 should mention non-standard domain size 100, got: {}",
errors[0].message
);
}
#[test]
fn precomputed_table_domain_size_standard_ok() {
for size in [16, 128, 256, 65536] {
let mut checker = PrecomputedTableChecker::new();
checker.declare_table("std_table".into(), size, "gen_fn".into(), 0..10);
let errors = checker.check_domain_size(None);
assert!(
errors.is_empty(),
"standard size {size} should not trigger A43005: {errors:?}"
);
}
}
#[test]
fn precomputed_table_domain_size_explicit_mismatch() {
let mut checker = PrecomputedTableChecker::new();
checker.declare_table("crc_table".into(), 100, "compute_crc".into(), 0..10);
let errors = checker.check_domain_size(Some(256));
assert_eq!(errors.len(), 1);
assert_eq!(errors[0].code, "A43005");
assert!(
errors[0].message.contains("256") && errors[0].message.contains("domain"),
"A43005 should mention expected domain size 256, got: {}",
errors[0].message
);
}
#[test]
fn precomputed_table_domain_size_explicit_match() {
let mut checker = PrecomputedTableChecker::new();
checker.declare_table("byte_table".into(), 256, "gen".into(), 0..10);
let errors = checker.check_domain_size(Some(256));
assert!(errors.is_empty());
}
#[test]
fn smt_obligations_empty_for_no_generator() {
let mut checker = PrecomputedTableChecker::new();
checker.declare_table("bare_table".into(), 256, String::new(), 0..10);
let obs = checker.smt_obligations();
assert!(
obs.is_empty(),
"table without generator should have no SMT obligation"
);
}
#[test]
fn smt_obligations_returns_obligation_with_generator() {
let mut checker = PrecomputedTableChecker::new();
checker.declare_table("crc32_table".into(), 256, "compute_crc32".into(), 0..10);
let obs = checker.smt_obligations();
assert_eq!(obs.len(), 1);
assert_eq!(obs[0].table_name, "crc32_table");
assert_eq!(obs[0].generator_fn, "compute_crc32");
assert_eq!(obs[0].domain_size, 256);
}
#[test]
fn smt_obligations_skips_zero_size_tables() {
let mut checker = PrecomputedTableChecker::new();
checker.declare_table("empty".into(), 0, "gen".into(), 0..5);
let obs = checker.smt_obligations();
assert!(
obs.is_empty(),
"zero-size table should produce no obligation"
);
}
#[test]
fn collect_table_smt_obligations_no_tables() {
let src = r#"contract Simple { requires { true } }"#;
let sf = parse_source(src);
let obs = collect_table_smt_obligations(&sf);
assert!(obs.is_empty());
}
#[test]
fn collect_table_smt_obligations_with_bare_table() {
let src = r#"contract Lookup { precomputed_table crc_table }"#;
let sf = parse_source(src);
let obs = collect_table_smt_obligations(&sf);
assert!(
obs.is_empty(),
"bare table without generator should have no SMT obligation"
);
}
#[test]
fn precomputed_table_checks_include_domain_size_error() {
let src = r#"contract Lookup { precomputed_table crc_table }"#;
let sf = parse_source(src);
let errors = run_precomputed_table_checks(&sf);
assert!(errors.iter().any(|e| e.code == "A43001"));
assert!(errors.iter().any(|e| e.code == "A43002"));
assert!(
!errors.iter().any(|e| e.code == "A43005"),
"256 is standard, should not get A43005"
);
}