semdiff-core 0.1.1

Core traversal, diff calculation, and reporting traits for semdiff.
Documentation
use super::*;
use std::convert::Infallible;
use std::sync::{Arc, Mutex};

#[derive(Debug, Clone)]
struct TestLeaf {
    name: String,
    value: i32,
}

impl TestLeaf {
    fn new(name: &str, value: i32) -> Self {
        Self {
            name: name.to_owned(),
            value,
        }
    }
}

impl LeafTraverse for TestLeaf {
    fn name(&self) -> &str {
        &self.name
    }
}

#[derive(Debug, Clone)]
struct TestNode {
    name: String,
    children: Vec<TestChild>,
}

#[derive(Debug, Clone)]
enum TestChild {
    Node(TestNode),
    Leaf(TestLeaf),
}

impl TestNode {
    fn new(name: &str, children: Vec<TestChild>) -> Self {
        Self {
            name: name.to_owned(),
            children,
        }
    }
}

impl NodeTraverse for TestNode {
    type Leaf = TestLeaf;
    type TraverseError = Infallible;

    fn name(&self) -> &str {
        &self.name
    }

    fn children(
        &mut self,
    ) -> Result<impl Iterator<Item = Result<TraversalNode<Self, Self::Leaf>, Self::TraverseError>>, Self::TraverseError>
    {
        let mut children = Vec::with_capacity(self.children.len());
        for child in &self.children {
            match child {
                TestChild::Node(node) => children.push(TraversalNode::Node(node.clone())),
                TestChild::Leaf(leaf) => children.push(TraversalNode::Leaf(leaf.clone())),
            }
        }
        Ok(children.into_iter().map(Ok))
    }
}

#[derive(Debug, PartialEq, Eq, Clone)]
enum ReportEvent {
    Start,
    Finish,
    Unchanged(String),
    Modified(String),
    Added(String),
    Deleted(String),
}

fn event_sort_key(event: &ReportEvent) -> (u8, String) {
    match event {
        ReportEvent::Unchanged(name) => (0, name.clone()),
        ReportEvent::Modified(name) => (1, name.clone()),
        ReportEvent::Added(name) => (2, name.clone()),
        ReportEvent::Deleted(name) => (3, name.clone()),
        ReportEvent::Start => (4, String::new()),
        ReportEvent::Finish => (5, String::new()),
    }
}

fn assert_events_unordered(events: Vec<ReportEvent>, expected: Vec<ReportEvent>) {
    assert!(events.len() >= 2);
    assert_eq!(events.first(), Some(&ReportEvent::Start));
    assert_eq!(events.last(), Some(&ReportEvent::Finish));

    let mut actual_events = events[1..events.len() - 1].to_vec();
    let mut expected_events = expected;
    actual_events.sort_by_key(event_sort_key);
    expected_events.sort_by_key(event_sort_key);
    assert_eq!(actual_events, expected_events);
}

#[derive(Clone, Default)]
struct TestReporter {
    events: Arc<Mutex<Vec<ReportEvent>>>,
}

impl Reporter for TestReporter {
    type Error = Infallible;

    fn start(&mut self) -> Result<(), Self::Error> {
        self.events.lock().unwrap().push(ReportEvent::Start);
        Ok(())
    }

    fn finish(self) -> Result<(), Self::Error> {
        self.events.lock().unwrap().push(ReportEvent::Finish);
        Ok(())
    }
}

#[derive(Clone, Default)]
struct TestDetailReporter {
    events: Arc<Mutex<Vec<ReportEvent>>>,
}

impl DetailReporter<TestDiff, TestLeaf, TestReporter> for TestDetailReporter {
    type Error = Infallible;

    fn report_unchanged(
        &self,
        name: &str,
        _diff: TestDiff,
        _reporter: &TestReporter,
    ) -> Result<MayUnsupported<()>, Self::Error> {
        self.events
            .lock()
            .unwrap()
            .push(ReportEvent::Unchanged(name.to_owned()));
        Ok(MayUnsupported::Ok(()))
    }

    fn report_modified(
        &self,
        name: &str,
        _diff: TestDiff,
        _reporter: &TestReporter,
    ) -> Result<MayUnsupported<()>, Self::Error> {
        self.events.lock().unwrap().push(ReportEvent::Modified(name.to_owned()));
        Ok(MayUnsupported::Ok(()))
    }

    fn report_added(
        &self,
        name: &str,
        _data: TestLeaf,
        _reporter: &TestReporter,
    ) -> Result<MayUnsupported<()>, Self::Error> {
        self.events.lock().unwrap().push(ReportEvent::Added(name.to_owned()));
        Ok(MayUnsupported::Ok(()))
    }

    fn report_deleted(
        &self,
        name: &str,
        _data: TestLeaf,
        _reporter: &TestReporter,
    ) -> Result<MayUnsupported<()>, Self::Error> {
        self.events.lock().unwrap().push(ReportEvent::Deleted(name.to_owned()));
        Ok(MayUnsupported::Ok(()))
    }
}

#[derive(Debug)]
struct TestDiff {
    equal: bool,
}

impl Diff for TestDiff {
    fn equal(&self) -> bool {
        self.equal
    }
}

#[derive(Debug)]
struct TestDiffCalculator;

impl DiffCalculator<TestLeaf> for TestDiffCalculator {
    type Error = Infallible;
    type Diff = TestDiff;

    fn diff(
        &self,
        _name: &str,
        expected: TestLeaf,
        actual: TestLeaf,
    ) -> Result<MayUnsupported<Self::Diff>, Self::Error> {
        Ok(MayUnsupported::Ok(TestDiff {
            equal: expected.value == actual.value,
        }))
    }
}

