echoes 0.10.2

Find duplicated Rust doctests, functions and contexts
Documentation
//! Value grouping membership, canonical sizes and thresholds.

#[path = "support/dependency.rs"]
mod support;

#[path = "support/fixture_group.rs"]
mod fixture_group;
#[path = "support/function_ids.rs"]
mod functions;
#[path = "support/group_members.rs"]
mod group_members;
#[path = "support/public_sites.rs"]
mod public_sites;
#[path = "support/same.rs"]
mod same;
#[path = "support/site_src.rs"]
mod site_src;

use fixture_group::assert_fixture_group;
use functions::function_ids;
use group_members::members;
use public_sites::assert_public_sites;
use same::same;
use site_src::site;

use echoes::{DocTest, Echoes, SourceFile, TargetScan, group};

#[test]
fn value_grouping_keeps_exact_passing_members_and_excludes_failing_variants() {
    for case in support::cases().iter().filter(|case| {
        case.family == "temporary"
            && case.expected == "same"
            && [
                "A07_copy_alias",
                "A08_inline_temporary",
                "A10_tuple_destructure",
                "A18_dead_primitive",
            ]
            .contains(&case.id.as_str())
    }) {
        assert_fixture_group(case);
    }
}

#[test]
fn value_threshold_uses_one_canonical_size_in_both_site_orders() {
    for (left, right) in [
        (
            "fn f(input: u32) -> u32 { let mask = input & 15u32; let copied = mask; copied ^ 3u32 }",
            "fn g(input: u32) -> u32 { let mask = input & 15u32; mask ^ 3u32 }",
        ),
        (
            "fn f(input: u32) -> u32 { let mask = input & 15u32; mask ^ 3u32 }",
            "fn g(input: u32) -> u32 { (input & 15u32) ^ 3u32 }",
        ),
    ] {
        let a =
            syn_canon::canonicalize(syn::parse_str(&format!("fn main() {{ {left} }}")).unwrap());
        let b =
            syn_canon::canonicalize(syn::parse_str(&format!("fn main() {{ {right} }}")).unwrap());
        assert_eq!(a, b);
        assert_eq!(a.leaf_tokens(), 15);
        assert_eq!(b.leaf_tokens(), 15);
        assert_eq!(a.body_units(), 12);
        assert_eq!(b.body_units(), 12);
        let sites = [site("first", left, &[]), site("second", right, &[])];
        let reversed = [site("second", right, &[]), site("first", left, &[])];
        let included = group(&sites, 2, 15);
        let reversed_report = group(&reversed, 2, 15);
        assert_eq!(members(&included.groups), [vec!["first", "second"]]);
        assert_eq!(members(&reversed_report.groups), members(&included.groups));
        assert_eq!(included.groups[0].id, reversed_report.groups[0].id);
        assert_eq!(included.groups[0].tokens, 15);
        assert_eq!(group(&sites, 2, 16).groups.len(), 0);
    }
}

#[test]
fn value_composition_erases_single_and_multiple_use_scalars() {
    same(
        "fn f(input: u32) -> (u32, u32) { let base = input & 15u32; let shifted = base >> 2u32; let widened = base | 4u32; let copied = shifted; (widened, copied ^ 3u32) }",
        "fn g(input: u32) -> (u32, u32) { ((input & 15u32) | 4u32, ((input & 15u32) >> 2u32) ^ 3u32) }",
    );
}

#[test]
fn value_composition_destructures_tuples_and_deletes_dead_bindings() {
    same(
        "fn f(input: u32) -> (u32, u32) { let (low, high) = (input & 15u32, input >> 4u32); let dead = 21u32; (low ^ high, low) }",
        "fn g(input: u32) -> (u32, u32) { ((input & 15u32) ^ (input >> 4u32), input & 15u32) }",
    );
}

#[test]
fn value_composition_composes_constant_boolean_and_idempotence_laws() {
    same(
        "fn f(input: u32, gate: bool) -> (u32, bool) { let low = input & 15u32; let summed = 2u32 + 3u32; let flagged = if gate { true } else { false }; (low ^ summed, flagged) }",
        "fn g(input: u32, gate: bool) -> (u32, bool) { ((input & 15u32) ^ 5u32, gate) }",
    );
    same(
        "fn f(input: u32) -> u32 { let high = input >> 4u32; let low = input & 15u32; let masked = low & low; masked ^ 3u32 }",
        "fn g(input: u32) -> u32 { (input & 15u32) ^ 3u32 }",
    );
}

