use super::*;
use assura_parser::ast::Spanned;
#[test]
fn exhaustive_all_variants_covered() {
let variants = vec!["Red".into(), "Green".into(), "Blue".into()];
let patterns = vec![
Pattern::Variant("Red".into()),
Pattern::Variant("Green".into()),
Pattern::Variant("Blue".into()),
];
assert_eq!(check_exhaustiveness(&patterns, &variants), None);
}
#[test]
fn exhaustive_wildcard_covers_all() {
let variants = vec!["Red".into(), "Green".into(), "Blue".into()];
let patterns = vec![Pattern::Wildcard];
assert_eq!(check_exhaustiveness(&patterns, &variants), None);
}
#[test]
fn exhaustive_wildcard_with_explicit() {
let variants = vec!["Red".into(), "Green".into(), "Blue".into()];
let patterns = vec![Pattern::Variant("Red".into()), Pattern::Wildcard];
assert_eq!(check_exhaustiveness(&patterns, &variants), None);
}
#[test]
fn non_exhaustive_missing_one() {
let variants = vec!["Red".into(), "Green".into(), "Blue".into()];
let patterns = vec![
Pattern::Variant("Red".into()),
Pattern::Variant("Green".into()),
];
let missing = check_exhaustiveness(&patterns, &variants);
assert_eq!(missing, Some(vec!["Blue".into()]));
}
#[test]
fn non_exhaustive_missing_multiple() {
let variants = vec!["Red".into(), "Green".into(), "Blue".into(), "Yellow".into()];
let patterns = vec![Pattern::Variant("Green".into())];
let missing = check_exhaustiveness(&patterns, &variants).unwrap();
assert_eq!(missing, vec!["Red", "Blue", "Yellow"]);
}
#[test]
fn non_exhaustive_empty_patterns() {
let variants = vec!["A".into(), "B".into(), "C".into()];
let patterns: Vec<Pattern> = vec![];
let missing = check_exhaustiveness(&patterns, &variants).unwrap();
assert_eq!(missing, vec!["A", "B", "C"]);
}
#[test]
fn exhaustive_empty_enum() {
let variants: Vec<String> = vec![];
let patterns: Vec<Pattern> = vec![];
assert_eq!(check_exhaustiveness(&patterns, &variants), None);
}
#[test]
fn exhaustive_duplicate_patterns_ignored() {
let variants = vec!["X".into(), "Y".into()];
let patterns = vec![
Pattern::Variant("X".into()),
Pattern::Variant("X".into()),
Pattern::Variant("Y".into()),
];
assert_eq!(check_exhaustiveness(&patterns, &variants), None);
}
#[test]
fn non_exhaustive_literal_does_not_cover_variant() {
let variants = vec!["Red".into(), "Green".into()];
let patterns = vec![
Pattern::Variant("Red".into()),
Pattern::Literal(AstLit::Int("42".into())),
];
let missing = check_exhaustiveness(&patterns, &variants).unwrap();
assert_eq!(missing, vec!["Green"]);
}
#[test]
fn exhaustive_single_variant_enum() {
let variants = vec!["Only".into()];
let patterns = vec![Pattern::Variant("Only".into())];
assert_eq!(check_exhaustiveness(&patterns, &variants), None);
}
#[test]
fn non_exhaustive_preserves_declaration_order() {
let variants = vec![
"Alpha".into(),
"Beta".into(),
"Gamma".into(),
"Delta".into(),
"Epsilon".into(),
];
let patterns = vec![
Pattern::Variant("Beta".into()),
Pattern::Variant("Delta".into()),
];
let missing = check_exhaustiveness(&patterns, &variants).unwrap();
assert_eq!(missing, vec!["Alpha", "Gamma", "Epsilon"]);
}
use assura_parser::ast::ClauseKind as AstClauseKind;
#[test]
fn clause_requires_bool_body_ok() {
let env = TypeEnv::new();
let body = Spanned::no_span(AstExpr::Literal(AstLit::Bool(true)));
let mut errors = Vec::new();
check_clause_expr(&AstClauseKind::Requires, &body, &env, &mut errors, &(0..0));
assert!(errors.is_empty());
}
#[test]
fn clause_requires_int_body_error() {
let env = TypeEnv::new();
let body = Spanned::no_span(AstExpr::Literal(AstLit::Int("42".into())));
let mut errors = Vec::new();
check_clause_expr(&AstClauseKind::Requires, &body, &env, &mut errors, &(0..0));
assert_eq!(errors.len(), 1);
assert_eq!(errors[0].code, "A03006");
assert!(errors[0].message.contains("requires"));
assert!(errors[0].message.contains("Bool"));
assert!(errors[0].message.contains("Int"));
}
#[test]
fn clause_ensures_bool_body_ok() {
let env = TypeEnv::new();
let body = Spanned::no_span(AstExpr::Literal(AstLit::Bool(false)));
let mut errors = Vec::new();
check_clause_expr(&AstClauseKind::Ensures, &body, &env, &mut errors, &(0..0));
assert!(errors.is_empty());
}
#[test]
fn clause_ensures_string_body_error() {
let env = TypeEnv::new();
let body = Spanned::no_span(AstExpr::Literal(AstLit::Str("hello".into())));
let mut errors = Vec::new();
check_clause_expr(&AstClauseKind::Ensures, &body, &env, &mut errors, &(0..0));
assert_eq!(errors.len(), 1);
assert_eq!(errors[0].code, "A03006");
assert!(errors[0].message.contains("ensures"));
}
#[test]
fn clause_invariant_bool_body_ok() {
let env = TypeEnv::new();
let body = Spanned::no_span(AstExpr::Literal(AstLit::Bool(true)));
let mut errors = Vec::new();
check_clause_expr(&AstClauseKind::Invariant, &body, &env, &mut errors, &(0..0));
assert!(errors.is_empty());
}
#[test]
fn clause_invariant_float_body_error() {
let env = TypeEnv::new();
let body = Spanned::no_span(AstExpr::Literal(AstLit::Float("3.14".into())));
let mut errors = Vec::new();
check_clause_expr(&AstClauseKind::Invariant, &body, &env, &mut errors, &(0..0));
assert_eq!(errors.len(), 1);
assert_eq!(errors[0].code, "A03006");
assert!(errors[0].message.contains("invariant"));
}
#[test]
fn clause_rule_bool_body_ok() {
let env = TypeEnv::new();
let body = Spanned::no_span(AstExpr::BinOp {
lhs: Box::new(Spanned::no_span(AstExpr::Literal(AstLit::Bool(true)))),
op: AstBinOp::And,
rhs: Box::new(Spanned::no_span(AstExpr::Literal(AstLit::Bool(false)))),
});
let mut errors = Vec::new();
check_clause_expr(&AstClauseKind::Rule, &body, &env, &mut errors, &(0..0));
assert!(errors.is_empty());
}
#[test]
fn clause_rule_int_body_error() {
let env = TypeEnv::new();
let body = Spanned::no_span(AstExpr::Literal(AstLit::Int("99".into())));
let mut errors = Vec::new();
check_clause_expr(&AstClauseKind::Rule, &body, &env, &mut errors, &(0..0));
assert_eq!(errors.len(), 1);
assert_eq!(errors[0].code, "A03006");
assert!(errors[0].message.contains("rule"));
}
#[test]
fn clause_effects_any_body_ok() {
let env = TypeEnv::new();
let body = Spanned::no_span(AstExpr::Ident("pure".into()));
let mut errors = Vec::new();
check_clause_expr(&AstClauseKind::Effects, &body, &env, &mut errors, &(0..0));
assert!(errors.is_empty());
}
#[test]
fn clause_modifies_any_body_ok() {
let env = TypeEnv::new();
let body = Spanned::no_span(AstExpr::Ident("buffer".into()));
let mut errors = Vec::new();
check_clause_expr(&AstClauseKind::Modifies, &body, &env, &mut errors, &(0..0));
assert!(errors.is_empty());
}
#[test]
fn clause_unknown_body_no_error() {
let env = TypeEnv::new();
let body = Spanned::no_span(AstExpr::Ident("unknown_predicate".into()));
let mut errors = Vec::new();
check_clause_expr(&AstClauseKind::Requires, &body, &env, &mut errors, &(0..0));
assert!(errors.is_empty());
}
#[test]
fn clause_comparison_in_requires_ok() {
let mut env = TypeEnv::new();
env.insert("x".into(), Type::Int);
let body = Spanned::no_span(AstExpr::BinOp {
lhs: Box::new(Spanned::no_span(AstExpr::Ident("x".into()))),
op: AstBinOp::Gt,
rhs: Box::new(Spanned::no_span(AstExpr::Literal(AstLit::Int("0".into())))),
});
let mut errors = Vec::new();
check_clause_expr(&AstClauseKind::Requires, &body, &env, &mut errors, &(0..0));
assert!(errors.is_empty());
}
#[test]
fn clause_requires_int_body_integration() {
let src = r#"
contract Bad {
requires { 42 }
}
"#;
let resolved = resolve_ok(src);
let result = type_check(resolved);
assert!(result.is_err());
let errors = result.unwrap_err();
assert!(errors.iter().any(|e| e.code == "A03006"));
}
#[test]
fn clause_requires_bool_integration() {
let src = r#"
contract Good {
requires { true }
}
"#;
let resolved = resolve_ok(src);
type_check(resolved).expect("should type-check successfully");
}
#[test]
fn demo_files_type_check() {
for path in [
"demos/libwebp-huffman.assura",
"demos/zlib-inflate.assura",
"demos/mbedtls-x509.assura",
"tests/fixtures/test_basic.assura",
] {
let full = format!(
"{}/{}",
env!("CARGO_MANIFEST_DIR")
.strip_suffix("/crates/assura-types")
.unwrap_or(env!("CARGO_MANIFEST_DIR")),
path
);
let content = match std::fs::read_to_string(&full) {
Ok(c) => c,
Err(_) => {
let alt = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.parent()
.and_then(|p| p.parent())
.unwrap()
.join(path);
std::fs::read_to_string(alt).unwrap_or_else(|e| panic!("cannot read {path}: {e}"))
}
};
let (file, parse_errs) = assura_parser::parse(&content);
assert!(
parse_errs.is_empty(),
"{path}: unexpected parse errors: {parse_errs:?}"
);
let file = file.unwrap_or_else(|| panic!("{path}: parse returned None"));
let resolved = assura_resolve::resolve(&file)
.unwrap_or_else(|e| panic!("{path}: resolve errors: {e:?}"));
type_check(resolved).unwrap_or_else(|e| panic!("{path}: type_check errors: {e:?}"));
}
}