| 1 | use onestore::{ |
| 2 | CommitState, ExGuid, PageCreation, PageEdit, RevisionIndex, Store, |
| 3 | document::{Document, FieldValue, Kind}, |
| 4 | op::{PageOp, SectionOp}, |
| 5 | }; |
| 6 | use std::{collections::BTreeMap, sync::LazyLock}; |
| 7 | |
| 8 | #[path = "current.rs"] |
| 9 | pub(crate) mod current; |
| 10 | #[path = "disk.rs"] |
| 11 | pub(crate) mod disk; |
| 12 | #[path = "ops.rs"] |
| 13 | pub(crate) mod ops; |
| 14 | |
| 15 | static SOURCE: LazyLock<Vec<u8>> = LazyLock::new(|| { |
| 16 | onestore::create_section("pages.one", "Original 🦀 é 東京", "Author").unwrap() |
| 17 | }); |
| 18 | |
| 19 | pub fn run(input: &[u8]) { |
| 20 | let source = match input.first().copied().unwrap_or(0) % 5 { |
| 21 | 1 => include_bytes!("../../../../corpus/page-lifecycle/page-edits/optional-cache/source/Lifecycle.one") |
| 22 | .as_slice(), |
| 23 | 2 => include_bytes!("../../../../corpus/page-lifecycle/page-edits/optional-cache/source-cold/notebook/Lifecycle.one") |
| 24 | .as_slice(), |
| 25 | 3 => include_bytes!("../../../../corpus/page-lifecycle/removal/features/native/before/notebook/Lifecycle.one").as_slice(), |
| 26 | 4 => include_bytes!("../../../../corpus/page-lifecycle/removal/ink/native/before/notebook/Lifecycle.one").as_slice(), |
| 27 | _ => SOURCE.as_slice(), |
| 28 | }; |
| 29 | if let Ok(intent) = serde_json::from_slice::<PageCreation>(input) |
| 30 | && let Ok(prepared) = ops::section_op(source, SectionOp::Create(intent.clone())) |
| 31 | { |
| 32 | current::current(prepared.as_bytes()); |
| 33 | } |
| 34 | if let Ok(edits) = serde_json::from_slice::<Vec<PageEdit>>(input) |
| 35 | && let Ok(prepared) = ops::section_op(source, SectionOp::Pages(edits.to_vec())) |
| 36 | { |
| 37 | current::current(prepared.as_bytes()); |
| 38 | } |
| 39 | if let Ok(pages) = serde_json::from_slice::<Vec<ExGuid>>(input) |
| 40 | && let Ok(prepared) = ops::section_op(source, SectionOp::Delete(pages.to_vec())) |
| 41 | { |
| 42 | current::current(prepared.as_bytes()); |
| 43 | } |
| 44 | let mut persisted = source.to_vec(); |
| 45 | let mut caches = std::array::from_fn::<_, 12, _>(|_| source.to_vec()); |
| 46 | for step in input.chunks_exact(8).take(24) { |
| 47 | let actor = usize::from(step[0]) % caches.len(); |
| 48 | if step[1] % 3 == 0 { |
| 49 | caches[actor].clone_from(&persisted); |
| 50 | } |
| 51 | let source = &caches[actor]; |
| 52 | let store = Store::parse(source).unwrap(); |
| 53 | let index = RevisionIndex::parse(&store).unwrap(); |
| 54 | let document = Document::parse(&index).unwrap(); |
| 55 | let mut pages = document.pages().unwrap(); |
| 56 | let mut levels: BTreeMap<_, _> = pages |
| 57 | .iter() |
| 58 | .map(|(sid, _)| { |
| 59 | let space = &document.spaces[sid]; |
| 60 | let view = &space.revisions[&space.contexts[&ExGuid::default()]]; |
| 61 | let Kind::Metadata { level, .. } = view.nodes[&view.roots[&2]].kind else { |
| 62 | panic!() |
| 63 | }; |
| 64 | (*sid, level.unwrap_or(1)) |
| 65 | }) |
| 66 | .collect(); |
| 67 | let selected = pages |
| 68 | .get(usize::from(step[2]) % pages.len().max(1)) |
| 69 | .copied(); |
| 70 | let text = ["", "Same title", "é 🦋 東京", " spaces "][usize::from(step[3]) % 4]; |
| 71 | let existing = if let Some((sid, _)) = selected { |
| 72 | let space = &document.spaces[&sid]; |
| 73 | let view = &space.revisions[&space.contexts[&ExGuid::default()]]; |
| 74 | let titles: Vec<_> = view |
| 75 | .nodes |
| 76 | .iter() |
| 77 | .filter_map(|(id, node)| { |
| 78 | (matches!( |
| 79 | node.kind, |
| 80 | Kind::RichText { |
| 81 | boilerplate: false, |
| 82 | .. |
| 83 | } |
| 84 | ) && node.extra[0].iter().any(|field| field.id == 0x88001cb4)) |
| 85 | .then_some(*id) |
| 86 | }) |
| 87 | .collect(); |
| 88 | if step[1] & 64 != 0 { |
| 89 | let count = if step[3] & 128 != 0 { |
| 90 | pages.len() |
| 91 | } else { |
| 92 | 1 + usize::from(step[3] & 64 != 0 && pages.len() > 1) |
| 93 | }; |
| 94 | let removed: Vec<_> = (0..count) |
| 95 | .map(|i| pages[(usize::from(step[2]) + i) % pages.len()].0) |
| 96 | .collect(); |
| 97 | let prepared = |
| 98 | ops::section_op(source, SectionOp::Delete(removed.to_vec())).unwrap(); |
| 99 | pages.retain(|page| !removed.contains(&page.0)); |
| 100 | for sid in removed { |
| 101 | levels.remove(&sid); |
| 102 | } |
| 103 | if let Some((first, _)) = pages.first() { |
| 104 | levels.insert(*first, 1); |
| 105 | } |
| 106 | Some((prepared, None)) |
| 107 | } else if step[1] & 32 != 0 { |
| 108 | let count = 1 + usize::from(step[3] & 128 != 0 && pages.len() > 1); |
| 109 | let selected: Vec<_> = (0..count) |
| 110 | .map(|i| pages[(usize::from(step[2]) + i) % pages.len()].0) |
| 111 | .collect(); |
| 112 | let before = (step[3] & 4 != 0) |
| 113 | .then_some(pages[usize::from(step[7]) % pages.len()].0) |
| 114 | .filter(|id| !selected.contains(id)); |
| 115 | let mut edits = Vec::new(); |
| 116 | for (ordinal, sid) in selected.iter().enumerate() { |
| 117 | let level = u32::from(if ordinal == 0 { step[3] } else { step[6] }) % 3 + 1; |
| 118 | levels.insert(*sid, level); |
| 119 | let edit = if step[3] & 8 == 0 { |
| 120 | PageEdit::set_level(*sid, level).unwrap() |
| 121 | } else { |
| 122 | let at = pages.iter().position(|p| p.0 == *sid).unwrap(); |
| 123 | let page = pages.remove(at); |
| 124 | let at = before.map_or(pages.len(), |id| { |
| 125 | pages.iter().position(|p| p.0 == id).unwrap() |
| 126 | }); |
| 127 | pages.insert(at, page); |
| 128 | PageEdit::move_to(*sid, before, level).unwrap() |
| 129 | }; |
| 130 | edits.push(edit); |
| 131 | } |
| 132 | let restored: Vec<PageEdit> = |
| 133 | serde_json::from_value(serde_json::to_value(&edits).unwrap()).unwrap(); |
| 134 | assert_eq!(edits, restored); |
| 135 | let prepared = ops::section_op(source, SectionOp::Pages(restored.to_vec())); |
| 136 | if levels[&pages[0].0] != 1 { |
| 137 | assert!(prepared.is_err()); |
| 138 | continue; |
| 139 | } |
| 140 | Some((prepared.unwrap(), None)) |
| 141 | } else if step[1] & 8 != 0 && !titles.is_empty() { |
| 142 | let object = titles[0]; |
| 143 | let Kind::RichText { text: original, .. } = &view.nodes[&object].kind else { |
| 144 | unreachable!() |
| 145 | }; |
| 146 | Some(( |
| 147 | ops::page_op( |
| 148 | source, |
| 149 | sid, |
| 150 | PageOp::Text { |
| 151 | text: object, |
| 152 | range: 0..u32::try_from(original.encode_utf16().count()).unwrap(), |
| 153 | with: text.into(), |
| 154 | }, |
| 155 | ) |
| 156 | .unwrap(), |
| 157 | Some((sid, object, text)), |
| 158 | )) |
| 159 | } else if step[1] & 16 != 0 { |
| 160 | let (add, _, inserted) = ops::new_outline(36.0, 36.0, text); |
| 161 | Some(( |
| 162 | ops::page_op(source, sid, add).unwrap(), |
| 163 | Some((sid, inserted, text)), |
| 164 | )) |
| 165 | } else { |
| 166 | None |
| 167 | } |
| 168 | } else { |
| 169 | None |
| 170 | }; |
| 171 | let (edit, title_update) = existing.unwrap_or_else(|| { |
| 172 | let before = if step[2] & 1 != 0 { |
| 173 | selected.map(|(sid, _)| { |
| 174 | let section = &document.spaces[&document.root]; |
| 175 | let section = &section.revisions[&section.contexts[&ExGuid::default()]]; |
| 176 | section.nodes[&section.roots[&1]] |
| 177 | .children |
| 178 | .iter() |
| 179 | .map(|id| &section.nodes[id]) |
| 180 | .find(|series| series.spaces.contains(&sid)) |
| 181 | .unwrap() |
| 182 | .spaces[0] |
| 183 | }) |
| 184 | } else { |
| 185 | None |
| 186 | }; |
| 187 | let title = (step[3] & 4 == 0).then_some(text); |
| 188 | let intent = PageCreation::new(before, title, "Page fuzz").unwrap(); |
| 189 | let restored = serde_json::from_value(serde_json::to_value(&intent).unwrap()).unwrap(); |
| 190 | assert_eq!(intent, restored); |
| 191 | let edit = ops::section_op(source, SectionOp::Create(restored.clone())).unwrap(); |
| 192 | let position = before.map_or(pages.len(), |sid| { |
| 193 | pages.iter().position(|p| p.0 == sid).unwrap() |
| 194 | }); |
| 195 | pages.insert(position, (intent.space(), intent.object())); |
| 196 | levels.insert(intent.space(), 1); |
| 197 | ( |
| 198 | edit, |
| 199 | intent |
| 200 | .title_object() |
| 201 | .map(|object| (intent.space(), object, text)), |
| 202 | ) |
| 203 | }); |
| 204 | let after_store = Store::parse(edit.as_bytes()).unwrap(); |
| 205 | let after_index = RevisionIndex::parse(&after_store).unwrap(); |
| 206 | let after_document = Document::parse(&after_index).unwrap(); |
| 207 | assert_eq!(after_document.pages().unwrap(), pages); |
| 208 | let mut metadata_levels = BTreeMap::new(); |
| 209 | for (sid, _) in &pages { |
| 210 | let space = &after_document.spaces[sid]; |
| 211 | let view = &space.revisions[&space.contexts[&ExGuid::default()]]; |
| 212 | let Kind::Metadata { level, .. } = view.nodes[&view.roots[&2]].kind else { |
| 213 | panic!() |
| 214 | }; |
| 215 | assert_eq!(level.unwrap_or(1), levels[sid]); |
| 216 | let metadata = &view.nodes[&view.roots[&2]]; |
| 217 | let FieldValue::Bytes(guid) = metadata.extra[0] |
| 218 | .iter() |
| 219 | .find(|field| field.id == 0x1c001c30) |
| 220 | .unwrap() |
| 221 | .value |
| 222 | else { |
| 223 | panic!() |
| 224 | }; |
| 225 | assert!(metadata_levels.insert(guid, levels[sid]).is_none()); |
| 226 | } |
| 227 | let section = &after_document.spaces[&after_document.root]; |
| 228 | let view = &section.revisions[&section.contexts[&ExGuid::default()]]; |
| 229 | for metadata in view.nodes.values() { |
| 230 | if let Kind::Metadata { level, .. } = metadata.kind { |
| 231 | let FieldValue::Bytes(guid) = metadata.extra[0] |
| 232 | .iter() |
| 233 | .find(|field| field.id == 0x1c001c30) |
| 234 | .unwrap() |
| 235 | .value |
| 236 | else { |
| 237 | panic!() |
| 238 | }; |
| 239 | assert_eq!(metadata_levels.get(guid), Some(&level.unwrap_or(1))); |
| 240 | } |
| 241 | } |
| 242 | if let Some((sid, object, text)) = title_update { |
| 243 | let space = &after_document.spaces[&sid]; |
| 244 | let view = &space.revisions[&space.contexts[&ExGuid::default()]]; |
| 245 | assert!( |
| 246 | matches!(&view.nodes[&object].kind, Kind::RichText { text: actual, .. } if actual == text) |
| 247 | ); |
| 248 | } |
| 249 | for (sid, space) in &index.spaces { |
| 250 | let rid = space.labels[&(ExGuid::default(), 1)]; |
| 251 | assert_eq!( |
| 252 | format!("{:?}", index.resolve(*sid, rid).unwrap()), |
| 253 | format!("{:?}", after_index.resolve(*sid, rid).unwrap()) |
| 254 | ); |
| 255 | } |
| 256 | let before = current::current(&persisted); |
| 257 | let after = current::current(edit.as_bytes()); |
| 258 | let mut disk = disk::Disk { |
| 259 | visible: persisted.clone(), |
| 260 | durable: persisted.clone(), |
| 261 | operation: 0, |
| 262 | fail_at: (step[4] != 0).then_some(usize::from(u16::from_le_bytes([step[4], step[5]]))), |
| 263 | write_limit: if step[6] & 1 == 0 { 17 } else { 4096 }, |
| 264 | random: u64::from(step[7]) + 1, |
| 265 | }; |
| 266 | let Some(transaction) = &edit.transaction else { |
| 267 | continue; |
| 268 | }; |
| 269 | let result = transaction.commit(&mut disk); |
| 270 | let observed = current::current(&disk.durable); |
| 271 | match result { |
| 272 | Ok(()) => assert_eq!(observed, after), |
| 273 | Err(error) => { |
| 274 | assert!(observed == before || observed == after); |
| 275 | match error.state { |
| 276 | CommitState::NotCommitted => assert_eq!(observed, before), |
| 277 | CommitState::Committed => assert_eq!(observed, after), |
| 278 | CommitState::Unknown => {} |
| 279 | } |
| 280 | } |
| 281 | } |
| 282 | persisted = disk.durable; |
| 283 | } |
| 284 | } |