diamond_types/list/
check.rs

1use jumprope::JumpRope;
2use crate::list::{Branch, ListCRDT, OpLog};
3use smallvec::smallvec;
4use crate::frontier::{advance_frontier_by_known_run, clone_smallvec, debug_assert_frontier_sorted};
5use crate::history::History;
6use crate::LocalVersion;
7
8/// This file contains debugging assertions to validate the document's internal state.
9///
10/// This is used during fuzzing to make sure everything is working properly, and if not, find bugs
11/// as early as possible.
12
13impl Branch {
14    #[allow(unused)]
15    pub fn dbg_assert_content_eq_rope(&self, expected_content: &JumpRope) {
16        assert_eq!(&self.content, expected_content);
17    }
18
19
20}
21
22impl OpLog {
23    fn get_frontier_inefficiently(&self) -> LocalVersion {
24        // Could improve this by just looking at the last txn, and following shadows down.
25
26        let mut b = smallvec![];
27        for txn in self.history.entries.iter() {
28            advance_frontier_by_known_run(&mut b, txn.parents.as_slice(), txn.span);
29        }
30        b
31    }
32
33    /// Check the internal state of the diamond types list. This is only exported for integration
34    /// testing.
35    ///
36    /// You shouldn't have any reason to call this method.
37    ///
38    /// This method is public, but do not depend on it as part of the DT API. It could be removed at
39    /// any time.
40    #[allow(unused)]
41    pub fn dbg_check(&self, deep: bool) {
42        let actual_frontier = self.get_frontier_inefficiently();
43        assert_eq!(self.version, actual_frontier);
44
45        if deep {
46            self.history.check();
47        }
48    }
49
50    #[allow(unused)]
51    pub(crate) fn check_all_changes_rle_merged(&self) {
52        assert_eq!(self.client_data[0].item_times.num_entries(), 1);
53        // .. And operation log.
54        assert_eq!(self.history.entries.num_entries(), 1);
55    }
56}
57
58impl ListCRDT {
59    /// Check the internal state of the diamond types document. This is only exported for
60    /// integration testing.
61    ///
62    /// You shouldn't have any reason to call this method.
63    ///
64    /// This method is public, but do not depend on it as part of the DT API. It could be removed at
65    /// any time.
66    #[allow(unused)]
67    pub fn dbg_check(&self, deep: bool) {
68        self.oplog.dbg_check(deep);
69    }
70}
71
72impl History {
73    fn check(&self) {
74        self.entries.check_packed();
75
76        let expect_root_children = self.entries
77        .iter()
78        .enumerate()
79        .filter_map(|(i, entry)| {
80            if entry.parents.is_empty() {
81                Some(i)
82            } else { None }
83        });
84        assert!(expect_root_children.eq(self.root_child_indexes.iter().copied()));
85
86        // The shadow entries in txns name the smallest order for which all txns from
87        // [shadow..txn.order] are transitive parents of the current txn.
88
89        // I'm testing here sort of by induction. Iterating the txns in order allows us to assume
90        // all previous txns have valid shadows while we advance.
91
92        for (idx, hist) in self.entries.iter().enumerate() {
93            assert!(hist.span.end > hist.span.start);
94
95            debug_assert_frontier_sorted(&hist.parents);
96
97            // We contain prev_txn_order *and more*! See if we can extend the shadow by
98            // looking at the other entries of parents.
99            let mut parents = clone_smallvec(&hist.parents);
100            let mut expect_shadow = hist.span.start;
101
102            // The first txn *must* have ROOT as a parent, so 0 should never show up in shadow.
103            assert_ne!(hist.shadow, 0);
104
105            // Check our child_indexes all contain this item in their parents list.
106            for child_idx in &hist.child_indexes {
107                let child = &self.entries.0[*child_idx];
108                assert!(child.parents.iter().any(|p| hist.contains(*p)));
109            }
110
111            if parents.is_empty() {
112                if hist.span.start == 0 { expect_shadow = usize::MAX; }
113                // assert!(hist.parent_indexes.is_empty());
114            } else {
115                // We'll resort parents into descending order.
116                parents.sort_unstable_by(|a, b| b.cmp(a)); // descending order
117
118                // By induction, we can assume the previous shadows are correct.
119                for parent_order in parents {
120                    // Note parent_order could point in the middle of a txn run.
121                    let parent_idx = self.entries.find_index(parent_order).unwrap();
122                    let parent_txn = &self.entries.0[parent_idx];
123                    let offs = parent_order - parent_txn.span.start;
124
125                    // Check the parent txn names this txn in its child_indexes
126                    assert!(parent_txn.child_indexes.contains(&idx));
127
128                    // dbg!(parent_txn.order + offs, expect_shadow);
129                    // Shift it if the expected shadow points to the last item in the txn run.
130                    if parent_txn.span.start + offs + 1 == expect_shadow {
131                        expect_shadow = parent_txn.shadow;
132                    }
133                }
134            }
135
136            assert_eq!(hist.shadow, expect_shadow);
137        }
138    }
139}