#[test]
fn traversal_node_ordering_and_eq() {
    let node_a = TraversalNode::Node(TestNode::new("a", vec![]));
    let node_a2 = TraversalNode::Node(TestNode::new("a", vec![]));
    let node_b = TraversalNode::Node(TestNode::new("b", vec![]));
    let leaf_a = TraversalNode::Leaf(TestLeaf::new("a", 1));
    let leaf_b = TraversalNode::Leaf(TestLeaf::new("b", 1));

    assert_eq!(node_a, node_a2);
    assert_ne!(node_a, node_b);
    assert_ne!(node_a, leaf_a);
    assert!(node_a < leaf_a);
    assert!(leaf_a < leaf_b);
}

#[test]
fn calc_diff_reports_expected_events() {
    let expected = TestNode::new(
        "root",
        vec![TestChild::Node(TestNode::new(
            "dir",
            vec![
                TestChild::Leaf(TestLeaf::new("same", 1)),
                TestChild::Leaf(TestLeaf::new("changed", 1)),
                TestChild::Leaf(TestLeaf::new("deleted", 1)),
            ],
        ))],
    );
    let actual = TestNode::new(
        "root",
        vec![TestChild::Node(TestNode::new(
            "dir",
            vec![
                TestChild::Leaf(TestLeaf::new("same", 1)),
                TestChild::Leaf(TestLeaf::new("changed", 2)),
                TestChild::Leaf(TestLeaf::new("added", 3)),
            ],
        ))],
    );

    let events = Arc::new(Mutex::new(Vec::new()));
    let reporter = TestReporter {
        events: Arc::clone(&events),
    };
    let diff = DiffAndReport::new(
        TestDiffCalculator,
        TestDetailReporter {
            events: Arc::clone(&events),
        },
    );

    let result = calc_diff(expected, actual, &[Box::new(diff)], reporter);
    assert!(result.is_ok());

    let events = events.lock().unwrap().clone();
    assert_events_unordered(
        events,
        vec![
            ReportEvent::Added("dir/added".to_owned()),
            ReportEvent::Modified("dir/changed".to_owned()),
            ReportEvent::Deleted("dir/deleted".to_owned()),
            ReportEvent::Unchanged("dir/same".to_owned()),
        ],
    );
}

#[test]
fn calc_diff_reports_expected_events_with_mixed_children_order() {
    let expected = TestNode::new(
        "root",
        vec![
            TestChild::Leaf(TestLeaf::new("root-leaf", 1)),
            TestChild::Node(TestNode::new(
                "dir",
                vec![
                    TestChild::Leaf(TestLeaf::new("same", 1)),
                    TestChild::Leaf(TestLeaf::new("changed", 1)),
                ],
            )),
            TestChild::Leaf(TestLeaf::new("removed", 1)),
        ],
    );
    let actual = TestNode::new(
        "root",
        vec![
            TestChild::Node(TestNode::new(
                "dir",
                vec![
                    TestChild::Leaf(TestLeaf::new("changed", 2)),
                    TestChild::Leaf(TestLeaf::new("same", 1)),
                    TestChild::Leaf(TestLeaf::new("added", 3)),
                ],
            )),
            TestChild::Leaf(TestLeaf::new("root-leaf", 1)),
            TestChild::Leaf(TestLeaf::new("added-root", 5)),
        ],
    );

    let events = Arc::new(Mutex::new(Vec::new()));
    let reporter = TestReporter {
        events: Arc::clone(&events),
    };
    let diff = DiffAndReport::new(
        TestDiffCalculator,
        TestDetailReporter {
            events: Arc::clone(&events),
        },
    );

    let result = calc_diff(expected, actual, &[Box::new(diff)], reporter);
    assert!(result.is_ok());

    let events = events.lock().unwrap().clone();
    assert_events_unordered(
        events,
        vec![
            ReportEvent::Added("dir/added".to_owned()),
            ReportEvent::Modified("dir/changed".to_owned()),
            ReportEvent::Unchanged("dir/same".to_owned()),
            ReportEvent::Added("added-root".to_owned()),
            ReportEvent::Deleted("removed".to_owned()),
            ReportEvent::Unchanged("root-leaf".to_owned()),
        ],
    );
}

#[test]
fn calc_diff_deletes_missing_node_children_in_mixed_order() {
    let expected = TestNode::new(
        "root",
        vec![
            TestChild::Leaf(TestLeaf::new("root-leaf", 1)),
            TestChild::Node(TestNode::new(
                "dir",
                vec![
                    TestChild::Leaf(TestLeaf::new("a", 1)),
                    TestChild::Node(TestNode::new("sub", vec![TestChild::Leaf(TestLeaf::new("b", 1))])),
                ],
            )),
        ],
    );
    let actual = TestNode::new("root", vec![TestChild::Leaf(TestLeaf::new("root-leaf", 1))]);

    let events = Arc::new(Mutex::new(Vec::new()));
    let reporter = TestReporter {
        events: Arc::clone(&events),
    };
    let diff = DiffAndReport::new(
        TestDiffCalculator,
        TestDetailReporter {
            events: Arc::clone(&events),
        },
    );

    let result = calc_diff(expected, actual, &[Box::new(diff)], reporter);
    assert!(result.is_ok());

    let events = events.lock().unwrap().clone();
    assert_events_unordered(
        events,
        vec![
            ReportEvent::Deleted("dir/a".to_owned()),
            ReportEvent::Deleted("dir/sub/b".to_owned()),
            ReportEvent::Unchanged("root-leaf".to_owned()),
        ],
    );
}