fn target(text: &str) -> TargetScan {
    TargetScan {
        name: "value".to_owned(),
        files: vec![SourceFile {
            path: "src/lib.rs".to_owned(),
            segments: Vec::new(),
            cfg: Vec::new(),
            parsed: syn::parse_file(text).unwrap(),
            text: text.to_owned(),
            rustdoc: true,
        }],
        library: true,
    }
}

#[test]
fn value_cli_fixture_preserves_exact_function_and_doctest_members() {
    let text = include_str!("fixtures/valuews/src/lib.rs");
    let target = target(text);
    for threshold in [0, 13] {
        let report = Echoes::default()
            .functions()
            .fn_min_tokens(threshold)
            .run_targets("", std::slice::from_ref(&target), &|_, _| None);
        let function_groups: Vec<_> = report
            .groups
            .iter()
            .filter(|group| group.kind == echoes::Kind::Function)
            .collect();
        let function_members = members(function_groups.iter().copied());
        assert_eq!(
            function_members,
            [
                vec!["value::Owner::first", "value::Owner::same"],
                vec!["value::left::first", "value::left::same"],
                vec!["value::right::first", "value::right::same"],
            ]
        );
        assert!(function_groups.iter().all(|group| group.tokens == 13));
        assert_public_sites(&function_groups);
        let doctest_groups: Vec<_> = report
            .groups
            .iter()
            .filter(|group| group.kind == echoes::Kind::Doctest)
            .collect();
        assert_eq!(doctest_groups.len(), 1);
        let sites = &doctest_groups[0].sites;
        assert_eq!(sites.len(), 2);
        assert!(
            sites.iter().all(|site| site.item == "value"
                && site.info.is_empty()
                && site.file == "src/lib.rs")
        );
        assert_ne!(sites[0].line, sites[1].line);
        assert_eq!(doctest_groups[0].tokens, 14);
        let grouped: Vec<&str> = report
            .groups
            .iter()
            .flat_map(|group| group.sites.iter())
            .map(|site| site.item.as_str())
            .collect();
        assert!(!grouped.iter().any(|item| item.contains("different")));
        assert_eq!(grouped.iter().filter(|item| **item == "value").count(), 2);
    }
}

#[test]
fn value_cli_fixture_boundaries_and_reversed_sites() {
    let text = include_str!("fixtures/valuews/src/lib.rs");
    let mut target = target(text);
    let scan = |floor: usize| {
        Echoes::default()
            .functions()
            .fn_min_tokens(floor)
            .run_targets("", std::slice::from_ref(&target), &|_, _| None)
    };
    let base = scan(0);
    let function_groups: Vec<_> = base
        .groups
        .iter()
        .filter(|group| group.kind == echoes::Kind::Function)
        .collect();
    assert_eq!(function_groups.len(), 3);
    for group in &function_groups {
        let included = scan(13);
        let excluded = scan(14);
        assert!(included.groups.iter().any(|member| member.id == group.id));
        assert!(!excluded.groups.iter().any(|member| member.id == group.id));
    }
    target.files[0].parsed.items.reverse();
    let reversed_scan = Echoes::default().functions().fn_min_tokens(0).run_targets(
        "",
        std::slice::from_ref(&target),
        &|_, _| None,
    );
    assert_eq!(function_ids(&base), function_ids(&reversed_scan));
    let doctest_sites: Vec<DocTest> = base
        .groups
        .iter()
        .filter(|group| group.kind == echoes::Kind::Doctest)
        .flat_map(|group| group.sites.iter().cloned())
        .collect();
    assert_eq!(doctest_sites.len(), 2);
    let reversed_sites: Vec<DocTest> = doctest_sites.iter().rev().cloned().collect();
    let included = group(&doctest_sites, 1, 0);
    let reversed_doctests = group(&reversed_sites, 1, 0);
    assert_eq!(included.groups.len(), 1);
    assert_eq!(
        members(&included.groups),
        members(&reversed_doctests.groups)
    );
    assert_eq!(included.groups[0].id, reversed_doctests.groups[0].id);
}