selfware 0.6.0

Your personal AI workshop — software you own, software that lasts
Documentation
use selfware::evolve::ast::AstAnalyzer;

#[test]
fn test_ast_analyzer_parses_rust_file() {
    let analyzer = AstAnalyzer::new();
    let ast = analyzer.parse_file("src/lib.rs").unwrap();
    assert_eq!(ast.kind, "source_file");
    assert_eq!(ast.start_byte, 0);
    assert!(ast.end_byte > 0);
    assert!(!ast.children.is_empty());
}

#[test]
fn test_ast_analyzer_finds_function_items() {
    let analyzer = AstAnalyzer::new();
    let ast = analyzer.parse_file("src/evolve/mod.rs").unwrap();
    let has_fn = ast.children.iter().any(|c| c.kind == "function_item");
    assert!(has_fn);
}

#[test]
fn test_ast_analyzer_missing_file_errors() {
    let analyzer = AstAnalyzer::new();
    assert!(analyzer.parse_file("src/does_not_exist.rs").is_err());
}

#[test]
fn test_cfg_test_ranges_finds_top_level_test_module() {
    let source = r#"fn production() {}

#[cfg(test)]
mod tests {
    #[test]
    fn smoke() {}
}

fn after() {}
"#;

    let ranges = AstAnalyzer::new().cfg_test_ranges(source).unwrap();

    assert_eq!(ranges, vec![(3, 7)]);
}

#[test]
fn test_cfg_test_ranges_recurses_into_modules_and_impls() {
    let source = r#"mod nested {
    struct Subject;

    impl Subject {
        fn production(&self) {}

        #[cfg(test)]
        fn test_helper(&self) {
            assert!(true);
        }
    }

    #[cfg(test)]
    fn module_test_helper() {}
}
"#;

    let ranges = AstAnalyzer::new().cfg_test_ranges(source).unwrap();

    assert_eq!(ranges, vec![(7, 10), (13, 14)]);
}

#[test]
fn test_cfg_test_ranges_keeps_attached_attributes_and_comments() {
    let source = r#"fn before() {}

#[allow(dead_code)]
// The test configuration applies to the next item.
#[cfg(
    test
)]
/* Keep this explanation with the helper. */
#[inline]
fn helper() {
    assert!(true);
}

fn after() {}
"#;

    let ranges = AstAnalyzer::new().cfg_test_ranges(source).unwrap();

    assert_eq!(ranges, vec![(3, 12)]);
}

#[test]
fn test_cfg_test_ranges_accepts_only_predicates_that_require_test() {
    let source = r#"#[cfg(any(test, feature = "slow"))]
fn any_predicate() {}

#[cfg(all(test, feature = "slow"))]
fn all_predicate() {}

#[cfg(all(any(test, feature = "slow"), unix))]
fn nested_non_exclusive_predicate() {}

#[cfg_attr(test, allow(dead_code))]
fn conditional_attribute() {}

#[cfg(feature = "slow")]
fn feature_only() {}
"#;

    let ranges = AstAnalyzer::new().cfg_test_ranges(source).unwrap();

    assert_eq!(ranges, vec![(4, 5)]);
}

#[test]
fn test_cfg_test_ranges_are_disjoint_when_outer_item_is_test_only() {
    let source = r#"#[cfg(test)]
mod tests {
    #[cfg(test)]
    fn nested() {}
}
"#;

    let ranges = AstAnalyzer::new().cfg_test_ranges(source).unwrap();

    assert_eq!(ranges, vec![(1, 5)]);
}

#[test]
fn test_cfg_test_body_ranges_do_not_count_external_test_module_links() {
    let source = r#"#[cfg(test)]
#[path = "subject_test.rs"]
mod tests;

#[cfg(test)]
fn inline_helper() {}
"#;

    let analyzer = AstAnalyzer::new();
    assert_eq!(
        analyzer.cfg_test_ranges(source).unwrap(),
        vec![(1, 3), (5, 6)]
    );
    assert_eq!(analyzer.cfg_test_body_ranges(source).unwrap(), vec![(5, 6)]);
}