| 1 | |
|
| 2 | |
|
| 3 | |
|
| 4 | |
|
| 5 | |
|
| 6 | |
|
| 7 | | use crate::fasthash::{FastMap as HashMap, FastSet as HashSet}; |
| 8 | | use crate::hash::ChangeHash; |
| 9 | | use crate::lines::{LineRef, Lines}; |
| 10 | | use crate::objects::AtomRange; |
| 11 | | use std::collections::{BTreeMap, BTreeSet}; |
| 12 | |
|
| 13 | |
|
| 14 | | pub type AtomKey = (ChangeHash, u32); |
| 15 | |
|
| 16 | | #[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)] |
| 17 | | pub enum Vertex { |
| 18 | | Start, |
| 19 | | Atom(AtomKey), |
| 20 | | End, |
| 21 | | } |
| 22 | |
|
| 23 | |
|
| 24 | | pub trait DepOracle { |
| 25 | | fn depends_on(&self, a: &ChangeHash, b: &ChangeHash) -> bool; |
| 26 | | } |
| 27 | |
|
| 28 | |
|
| 29 | |
|
| 30 | |
|
| 31 | |
|
| 32 | | struct MemoDeps<'a> { |
| 33 | | inner: &'a dyn DepOracle, |
| 34 | | known: std::cell::RefCell<crate::fasthash::FastMap<(ChangeHash, ChangeHash), bool>>, |
| 35 | | } |
| 36 | |
|
| 37 | | impl DepOracle for MemoDeps<'_> { |
| 38 | 4943× | fn depends_on(&self, a: &ChangeHash, b: &ChangeHash) -> bool { |
| 39 | 4943× | if let Some(v) = self.known.borrow().get(&(*a, *b)) { |
| 40 | 2483× | return *v; |
| 41 | 2460× | } |
| 42 | 2460× | let v = self.inner.depends_on(a, b); |
| 43 | 2460× | self.known.borrow_mut().insert((*a, *b), v); |
| 44 | 2460× | v |
| 45 | 4943× | } |
| 46 | | } |
| 47 | |
|
| 48 | |
|
| 49 | | pub struct NoDeps; |
| 50 | | impl DepOracle for NoDeps { |
| 51 | 4× | fn depends_on(&self, a: &ChangeHash, b: &ChangeHash) -> bool { |
| 52 | 4× | a == b |
| 53 | 4× | } |
| 54 | | } |
| 55 | |
|
| 56 | | #[derive(Default, Clone, Debug)] |
| 57 | | struct AtomInfo { |
| 58 | | |
| 59 | | |
| 60 | | content: Option<LineRef>, |
| 61 | | deleted_by: BTreeSet<ChangeHash>, |
| 62 | | } |
| 63 | |
|
| 64 | | #[derive(Clone, Debug)] |
| 65 | | pub struct FileGraph { |
| 66 | | atoms: HashMap<AtomKey, AtomInfo>, |
| 67 | | |
| 68 | | succ: HashMap<Vertex, Edges>, |
| 69 | | |
| 70 | | order: OrderSlot, |
| 71 | | } |
| 72 | |
|
| 73 | |
|
| 74 | |
|
| 75 | |
|
| 76 | |
|
| 77 | |
|
| 78 | | #[derive(Debug)] |
| 79 | | pub struct Order { |
| 80 | | atoms: Vec<AtomKey>, |
| 81 | | |
| 82 | | |
| 83 | | |
| 84 | | unchecked: Vec<AtomKey>, |
| 85 | | confirmed: std::sync::atomic::AtomicBool, |
| 86 | | } |
| 87 | |
|
| 88 | |
|
| 89 | |
|
| 90 | | #[derive(Debug, Default)] |
| 91 | | struct OrderSlot(std::sync::Mutex<Option<std::sync::Arc<Order>>>); |
| 92 | |
|
| 93 | | impl OrderSlot { |
| 94 | 30917× | fn get(&self) -> Option<std::sync::Arc<Order>> { |
| 95 | 30917× | self.0.lock().unwrap_or_else(|e| e.into_inner()).clone() |
| 96 | 30917× | } |
| 97 | 1820× | fn set(&self, o: Option<std::sync::Arc<Order>>) { |
| 98 | 1820× | *self.0.lock().unwrap_or_else(|e| e.into_inner()) = o; |
| 99 | 1820× | } |
| 100 | 75103× | fn take(&mut self) -> Option<std::sync::Arc<Order>> { |
| 101 | 75103× | self.0.get_mut().unwrap_or_else(|e| e.into_inner()).take() |
| 102 | 75103× | } |
| 103 | | } |
| 104 | |
|
| 105 | | impl Clone for OrderSlot { |
| 106 | 7× | fn clone(&self) -> Self { |
| 107 | 7× | OrderSlot(std::sync::Mutex::new(self.get())) |
| 108 | 7× | } |
| 109 | | } |
| 110 | |
|
| 111 | | impl Order { |
| 112 | 1810× | fn known(atoms: Vec<AtomKey>) -> Order { |
| 113 | 1810× | Order { |
| 114 | 1810× | atoms, |
| 115 | 1810× | unchecked: Vec::new(), |
| 116 | 1810× | confirmed: std::sync::atomic::AtomicBool::new(true), |
| 117 | 1810× | } |
| 118 | 1810× | } |
| 119 | |
|
| 120 | | |
| 121 | | |
| 122 | | |
| 123 | | |
| 124 | | |
| 125 | 535× | fn splice( |
| 126 | 535× | &self, |
| 127 | 535× | h: ChangeHash, |
| 128 | 535× | inserts: &[(Vertex, Vertex, u32, u32)], |
| 129 | 535× | deleted: &[AtomKey], |
| 130 | 535× | ) -> Option<std::sync::Arc<Order>> { |
| 131 | 535× | let mut want: HashSet<AtomKey> = deleted.iter().copied().collect(); |
| 132 | 579× | for (a, b, _, _) in inserts { |
| 133 | 1158× | for v in [a, b] { |
| 134 | 1158× | if let Vertex::Atom(k) = v { |
| 135 | 947× | want.insert(*k); |
| 136 | 947× | } |
| 137 | | } |
| 138 | | } |
| 139 | 535× | let mut pos: HashMap<AtomKey, usize> = HashMap::default(); |
| 140 | 2865× | for (i, k) in self.atoms.iter().enumerate() { |
| 141 | 2865× | if want.contains(k) { |
| 142 | 1294× | pos.insert(*k, i); |
| 143 | 1571× | } |
| 144 | | } |
| 145 | 535× | let len = self.atoms.len(); |
| 146 | 535× | let mut gone = vec![false; len]; |
| 147 | 535× | let mut unchecked = if self.confirmed.load(std::sync::atomic::Ordering::Relaxed) { |
| 148 | 535× | Vec::new() |
| 149 | | } else { |
| 150 | 0× | self.unchecked.clone() |
| 151 | | }; |
| 152 | 740× | for k in deleted { |
| 153 | 740× | if k.0 == h { |
| 154 | 0× | return None; |
| 155 | 740× | } |
| 156 | 740× | if let Some(&i) = pos.get(k) { |
| 157 | 717× | if !gone[i] { |
| 158 | 717× | gone[i] = true; |
| 159 | 717× | unchecked.push(*k); |
| 160 | 717× | } |
| 161 | 23× | } |
| 162 | | } |
| 163 | | |
| 164 | | |
| 165 | 535× | let mut slots: Vec<(usize, usize, u32, u32)> = Vec::with_capacity(inserts.len()); |
| 166 | 560× | for &(a, b, first, count) in inserts { |
| 167 | 560× | let from = match a { |
| 168 | 143× | Vertex::Start => 0, |
| 169 | 417× | Vertex::Atom(k) => pos.get(&k)? + 1, |
| 170 | 0× | Vertex::End => return None, |
| 171 | | }; |
| 172 | 549× | let to = match b { |
| 173 | 64× | Vertex::End => len, |
| 174 | 485× | Vertex::Atom(k) => *pos.get(&k)?, |
| 175 | 0× | Vertex::Start => return None, |
| 176 | | }; |
| 177 | 542× | if from > to || gone[from..to].iter().any(|g| !g) { |
| 178 | 5× | return None; |
| 179 | 537× | } |
| 180 | 537× | slots.push((from, to, first, count)); |
| 181 | | } |
| 182 | | |
| 183 | | |
| 184 | 512× | slots.sort_unstable(); |
| 185 | 512× | for w in slots.windows(2) { |
| 186 | 159× | let (end, next) = (w[0].1, w[1].0); |
| 187 | 302× | if end >= next || !gone[end..next].iter().any(|g| !g) { |
| 188 | 0× | return None; |
| 189 | 159× | } |
| 190 | | } |
| 191 | 536× | let added: usize = slots.iter().map(|s| s.3 as usize).sum(); |
| 192 | 512× | let mut atoms = Vec::with_capacity(len + added); |
| 193 | 512× | let mut slots = slots.into_iter().peekable(); |
| 194 | 3165× | let mut runs_at = |i: usize, atoms: &mut Vec<AtomKey>| { |
| 195 | 3701× | while let Some(&(from, _, first, count)) = slots.peek() { |
| 196 | 1790× | if from != i { |
| 197 | 1254× | break; |
| 198 | 536× | } |
| 199 | 783× | atoms.extend((first..first + count).map(|j| (h, j))); |
| 200 | 536× | slots.next(); |
| 201 | | } |
| 202 | 3165× | }; |
| 203 | 2653× | for (i, (k, g)) in self.atoms.iter().zip(&gone).enumerate() { |
| 204 | 2653× | runs_at(i, &mut atoms); |
| 205 | 2653× | if !g { |
| 206 | 1973× | atoms.push(*k); |
| 207 | 1973× | } |
| 208 | | } |
| 209 | 512× | runs_at(len, &mut atoms); |
| 210 | 512× | let confirmed = unchecked.is_empty(); |
| 211 | 512× | Some(std::sync::Arc::new(Order { |
| 212 | 512× | atoms, |
| 213 | 512× | unchecked, |
| 214 | 512× | confirmed: std::sync::atomic::AtomicBool::new(confirmed), |
| 215 | 512× | })) |
| 216 | 535× | } |
| 217 | | } |
| 218 | |
|
| 219 | |
|
| 220 | |
|
| 221 | | #[derive(Clone, Debug, Default, PartialEq, Eq)] |
| 222 | | enum Edges { |
| 223 | | #[default] |
| 224 | | None, |
| 225 | | One(Vertex), |
| 226 | | Many(Vec<Vertex>), |
| 227 | | } |
| 228 | |
|
| 229 | | impl Edges { |
| 230 | 246550× | fn insert(&mut self, v: Vertex) { |
| 231 | 35811× | match self { |
| 232 | 208409× | Edges::None => *self = Edges::One(v), |
| 233 | 35811× | Edges::One(a) if *a == v => {} |
| 234 | 35811× | Edges::One(a) => { |
| 235 | 35811× | let a = *a; |
| 236 | 35811× | *self = Edges::Many(if a < v { vec![a, v] } else { vec![v, a] }); |
| 237 | | } |
| 238 | 2330× | Edges::Many(list) => { |
| 239 | 2330× | if let Err(i) = list.binary_search(&v) { |
| 240 | 2330× | list.insert(i, v); |
| 241 | 2330× | } |
| 242 | | } |
| 243 | | } |
| 244 | 246550× | } |
| 245 | |
|
| 246 | 957891× | fn as_slice(&self) -> &[Vertex] { |
| 247 | 957891× | match self { |
| 248 | 5045× | Edges::None => &[], |
| 249 | 697776× | Edges::One(v) => std::slice::from_ref(v), |
| 250 | 255070× | Edges::Many(list) => list, |
| 251 | | } |
| 252 | 957891× | } |
| 253 | |
|
| 254 | 693737× | fn iter(&self) -> std::slice::Iter<'_, Vertex> { |
| 255 | 693737× | self.as_slice().iter() |
| 256 | 693737× | } |
| 257 | | } |
| 258 | |
|
| 259 | | impl Default for FileGraph { |
| 260 | 29562× | fn default() -> Self { |
| 261 | 29562× | Self::new() |
| 262 | 29562× | } |
| 263 | | } |
| 264 | |
|
| 265 | |
|
| 266 | | #[derive(Clone, Debug, PartialEq, Eq)] |
| 267 | | pub enum Segment { |
| 268 | | Lines(Vec<AtomKey>), |
| 269 | | |
| 270 | | Conflict(Vec<Vec<AtomKey>>), |
| 271 | | |
| 272 | | |
| 273 | | Zombie { |
| 274 | | deleted: Vec<AtomKey>, |
| 275 | | inserted: Vec<AtomKey>, |
| 276 | | }, |
| 277 | | } |
| 278 | |
|
| 279 | | #[derive(Clone, Debug, PartialEq, Eq, Default)] |
| 280 | | pub struct Materialized { |
| 281 | | pub segments: Vec<Segment>, |
| 282 | | } |
| 283 | |
|
| 284 | | impl Materialized { |
| 285 | 50688× | pub fn is_conflicted(&self) -> bool { |
| 286 | 50688× | self.segments |
| 287 | 50688× | .iter() |
| 288 | 50688× | .any(|s| !matches!(s, Segment::Lines(_))) |
| 289 | 50688× | } |
| 290 | 28974× | pub fn conflict_count(&self) -> usize { |
| 291 | 28974× | self.segments |
| 292 | 28974× | .iter() |
| 293 | 29813× | .filter(|s| !matches!(s, Segment::Lines(_))) |
| 294 | 28974× | .count() |
| 295 | 28974× | } |
| 296 | | |
| 297 | 1370× | pub fn live_atoms(&self) -> Vec<AtomKey> { |
| 298 | 1370× | let mut out = Vec::new(); |
| 299 | 1404× | for s in &self.segments { |
| 300 | 1404× | match s { |
| 301 | 1360× | Segment::Lines(a) => out.extend(a), |
| 302 | 30× | Segment::Conflict(sides) => sides.iter().for_each(|x| out.extend(x)), |
| 303 | 29× | Segment::Zombie { inserted, .. } => out.extend(inserted), |
| 304 | | } |
| 305 | | } |
| 306 | 1370× | out |
| 307 | 1370× | } |
| 308 | | } |
| 309 | |
|
| 310 | | impl FileGraph { |
| 311 | 29632× | pub fn new() -> Self { |
| 312 | 29632× | let mut g = FileGraph { |
| 313 | 29632× | atoms: HashMap::default(), |
| 314 | 29632× | succ: HashMap::default(), |
| 315 | 29632× | order: OrderSlot::default(), |
| 316 | 29632× | }; |
| 317 | 29632× | g.edge(Vertex::Start, Vertex::End); |
| 318 | 29632× | g |
| 319 | 29632× | } |
| 320 | |
|
| 321 | 34814× | fn edge(&mut self, a: Vertex, b: Vertex) { |
| 322 | 34814× | self.succ.entry(a).or_default().insert(b); |
| 323 | 34814× | self.succ.entry(b).or_default(); |
| 324 | 34814× | } |
| 325 | |
|
| 326 | | |
| 327 | 1042× | pub fn insert( |
| 328 | 1042× | &mut self, |
| 329 | 1042× | change: ChangeHash, |
| 330 | 1042× | first_index: u32, |
| 331 | 1042× | after: Vertex, |
| 332 | 1042× | before: Vertex, |
| 333 | 1042× | lines: &Lines, |
| 334 | 1042× | ) { |
| 335 | 1042× | self.insert_owned(change, first_index, after, before, lines.clone()); |
| 336 | 1042× | } |
| 337 | |
|
| 338 | | |
| 339 | | |
| 340 | | |
| 341 | 37424× | pub fn insert_owned( |
| 342 | 37424× | &mut self, |
| 343 | 37424× | change: ChangeHash, |
| 344 | 37424× | first_index: u32, |
| 345 | 37424× | after: Vertex, |
| 346 | 37424× | before: Vertex, |
| 347 | 37424× | lines: Lines, |
| 348 | 37424× | ) { |
| 349 | 37424× | self.order.take(); |
| 350 | 37424× | self.atoms.reserve(lines.len()); |
| 351 | 37424× | self.succ.reserve(lines.len() + 1); |
| 352 | 37424× | let mut prev = after; |
| 353 | 174312× | for i in 0..lines.len() { |
| 354 | 174312× | let key = (change, first_index + i as u32); |
| 355 | 174312× | self.atoms.entry(key).or_default().content = |
| 356 | 174312× | Some(LineRef::new(lines.clone(), i as u32)); |
| 357 | 174312× | let v = Vertex::Atom(key); |
| 358 | 174312× | self.succ.entry(prev).or_default().insert(v); |
| 359 | 174312× | prev = v; |
| 360 | 174312× | } |
| 361 | 37424× | self.succ.entry(prev).or_default().insert(before); |
| 362 | 37424× | self.succ.entry(before).or_default(); |
| 363 | 37424× | } |
| 364 | |
|
| 365 | | |
| 366 | 6244× | pub fn delete(&mut self, by: ChangeHash, range: &AtomRange) { |
| 367 | 6244× | self.order.take(); |
| 368 | 6559× | for i in range.from..range.to { |
| 369 | 6559× | self.atoms |
| 370 | 6559× | .entry((range.change, i)) |
| 371 | 6559× | .or_default() |
| 372 | 6559× | .deleted_by |
| 373 | 6559× | .insert(by); |
| 374 | 6559× | } |
| 375 | 6244× | } |
| 376 | |
|
| 377 | 470992× | pub fn is_live(&self, k: &AtomKey) -> bool { |
| 378 | 470992× | self.atoms |
| 379 | 470992× | .get(k) |
| 380 | 470992× | .is_some_and(|a| a.content.is_some() && a.deleted_by.is_empty()) |
| 381 | 470992× | } |
| 382 | |
|
| 383 | 159791× | pub fn content(&self, k: &AtomKey) -> Option<&[u8]> { |
| 384 | 159791× | self.atoms |
| 385 | 159791× | .get(k) |
| 386 | 159791× | .and_then(|a| a.content.as_ref().map(LineRef::bytes)) |
| 387 | 159791× | } |
| 388 | |
|
| 389 | 277× | pub fn deleters(&self, k: &AtomKey) -> impl Iterator<Item = &ChangeHash> { |
| 390 | 277× | self.atoms |
| 391 | 277× | .get(k) |
| 392 | 277× | .into_iter() |
| 393 | 277× | .flat_map(|a| a.deleted_by.iter()) |
| 394 | 277× | } |
| 395 | |
|
| 396 | | |
| 397 | | |
| 398 | | |
| 399 | | |
| 400 | | |
| 401 | | |
| 402 | | |
| 403 | 9× | pub fn adjacent_insertion(&self, m: &Materialized, i: usize, deps: &dyn DepOracle) -> bool { |
| 404 | 9× | let Some(Segment::Conflict(sides)) = m.segments.get(i) else { |
| 405 | 0× | return false; |
| 406 | | }; |
| 407 | 9× | let before = match i.checked_sub(1).map(|j| &m.segments[j]) { |
| 408 | 0× | None => Vertex::Start, |
| 409 | 9× | Some(Segment::Lines(a)) => match a.last() { |
| 410 | 9× | Some(k) => Vertex::Atom(*k), |
| 411 | 0× | None => return false, |
| 412 | | }, |
| 413 | 0× | Some(_) => return false, |
| 414 | | }; |
| 415 | 9× | let on_side: HashSet<AtomKey> = sides.iter().flatten().copied().collect(); |
| 416 | 9× | let side_changes: Vec<BTreeSet<ChangeHash>> = sides |
| 417 | 9× | .iter() |
| 418 | 18× | .map(|s| s.iter().map(|a| a.0).collect()) |
| 419 | 9× | .collect(); |
| 420 | 9× | let shared = |d: &ChangeHash| { |
| 421 | 3× | side_changes |
| 422 | 3× | .iter() |
| 423 | 5× | .all(|s| s.iter().any(|c| deps.depends_on(c, d))) |
| 424 | 3× | }; |
| 425 | | |
| 426 | | |
| 427 | 9× | let mut seen: HashSet<AtomKey> = HashSet::default(); |
| 428 | 9× | let mut todo = vec![before]; |
| 429 | 27× | while let Some(v) = todo.pop() { |
| 430 | 33× | for &w in self.succ.get(&v).map(Edges::as_slice).unwrap_or(&[]) { |
| 431 | 33× | let Vertex::Atom(k) = w else { continue }; |
| 432 | 33× | if !seen.insert(k) { |
| 433 | 12× | continue; |
| 434 | 21× | } |
| 435 | 21× | if !on_side.contains(&k) { |
| 436 | 9× | if self.is_live(&k) { |
| 437 | 6× | continue; |
| 438 | 3× | } |
| 439 | 3× | if self.deleters(&k).any(|d| !shared(d)) { |
| 440 | 3× | return false; |
| 441 | 0× | } |
| 442 | 12× | } |
| 443 | 12× | todo.push(w); |
| 444 | | } |
| 445 | | } |
| 446 | 6× | true |
| 447 | 9× | } |
| 448 | |
|
| 449 | | |
| 450 | 56× | pub fn atom_count(&self) -> usize { |
| 451 | 56× | self.atoms.len() |
| 452 | 56× | } |
| 453 | |
|
| 454 | | |
| 455 | | |
| 456 | | |
| 457 | | |
| 458 | | |
| 459 | | |
| 460 | | |
| 461 | 3× | pub fn between(&self, first: &AtomKey, last: &AtomKey) -> Vec<AtomKey> { |
| 462 | 3× | let (from, to) = (Vertex::Atom(*first), Vertex::Atom(*last)); |
| 463 | 3× | let mut ahead: HashSet<Vertex> = HashSet::default(); |
| 464 | 3× | let mut stack = vec![from]; |
| 465 | 29× | while let Some(v) = stack.pop() { |
| 466 | 26× | if ahead.insert(v) { |
| 467 | 21× | stack.extend( |
| 468 | 21× | self.succ |
| 469 | 21× | .get(&v) |
| 470 | 21× | .map(|e| e.as_slice()) |
| 471 | 21× | .unwrap_or(&[]) |
| 472 | 21× | .iter() |
| 473 | 21× | .copied(), |
| 474 | | ); |
| 475 | 5× | } |
| 476 | | } |
| 477 | 3× | if !ahead.contains(&to) { |
| 478 | 0× | return vec![]; |
| 479 | 3× | } |
| 480 | 3× | let mut pred: HashMap<Vertex, Vec<Vertex>> = HashMap::default(); |
| 481 | 28× | for (u, es) in &self.succ { |
| 482 | 28× | if ahead.contains(u) { |
| 483 | 23× | for w in es.iter() { |
| 484 | 23× | pred.entry(*w).or_default().push(*u); |
| 485 | 23× | } |
| 486 | 7× | } |
| 487 | | } |
| 488 | 3× | let mut behind: HashSet<Vertex> = HashSet::default(); |
| 489 | 3× | let mut stack = vec![to]; |
| 490 | 15× | while let Some(v) = stack.pop() { |
| 491 | 12× | if behind.insert(v) { |
| 492 | 10× | stack.extend(pred.get(&v).into_iter().flatten().copied()); |
| 493 | 10× | } |
| 494 | | } |
| 495 | 3× | let mut out: Vec<AtomKey> = ahead |
| 496 | 3× | .intersection(&behind) |
| 497 | 10× | .filter_map(|v| match v { |
| 498 | 10× | Vertex::Atom(k) if self.atoms.contains_key(k) => Some(*k), |
| 499 | 0× | _ => None, |
| 500 | 10× | }) |
| 501 | 3× | .collect(); |
| 502 | 3× | out.sort(); |
| 503 | 3× | out |
| 504 | 3× | } |
| 505 | |
|
| 506 | | |
| 507 | 3× | pub fn touching(&self, atoms: &[AtomKey]) -> BTreeSet<ChangeHash> { |
| 508 | 3× | let mut out = BTreeSet::new(); |
| 509 | 10× | for k in atoms { |
| 510 | 10× | out.insert(k.0); |
| 511 | 10× | if let Some(info) = self.atoms.get(k) { |
| 512 | 10× | out.extend(info.deleted_by.iter().copied()); |
| 513 | 10× | } |
| 514 | | } |
| 515 | 3× | out |
| 516 | 3× | } |
| 517 | |
|
| 518 | 1262× | pub fn live_successor_changes(&self, k: &AtomKey) -> Vec<ChangeHash> { |
| 519 | 1262× | self.succ |
| 520 | 1262× | .get(&Vertex::Atom(*k)) |
| 521 | 1262× | .map(|ss| { |
| 522 | 1261× | ss.iter() |
| 523 | 1261× | .filter_map(|s| match s { |
| 524 | 964× | Vertex::Atom(y) if self.is_live(y) => Some(y.0), |
| 525 | 458× | _ => None, |
| 526 | 1310× | }) |
| 527 | 1261× | .collect() |
| 528 | 1261× | }) |
| 529 | 1262× | .unwrap_or_default() |
| 530 | 1262× | } |
| 531 | |
|
| 532 | 356613× | fn is_ghost(&self, k: &AtomKey, info: &AtomInfo, deps: &dyn DepOracle) -> bool { |
| 533 | | |
| 534 | | |
| 535 | | |
| 536 | | |
| 537 | 356613× | self.succ.get(&Vertex::Atom(*k)).is_some_and(|ss| { |
| 538 | 469169× | ss.iter().any(|s| match s { |
| 539 | 469169× | Vertex::Atom(y) if self.is_live(y) => !info |
| 540 | 4814× | .deleted_by |
| 541 | 4814× | .iter() |
| 542 | 4942× | .any(|d| deps.depends_on(d, &y.0) || deps.depends_on(&y.0, d)), |
| 543 | 494295× | _ => false, |
| 544 | 499109× | }) |
| 545 | 356613× | }) |
| 546 | 356613× | } |
| 547 | |
|
| 548 | | |
| 549 | 10401× | fn reduced_succ( |
| 550 | 10401× | &self, |
| 551 | 10401× | v: Vertex, |
| 552 | 10401× | kept: &HashSet<Vertex>, |
| 553 | 10401× | memo: &mut HashMap<Vertex, BTreeSet<Vertex>>, |
| 554 | 10401× | ) -> BTreeSet<Vertex> { |
| 555 | 10401× | let direct = self.succ.get(&v).map(Edges::as_slice).unwrap_or_default(); |
| 556 | | |
| 557 | | |
| 558 | 11151× | if direct.iter().all(|s| kept.contains(s)) { |
| 559 | 6288× | return direct.iter().copied().collect(); |
| 560 | 4113× | } |
| 561 | 4113× | let mut out = BTreeSet::new(); |
| 562 | 4113× | let mut stack: Vec<Vertex> = direct.to_vec(); |
| 563 | 4113× | let mut seen = HashSet::default(); |
| 564 | 503517× | while let Some(s) = stack.pop() { |
| 565 | 499404× | if kept.contains(&s) { |
| 566 | 34452× | out.insert(s); |
| 567 | 464952× | } else if seen.insert(s) { |
| 568 | 319898× | if let Some(m) = memo.get(&s) { |
| 569 | 0× | out.extend(m.iter().copied()); |
| 570 | 319898× | } else if let Some(next) = self.succ.get(&s) { |
| 571 | 319898× | stack.extend(next.iter().copied()); |
| 572 | 319898× | } |
| 573 | 145054× | } |
| 574 | | } |
| 575 | 4113× | out |
| 576 | 10401× | } |
| 577 | |
|
| 578 | | |
| 579 | | |
| 580 | | |
| 581 | | |
| 582 | | |
| 583 | 29176× | fn as_chain(&self) -> Option<Vec<AtomKey>> { |
| 584 | 29176× | if self |
| 585 | 29176× | .atoms |
| 586 | 29176× | .values() |
| 587 | 112057× | .any(|a| a.content.is_none() || !a.deleted_by.is_empty()) |
| 588 | | { |
| 589 | 2188× | return None; |
| 590 | 26988× | } |
| 591 | 26988× | let mut order = Vec::with_capacity(self.atoms.len()); |
| 592 | 26988× | let mut v = Vertex::Start; |
| 593 | | loop { |
| 594 | 130912× | let succ = self.succ.get(&v)?.as_slice(); |
| 595 | 159611× | let mut atoms = succ.iter().filter(|s| **s != Vertex::End); |
| 596 | 130912× | match (atoms.next(), atoms.next()) { |
| 597 | 26586× | (None, _) if succ.contains(&Vertex::End) => break, |
| 598 | 103924× | (Some(Vertex::Atom(k)), None) if order.len() < self.atoms.len() => { |
| 599 | 103924× | order.push(*k); |
| 600 | 103924× | v = Vertex::Atom(*k); |
| 601 | 103924× | } |
| 602 | 402× | _ => return None, |
| 603 | | } |
| 604 | | } |
| 605 | 26586× | (order.len() == self.atoms.len()).then_some(order) |
| 606 | 29176× | } |
| 607 | |
|
| 608 | | |
| 609 | 30452× | pub fn materialize(&self, deps: &dyn DepOracle) -> Materialized { |
| 610 | 30452× | let deps = &MemoDeps { |
| 611 | 30452× | inner: deps, |
| 612 | 30452× | known: Default::default(), |
| 613 | 30452× | }; |
| 614 | 30452× | let lines = |order: Vec<AtomKey>| Materialized { |
| 615 | 29676× | segments: if order.is_empty() { |
| 616 | 163× | vec![] |
| 617 | | } else { |
| 618 | 29513× | vec![Segment::Lines(order)] |
| 619 | | }, |
| 620 | 29676× | }; |
| 621 | | use std::sync::atomic::Ordering::Relaxed; |
| 622 | | |
| 623 | 30452× | let started = crate::timing::enabled().then(std::time::Instant::now); |
| 624 | 30452× | let took = |name: &'static str| { |
| 625 | 30452× | if let Some(t) = started { |
| 626 | 0× | crate::timing::add(name, t.elapsed()); |
| 627 | 30452× | } |
| 628 | 30452× | }; |
| 629 | 30452× | if let Some(o) = self.order.get() { |
| 630 | 1292× | let holds = o.confirmed.load(Relaxed) |
| 631 | 616× | || o.unchecked.iter().all(|k| { |
| 632 | 616× | self.atoms |
| 633 | 616× | .get(k) |
| 634 | 616× | .is_none_or(|info| !self.is_ghost(k, info, deps)) |
| 635 | 616× | }); |
| 636 | 1292× | if holds { |
| 637 | 1282× | o.confirmed.store(true, Relaxed); |
| 638 | 1282× | took("graph: kept order"); |
| 639 | 1282× | return lines(o.atoms.clone()); |
| 640 | 10× | } |
| 641 | 10× | self.order.set(None); |
| 642 | 29160× | } |
| 643 | | |
| 644 | | |
| 645 | 29170× | if let Some(order) = self.as_chain() { |
| 646 | 26584× | took("graph: chain"); |
| 647 | 26584× | return lines(order); |
| 648 | 2586× | } |
| 649 | 2586× | if let Some(order) = self.as_order(deps) { |
| 650 | 1810× | self.order |
| 651 | 1810× | .set(Some(std::sync::Arc::new(Order::known(order.clone())))); |
| 652 | 1810× | took("graph: single order"); |
| 653 | 1810× | return lines(order); |
| 654 | 776× | } |
| 655 | 776× | let m = self.materialize_general(deps); |
| 656 | 776× | took("graph: general"); |
| 657 | 776× | m |
| 658 | 30452× | } |
| 659 | |
|
| 660 | | |
| 661 | | |
| 662 | 31435× | pub fn take_order(&mut self) -> Option<std::sync::Arc<Order>> { |
| 663 | 31435× | self.order.take() |
| 664 | 31435× | } |
| 665 | |
|
| 666 | | |
| 667 | | |
| 668 | | |
| 669 | | |
| 670 | 536× | pub fn keep_spliced( |
| 671 | 536× | &mut self, |
| 672 | 536× | order: std::sync::Arc<Order>, |
| 673 | 536× | h: ChangeHash, |
| 674 | 536× | inserts: &[(Vertex, Vertex, u32, u32)], |
| 675 | 536× | deleted: &[AtomKey], |
| 676 | 536× | ) { |
| 677 | 536× | let live = inserts |
| 678 | 536× | .iter() |
| 679 | 850× | .all(|&(_, _, first, count)| (first..first + count).all(|i| self.is_live(&(h, i)))); |
| 680 | 536× | let spliced = if live { |
| 681 | 535× | order.splice(h, inserts, deleted) |
| 682 | | } else { |
| 683 | 1× | None |
| 684 | | }; |
| 685 | 536× | *self.order.0.get_mut().unwrap_or_else(|e| e.into_inner()) = spliced; |
| 686 | 536× | } |
| 687 | |
|
| 688 | | |
| 689 | | |
| 690 | | |
| 691 | | |
| 692 | | |
| 693 | | |
| 694 | 2588× | fn as_order(&self, deps: &dyn DepOracle) -> Option<Vec<AtomKey>> { |
| 695 | | |
| 696 | 2588× | let mut index: HashMap<Vertex, u32> = |
| 697 | 2588× | HashMap::with_capacity_and_hasher(self.atoms.len() + 2, Default::default()); |
| 698 | 2588× | let mut verts: Vec<Vertex> = Vec::with_capacity(self.atoms.len() + 2); |
| 699 | 2588× | verts.extend([Vertex::Start, Vertex::End]); |
| 700 | 2588× | index.insert(Vertex::Start, 0); |
| 701 | 2588× | index.insert(Vertex::End, 1); |
| 702 | 99564× | for (k, info) in &self.atoms { |
| 703 | 99564× | if info.content.is_none() { |
| 704 | 0× | continue; |
| 705 | 99564× | } |
| 706 | 99564× | if info.deleted_by.is_empty() { |
| 707 | 51539× | index.insert(Vertex::Atom(*k), verts.len() as u32); |
| 708 | 51539× | verts.push(Vertex::Atom(*k)); |
| 709 | 51539× | } else if self.is_ghost(k, info, deps) { |
| 710 | 350× | return None; |
| 711 | 47675× | } |
| 712 | | } |
| 713 | | |
| 714 | | |
| 715 | 2238× | let mut reach: HashMap<Vertex, Vec<u32>> = HashMap::default(); |
| 716 | 11111× | let contracted = |v: Vertex, reach: &mut HashMap<Vertex, Vec<u32>>| { |
| 717 | 11111× | let mut stack: Vec<(Vertex, bool)> = vec![(v, false)]; |
| 718 | 88844× | while let Some((x, done)) = stack.pop() { |
| 719 | 77733× | if reach.contains_key(&x) { |
| 720 | 10169× | continue; |
| 721 | 67564× | } |
| 722 | 67564× | let next = self.succ.get(&x).map(Edges::as_slice).unwrap_or_default(); |
| 723 | 67564× | if !done { |
| 724 | 33782× | stack.push((x, true)); |
| 725 | 45722× | for s in next { |
| 726 | 45722× | if !index.contains_key(s) && !reach.contains_key(s) { |
| 727 | 32840× | stack.push((*s, false)); |
| 728 | 32840× | } |
| 729 | | } |
| 730 | 33782× | continue; |
| 731 | 33782× | } |
| 732 | 33782× | let mut out: Vec<u32> = Vec::new(); |
| 733 | 45722× | for s in next { |
| 734 | 45722× | match index.get(s) { |
| 735 | 5605× | Some(i) => out.push(*i), |
| 736 | 40117× | None => out.extend(reach.get(s).into_iter().flatten()), |
| 737 | | } |
| 738 | | } |
| 739 | 33782× | out.sort_unstable(); |
| 740 | 33782× | out.dedup(); |
| 741 | 33782× | reach.insert(x, out); |
| 742 | | } |
| 743 | 11111× | }; |
| 744 | | |
| 745 | 2238× | let mut succ: Vec<u32> = Vec::with_capacity(verts.len() * 2); |
| 746 | 2238× | let mut starts: Vec<usize> = Vec::with_capacity(verts.len() + 1); |
| 747 | 2238× | let mut indeg: Vec<u32> = vec![0; verts.len()]; |
| 748 | 55235× | for v in &verts { |
| 749 | 55235× | let from = succ.len(); |
| 750 | 55235× | starts.push(from); |
| 751 | 64938× | for s in self.succ.get(v).map(Edges::as_slice).unwrap_or_default() { |
| 752 | 64938× | match index.get(s) { |
| 753 | 53827× | Some(i) => succ.push(*i), |
| 754 | 11111× | None => { |
| 755 | 11111× | contracted(*s, &mut reach); |
| 756 | 11111× | succ.extend(reach.get(s).into_iter().flatten()); |
| 757 | 11111× | } |
| 758 | | } |
| 759 | | } |
| 760 | 55235× | if succ.len() - from > 1 { |
| 761 | 6036× | succ[from..].sort_unstable(); |
| 762 | 6036× | let mut kept = from; |
| 763 | 18169× | for i in from..succ.len() { |
| 764 | 18169× | if i == from || succ[i] != succ[kept - 1] { |
| 765 | 12975× | succ[kept] = succ[i]; |
| 766 | 12975× | kept += 1; |
| 767 | 12975× | } |
| 768 | | } |
| 769 | 6036× | succ.truncate(kept); |
| 770 | 49199× | } |
| 771 | 59936× | for &w in &succ[from..] { |
| 772 | 59936× | indeg[w as usize] += 1; |
| 773 | 59936× | } |
| 774 | | } |
| 775 | 2238× | starts.push(succ.len()); |
| 776 | | |
| 777 | 2238× | let mut ready: Vec<u32> = (0..verts.len() as u32) |
| 778 | 55235× | .filter(|&i| indeg[i as usize] == 0) |
| 779 | 2238× | .collect(); |
| 780 | 2238× | let mut order: Vec<AtomKey> = Vec::with_capacity(verts.len()); |
| 781 | 2238× | let mut seen = 0; |
| 782 | 55288× | while let Some(v) = ready.pop() { |
| 783 | 53477× | if !ready.is_empty() { |
| 784 | 0× | return None; |
| 785 | 53477× | } |
| 786 | 53477× | seen += 1; |
| 787 | 53477× | if let Vertex::Atom(k) = verts[v as usize] { |
| 788 | 49428× | order.push(k); |
| 789 | 49428× | } |
| 790 | 58460× | for &w in &succ[starts[v as usize]..starts[v as usize + 1]] { |
| 791 | 58460× | indeg[w as usize] -= 1; |
| 792 | 58460× | if indeg[w as usize] == 0 { |
| 793 | 52093× | ready.push(w); |
| 794 | 52093× | } |
| 795 | | } |
| 796 | 53477× | if ready.len() > 1 { |
| 797 | 427× | return None; |
| 798 | 53050× | } |
| 799 | | } |
| 800 | 1811× | (seen == verts.len()).then_some(order) |
| 801 | 2588× | } |
| 802 | |
|
| 803 | | |
| 804 | | |
| 805 | 1270× | fn materialize_general(&self, deps: &dyn DepOracle) -> Materialized { |
| 806 | | |
| 807 | 1270× | let mut kept: HashSet<Vertex> = HashSet::default(); |
| 808 | 1270× | let mut ghosts: HashSet<AtomKey> = HashSet::default(); |
| 809 | 1270× | kept.insert(Vertex::Start); |
| 810 | 1270× | kept.insert(Vertex::End); |
| 811 | 315353× | for (k, info) in &self.atoms { |
| 812 | 315353× | if info.content.is_none() { |
| 813 | 0× | continue; |
| 814 | 315353× | } |
| 815 | 315353× | if info.deleted_by.is_empty() { |
| 816 | 7381× | kept.insert(Vertex::Atom(*k)); |
| 817 | 307972× | } else if self.is_ghost(k, info, deps) { |
| 818 | 480× | kept.insert(Vertex::Atom(*k)); |
| 819 | 480× | ghosts.insert(*k); |
| 820 | 307492× | } |
| 821 | | } |
| 822 | |
|
| 823 | | |
| 824 | 1270× | let mut memo = HashMap::default(); |
| 825 | 1270× | let mut rsucc: HashMap<Vertex, BTreeSet<Vertex>> = HashMap::default(); |
| 826 | 1270× | let mut indeg: HashMap<Vertex, usize> = HashMap::default(); |
| 827 | 10401× | for &u in &kept { |
| 828 | 10401× | indeg.entry(u).or_insert(0); |
| 829 | 10401× | let rs = self.reduced_succ(u, &kept, &mut memo); |
| 830 | 15620× | for &w in &rs { |
| 831 | 15620× | *indeg.entry(w).or_insert(0) += 1; |
| 832 | 15620× | } |
| 833 | 10401× | rsucc.insert(u, rs); |
| 834 | | } |
| 835 | |
|
| 836 | | |
| 837 | 1270× | let mut ready: BTreeSet<Vertex> = indeg |
| 838 | 1270× | .iter() |
| 839 | 10401× | .filter(|(_, &d)| d == 0) |
| 840 | 1270× | .map(|(v, _)| *v) |
| 841 | 1270× | .collect(); |
| 842 | 1270× | let mut side: HashMap<Vertex, u32> = HashMap::default(); |
| 843 | 1270× | let mut joins: HashSet<Vertex> = HashSet::default(); |
| 844 | 1270× | let mut next_side = 0u32; |
| 845 | 1270× | let mut plain: Vec<Vertex> = Vec::new(); |
| 846 | 1270× | let mut conflict: Option<BTreeMap<u32, Vec<Vertex>>> = None; |
| 847 | 1270× | let mut segments: Vec<Segment> = Vec::new(); |
| 848 | |
|
| 849 | 1863× | let flush_plain = |plain: &mut Vec<Vertex>, segments: &mut Vec<Segment>| { |
| 850 | 1863× | let atoms: Vec<AtomKey> = plain |
| 851 | 1863× | .iter() |
| 852 | 8866× | .filter_map(|v| { |
| 853 | 8866× | if let Vertex::Atom(k) = v { |
| 854 | 6326× | Some(*k) |
| 855 | | } else { |
| 856 | 2540× | None |
| 857 | | } |
| 858 | 8866× | }) |
| 859 | 1863× | .collect(); |
| 860 | 1863× | plain.clear(); |
| 861 | 1863× | if !atoms.is_empty() { |
| 862 | 1583× | segments.extend(split_zombies(atoms, &ghosts)); |
| 863 | 1583× | } |
| 864 | 1863× | }; |
| 865 | 1270× | let finish_conflict = |c: BTreeMap<u32, Vec<Vertex>>, segments: &mut Vec<Segment>| { |
| 866 | 593× | let sides: Vec<Vec<AtomKey>> = c |
| 867 | 593× | .into_values() |
| 868 | 1186× | .map(|vs| { |
| 869 | 1186× | vs.into_iter() |
| 870 | 1535× | .filter_map(|v| match v { |
| 871 | 1535× | Vertex::Atom(k) if !ghosts.contains(&k) => Some(k), |
| 872 | 54× | _ => None, |
| 873 | 1535× | }) |
| 874 | 1186× | .collect::<Vec<_>>() |
| 875 | 1186× | }) |
| 876 | 1186× | .filter(|s| !s.is_empty()) |
| 877 | 593× | .collect(); |
| 878 | 593× | match sides.len() { |
| 879 | 0× | 0 => {} |
| 880 | 0× | 1 => segments.push(Segment::Lines(sides.into_iter().next().unwrap())), |
| 881 | 593× | _ => segments.push(Segment::Conflict(sides)), |
| 882 | | } |
| 883 | 593× | }; |
| 884 | |
|
| 885 | 11671× | while !ready.is_empty() { |
| 886 | 10401× | if ready.len() >= 2 { |
| 887 | 650× | if conflict.is_none() { |
| 888 | 593× | flush_plain(&mut plain, &mut segments); |
| 889 | 593× | conflict = Some(BTreeMap::new()); |
| 890 | 593× | } |
| 891 | 1300× | for r in &ready { |
| 892 | 1300× | if !side.contains_key(r) { |
| 893 | 1186× | side.insert(*r, next_side); |
| 894 | 1186× | next_side += 1; |
| 895 | 1186× | } |
| 896 | | } |
| 897 | 9751× | } |
| 898 | 10401× | let v = ready.pop_first().unwrap(); |
| 899 | 10401× | let my_side = side.get(&v).copied(); |
| 900 | 10401× | match (&mut conflict, my_side) { |
| 901 | 1535× | (Some(c), Some(s)) => c.entry(s).or_default().push(v), |
| 902 | 593× | (Some(_), None) => { |
| 903 | 593× | let c = conflict.take().unwrap(); |
| 904 | 593× | finish_conflict(c, &mut segments); |
| 905 | 593× | plain.push(v); |
| 906 | 593× | } |
| 907 | 8273× | (None, _) => plain.push(v), |
| 908 | | } |
| 909 | 15620× | for &w in &rsucc[&v] { |
| 910 | 15620× | let d = indeg.get_mut(&w).unwrap(); |
| 911 | 15620× | *d -= 1; |
| 912 | 15620× | if conflict.is_some() && !joins.contains(&w) { |
| 913 | 1649× | match (my_side, side.get(&w).copied()) { |
| 914 | 982× | (Some(s), None) => { |
| 915 | 982× | side.insert(w, s); |
| 916 | 982× | } |
| 917 | 667× | (Some(s), Some(t)) if s != t => { |
| 918 | 633× | side.remove(&w); |
| 919 | 633× | joins.insert(w); |
| 920 | 633× | } |
| 921 | 0× | (None, Some(_)) => { |
| 922 | 0× | side.remove(&w); |
| 923 | 0× | joins.insert(w); |
| 924 | 0× | } |
| 925 | 34× | _ => {} |
| 926 | | } |
| 927 | 13971× | } |
| 928 | 15620× | if *d == 0 { |
| 929 | 9131× | ready.insert(w); |
| 930 | 9131× | } |
| 931 | | } |
| 932 | | } |
| 933 | 1270× | if let Some(c) = conflict.take() { |
| 934 | 0× | finish_conflict(c, &mut segments); |
| 935 | 1270× | } |
| 936 | 1270× | flush_plain(&mut plain, &mut segments); |
| 937 | 1270× | Materialized { segments } |
| 938 | 1270× | } |
| 939 | |
|
| 940 | | |
| 941 | | |
| 942 | 28973× | pub fn render(&self, m: &Materialized, label: &dyn Fn(&ChangeHash) -> String) -> Vec<u8> { |
| 943 | 28973× | let total = m.conflict_count(); |
| 944 | 28973× | let mut n = 0; |
| 945 | 28973× | let mut out: Vec<u8> = Vec::new(); |
| 946 | 30513× | let push_atoms = |out: &mut Vec<u8>, atoms: &[AtomKey]| { |
| 947 | 159514× | for a in atoms { |
| 948 | 159514× | if let Some(c) = self.content(a) { |
| 949 | 159514× | out.extend_from_slice(c); |
| 950 | 159514× | } |
| 951 | | } |
| 952 | 30513× | }; |
| 953 | 28973× | let newline_if_needed = |out: &mut Vec<u8>| { |
| 954 | 2109× | if !out.is_empty() && *out.last().unwrap() != b'\n' { |
| 955 | 0× | out.push(b'\n'); |
| 956 | 2109× | } |
| 957 | 2109× | }; |
| 958 | 29810× | for seg in &m.segments { |
| 959 | 29810× | match seg { |
| 960 | 29107× | Segment::Lines(a) => push_atoms(&mut out, a), |
| 961 | 429× | Segment::Conflict(sides) => { |
| 962 | 429× | n += 1; |
| 963 | 429× | let k = sides.len(); |
| 964 | 858× | for (i, s) in sides.iter().enumerate() { |
| 965 | 858× | newline_if_needed(&mut out); |
| 966 | 858× | let who = s.first().map(|a| label(&a.0)).unwrap_or_default(); |
| 967 | 858× | let marker = if i == 0 { "<<<<<<<" } else { "=======" }; |
| 968 | 858× | out.extend_from_slice( |
| 969 | 858× | format!("{marker} oxy conflict {n} of {total}, side {} of {k}: change {who}\n", i + 1).as_bytes(), |
| 970 | | ); |
| 971 | 858× | push_atoms(&mut out, s); |
| 972 | | } |
| 973 | 429× | newline_if_needed(&mut out); |
| 974 | 429× | out.extend_from_slice( |
| 975 | 429× | format!(">>>>>>> oxy conflict {n} of {total} end\n").as_bytes(), |
| 976 | | ); |
| 977 | | } |
| 978 | 274× | Segment::Zombie { deleted, inserted } => { |
| 979 | 274× | n += 1; |
| 980 | 274× | newline_if_needed(&mut out); |
| 981 | 274× | let deleter = deleted |
| 982 | 274× | .first() |
| 983 | 274× | .and_then(|a| self.deleters(a).next().copied()) |
| 984 | 274× | .map(|d| label(&d)) |
| 985 | 274× | .unwrap_or_default(); |
| 986 | 274× | let inserter = inserted.first().map(|a| label(&a.0)).unwrap_or_default(); |
| 987 | 274× | out.extend_from_slice( |
| 988 | 274× | format!("<<<<<<< oxy conflict {n} of {total}, side 1 of 2: deleted by change {deleter}\n-------\n").as_bytes(), |
| 989 | | ); |
| 990 | 274× | push_atoms(&mut out, deleted); |
| 991 | 274× | newline_if_needed(&mut out); |
| 992 | 274× | out.extend_from_slice( |
| 993 | 274× | format!( |
| 994 | 274× | "======= oxy conflict {n} of {total}, side 2 of 2: change {inserter}\n" |
| 995 | 274× | ) |
| 996 | 274× | .as_bytes(), |
| 997 | | ); |
| 998 | 274× | push_atoms(&mut out, inserted); |
| 999 | 274× | newline_if_needed(&mut out); |
| 1000 | 274× | out.extend_from_slice( |
| 1001 | 274× | format!(">>>>>>> oxy conflict {n} of {total} end\n").as_bytes(), |
| 1002 | | ); |
| 1003 | | } |
| 1004 | | } |
| 1005 | | } |
| 1006 | 28973× | out |
| 1007 | 28973× | } |
| 1008 | | } |
| 1009 | |
|
| 1010 | 35392× | fn vertex_value(v: &Vertex) -> crate::cbor::Value { |
| 1011 | | use crate::cbor::Value; |
| 1012 | 35392× | match v { |
| 1013 | 3598× | Vertex::Start => Value::Uint(0), |
| 1014 | 3489× | Vertex::End => Value::Uint(1), |
| 1015 | 28305× | Vertex::Atom((c, i)) => { |
| 1016 | 28305× | Value::Array(vec![Value::Bytes(c.0.to_vec()), Value::Uint(*i as u64)]) |
| 1017 | | } |
| 1018 | | } |
| 1019 | 35392× | } |
| 1020 | |
|
| 1021 | 10364× | fn vertex_from(v: &crate::cbor::Value) -> Result<Vertex, crate::objects::ObjectError> { |
| 1022 | | use crate::cbor::Value; |
| 1023 | | use crate::objects::ObjectError; |
| 1024 | 8702× | match v { |
| 1025 | 742× | Value::Uint(0) => Ok(Vertex::Start), |
| 1026 | 920× | Value::Uint(1) => Ok(Vertex::End), |
| 1027 | 8702× | Value::Array(a) if a.len() == 2 => Ok(Vertex::Atom(( |
| 1028 | 8702× | crate::objects::oref(&a[0], "vertex")?, |
| 1029 | 8702× | crate::objects::u32_(&a[1], "vertex")?, |
| 1030 | | ))), |
| 1031 | 0× | _ => Err(ObjectError::Schema( |
| 1032 | 0× | "vertex: expected 0, 1, or [hash, index]".into(), |
| 1033 | 0× | )), |
| 1034 | | } |
| 1035 | 10364× | } |
| 1036 | |
|
| 1037 | | impl FileGraph { |
| 1038 | | |
| 1039 | 1531× | pub fn to_value(&self) -> crate::cbor::Value { |
| 1040 | | use crate::cbor::Value; |
| 1041 | 1531× | let mut atoms: Vec<(&AtomKey, &AtomInfo)> = self.atoms.iter().collect(); |
| 1042 | 1531× | atoms.sort_by_key(|(k, _)| **k); |
| 1043 | 1531× | let mut edges: Vec<(Vertex, Vertex)> = self |
| 1044 | 1531× | .succ |
| 1045 | 1531× | .iter() |
| 1046 | 17696× | .flat_map(|(a, bs)| bs.iter().map(move |b| (*a, *b))) |
| 1047 | 1531× | .collect(); |
| 1048 | 1531× | edges.sort(); |
| 1049 | 1531× | Value::map([ |
| 1050 | | ( |
| 1051 | | 0, |
| 1052 | | Value::Array( |
| 1053 | 1531× | atoms |
| 1054 | 1531× | .into_iter() |
| 1055 | 12882× | .map(|((c, i), info)| { |
| 1056 | 12882× | Value::Array(vec![ |
| 1057 | 12882× | Value::Bytes(c.0.to_vec()), |
| 1058 | 12882× | Value::Uint(*i as u64), |
| 1059 | 12882× | info.content |
| 1060 | 12882× | .as_ref() |
| 1061 | 12882× | .map(|b| Value::Bytes(b.bytes().to_vec())) |
| 1062 | 12882× | .unwrap_or(Value::Null), |
| 1063 | | Value::Array( |
| 1064 | 12882× | info.deleted_by |
| 1065 | 12882× | .iter() |
| 1066 | 12882× | .map(|d| Value::Bytes(d.0.to_vec())) |
| 1067 | 12882× | .collect(), |
| 1068 | | ), |
| 1069 | | ]) |
| 1070 | 12882× | }) |
| 1071 | 1531× | .collect(), |
| 1072 | | ), |
| 1073 | | ), |
| 1074 | | ( |
| 1075 | | 1, |
| 1076 | | Value::Array( |
| 1077 | 1531× | edges |
| 1078 | 1531× | .iter() |
| 1079 | 17696× | .map(|(a, b)| Value::Array(vec![vertex_value(a), vertex_value(b)])) |
| 1080 | 1531× | .collect(), |
| 1081 | | ), |
| 1082 | | ), |
| 1083 | | ]) |
| 1084 | 1531× | } |
| 1085 | |
|
| 1086 | 339× | pub fn from_value(v: &crate::cbor::Value) -> Result<FileGraph, crate::objects::ObjectError> { |
| 1087 | | use crate::objects::{array, bytes, field, map, oref, u32_, ObjectError}; |
| 1088 | 339× | let m = map(v, "file graph")?; |
| 1089 | 339× | let mut g = FileGraph { |
| 1090 | 339× | atoms: HashMap::default(), |
| 1091 | 339× | succ: HashMap::default(), |
| 1092 | 339× | order: OrderSlot::default(), |
| 1093 | 339× | }; |
| 1094 | 339× | let atoms = array(field(m, 0, "atoms")?, "atoms")?; |
| 1095 | | |
| 1096 | 339× | let mut texts: Vec<&[u8]> = Vec::new(); |
| 1097 | 4124× | for a in atoms { |
| 1098 | 4124× | let e = array(a, "atom")?; |
| 1099 | 4124× | if e.len() == 4 && !e[2].is_null() { |
| 1100 | 4124× | texts.push(bytes(&e[2], "atom content")?); |
| 1101 | 0× | } |
| 1102 | | } |
| 1103 | 339× | let all: Lines = texts.into_iter().collect(); |
| 1104 | 339× | let mut next = 0u32; |
| 1105 | 4124× | for a in atoms { |
| 1106 | 4124× | let e = array(a, "atom")?; |
| 1107 | 4124× | if e.len() != 4 { |
| 1108 | 0× | return Err(ObjectError::Schema("atom: expected 4 fields".into())); |
| 1109 | 4124× | } |
| 1110 | 4124× | let key = (oref(&e[0], "atom change")?, u32_(&e[1], "atom index")?); |
| 1111 | 4124× | let content = if e[2].is_null() { |
| 1112 | 0× | None |
| 1113 | | } else { |
| 1114 | 4124× | next += 1; |
| 1115 | 4124× | Some(LineRef::new(all.clone(), next - 1)) |
| 1116 | | }; |
| 1117 | 4124× | let deleted_by = array(&e[3], "deleters")? |
| 1118 | 4124× | .iter() |
| 1119 | 4124× | .map(|d| oref(d, "deleter")) |
| 1120 | 4124× | .collect::<Result<BTreeSet<_>, _>>()?; |
| 1121 | 4124× | g.atoms.insert( |
| 1122 | 4124× | key, |
| 1123 | 4124× | AtomInfo { |
| 1124 | 4124× | content, |
| 1125 | 4124× | deleted_by, |
| 1126 | 4124× | }, |
| 1127 | | ); |
| 1128 | | } |
| 1129 | 5182× | for e in array(field(m, 1, "edges")?, "edges")? { |
| 1130 | 5182× | let pair = array(e, "edge")?; |
| 1131 | 5182× | if pair.len() != 2 { |
| 1132 | 0× | return Err(ObjectError::Schema("edge: expected [from, to]".into())); |
| 1133 | 5182× | } |
| 1134 | 5182× | g.edge(vertex_from(&pair[0])?, vertex_from(&pair[1])?); |
| 1135 | | } |
| 1136 | 339× | Ok(g) |
| 1137 | 339× | } |
| 1138 | | } |
| 1139 | |
|
| 1140 | |
|
| 1141 | 1583× | fn split_zombies(atoms: Vec<AtomKey>, ghosts: &HashSet<AtomKey>) -> Vec<Segment> { |
| 1142 | 1583× | let mut out = Vec::new(); |
| 1143 | 1583× | let mut lines: Vec<AtomKey> = Vec::new(); |
| 1144 | 1583× | let mut i = 0; |
| 1145 | 7439× | while i < atoms.len() { |
| 1146 | 5856× | if !ghosts.contains(&atoms[i]) { |
| 1147 | 5430× | lines.push(atoms[i]); |
| 1148 | 5430× | i += 1; |
| 1149 | 5430× | continue; |
| 1150 | 426× | } |
| 1151 | 426× | if !lines.is_empty() { |
| 1152 | 244× | out.push(Segment::Lines(std::mem::take(&mut lines))); |
| 1153 | 244× | } |
| 1154 | 426× | let mut deleted = Vec::new(); |
| 1155 | 852× | while i < atoms.len() && ghosts.contains(&atoms[i]) { |
| 1156 | 426× | deleted.push(atoms[i]); |
| 1157 | 426× | i += 1; |
| 1158 | 426× | } |
| 1159 | 426× | let mut inserted = Vec::new(); |
| 1160 | 426× | if i < atoms.len() { |
| 1161 | 406× | let ch = atoms[i].0; |
| 1162 | 876× | while i < atoms.len() && atoms[i].0 == ch && !ghosts.contains(&atoms[i]) { |
| 1163 | 470× | inserted.push(atoms[i]); |
| 1164 | 470× | i += 1; |
| 1165 | 470× | } |
| 1166 | 20× | } |
| 1167 | 426× | out.push(Segment::Zombie { deleted, inserted }); |
| 1168 | | } |
| 1169 | 1583× | if !lines.is_empty() { |
| 1170 | 1354× | out.push(Segment::Lines(lines)); |
| 1171 | 1354× | } |
| 1172 | 1583× | out |
| 1173 | 1583× | } |
| 1174 | |
|
| 1175 | | #[cfg(test)] |
| 1176 | | mod tests { |
| 1177 | | use super::*; |
| 1178 | | use crate::hash::ObjectRef; |
| 1179 | |
|
| 1180 | 156× | fn h(n: u8) -> ChangeHash { |
| 1181 | 156× | ObjectRef([n; 32]) |
| 1182 | 156× | } |
| 1183 | 792× | fn lines(v: &[&str]) -> Lines { |
| 1184 | 1741× | v.iter().map(|s| s.as_bytes()).collect() |
| 1185 | 792× | } |
| 1186 | 66× | fn text(g: &FileGraph, m: &Materialized) -> String { |
| 1187 | 208× | String::from_utf8(g.render(m, &|c| c.short(4))).unwrap() |
| 1188 | 66× | } |
| 1189 | |
|
| 1190 | | struct Deps(HashMap<ChangeHash, Vec<ChangeHash>>); |
| 1191 | | impl DepOracle for Deps { |
| 1192 | 1705× | fn depends_on(&self, a: &ChangeHash, b: &ChangeHash) -> bool { |
| 1193 | 1705× | if a == b { |
| 1194 | 0× | return true; |
| 1195 | 1705× | } |
| 1196 | 1705× | let mut stack = vec![*a]; |
| 1197 | 1705× | let mut seen = HashSet::default(); |
| 1198 | 50101× | while let Some(x) = stack.pop() { |
| 1199 | 49787× | if let Some(ds) = self.0.get(&x) { |
| 1200 | 7887494× | for d in ds { |
| 1201 | 7887494× | if d == b { |
| 1202 | 1391× | return true; |
| 1203 | 7886103× | } |
| 1204 | 7886103× | if seen.insert(*d) { |
| 1205 | 362101× | stack.push(*d); |
| 1206 | 7524002× | } |
| 1207 | | } |
| 1208 | 314× | } |
| 1209 | | } |
| 1210 | 314× | false |
| 1211 | 1705× | } |
| 1212 | | } |
| 1213 | |
|
| 1214 | | |
| 1215 | | struct Linear; |
| 1216 | | impl DepOracle for Linear { |
| 1217 | 49× | fn depends_on(&self, a: &ChangeHash, b: &ChangeHash) -> bool { |
| 1218 | 49× | a.0[..2] >= b.0[..2] |
| 1219 | 49× | } |
| 1220 | | } |
| 1221 | |
|
| 1222 | | |
| 1223 | | |
| 1224 | 1× | fn edited_file(n: usize, revs: usize, per: usize) -> FileGraph { |
| 1225 | 401× | let hn = |i: usize| { |
| 1226 | 401× | let mut b = [0u8; 32]; |
| 1227 | 401× | b[..2].copy_from_slice(&(i as u16).to_be_bytes()); |
| 1228 | 401× | ObjectRef(b) |
| 1229 | 401× | }; |
| 1230 | 1× | let mut g = FileGraph::new(); |
| 1231 | 500× | let first: Vec<String> = (0..n).map(|i| format!("line {i}\n")).collect(); |
| 1232 | 1× | let first: Vec<&str> = first.iter().map(String::as_str).collect(); |
| 1233 | 1× | g.insert(hn(1), 0, Vertex::Start, Vertex::End, &lines(&first)); |
| 1234 | 10× | for r in 2..revs + 2 { |
| 1235 | 10× | let order = g.materialize(&Linear).live_atoms(); |
| 1236 | 10× | let step = order.len() / per; |
| 1237 | 200× | for j in 0..per { |
| 1238 | 200× | let at = j * step + (r % step.max(1)); |
| 1239 | 200× | let k = order[at]; |
| 1240 | 200× | g.delete( |
| 1241 | 200× | hn(r), |
| 1242 | 200× | &AtomRange { |
| 1243 | 200× | change: k.0, |
| 1244 | 200× | from: k.1, |
| 1245 | 200× | to: k.1 + 1, |
| 1246 | 200× | }, |
| 1247 | | ); |
| 1248 | 200× | let after = if at == 0 { |
| 1249 | 0× | Vertex::Start |
| 1250 | | } else { |
| 1251 | 200× | Vertex::Atom(order[at - 1]) |
| 1252 | | }; |
| 1253 | 200× | let before = order |
| 1254 | 200× | .get(at + 1) |
| 1255 | 200× | .map(|k| Vertex::Atom(*k)) |
| 1256 | 200× | .unwrap_or(Vertex::End); |
| 1257 | 200× | g.insert(hn(r), j as u32, after, before, &lines(&["new\n"])); |
| 1258 | | } |
| 1259 | | } |
| 1260 | 1× | g |
| 1261 | 1× | } |
| 1262 | |
|
| 1263 | | |
| 1264 | | #[test] |
| 1265 | 1× | fn the_order_shortcut_agrees_with_the_general_order() { |
| 1266 | 1× | let g = edited_file(500, 10, 20); |
| 1267 | 1× | assert!(g.as_chain().is_none()); |
| 1268 | 1× | let fast = g.as_order(&Linear).expect("a single order"); |
| 1269 | 1× | assert_eq!( |
| 1270 | 1× | Materialized { |
| 1271 | 1× | segments: vec![Segment::Lines(fast)] |
| 1272 | 1× | }, |
| 1273 | 1× | g.materialize_general(&Linear) |
| 1274 | | ); |
| 1275 | | |
| 1276 | | |
| 1277 | 1× | let mut g = FileGraph::new(); |
| 1278 | 1× | g.insert(h(1), 0, Vertex::Start, Vertex::End, &lines(&["a\n"])); |
| 1279 | 1× | let a = Vertex::Atom((h(1), 0)); |
| 1280 | 1× | g.insert(h(2), 0, a, Vertex::End, &lines(&["b\n"])); |
| 1281 | 1× | g.insert(h(3), 0, a, Vertex::End, &lines(&["c\n"])); |
| 1282 | 1× | assert!(g.as_order(&NoDeps).is_none()); |
| 1283 | 1× | assert!(g.materialize(&NoDeps).is_conflicted()); |
| 1284 | 1× | } |
| 1285 | |
|
| 1286 | | |
| 1287 | | |
| 1288 | 491× | fn apply_spliced( |
| 1289 | 491× | g: &mut FileGraph, |
| 1290 | 491× | h: ChangeHash, |
| 1291 | 491× | inserts: &[(Vertex, Vertex, usize)], |
| 1292 | 491× | deleted: &[AtomKey], |
| 1293 | 491× | ) { |
| 1294 | 491× | let order = g.take_order(); |
| 1295 | 491× | let mut runs = Vec::new(); |
| 1296 | 491× | let mut next = 0u32; |
| 1297 | 554× | for (i, &(a, b, n)) in inserts.iter().enumerate() { |
| 1298 | 947× | let text: Vec<String> = (0..n).map(|j| format!("{i}.{j}\n")).collect(); |
| 1299 | 554× | let text: Vec<&str> = text.iter().map(String::as_str).collect(); |
| 1300 | 554× | g.insert(h, next, a, b, &lines(&text)); |
| 1301 | 554× | runs.push((a, b, next, n as u32)); |
| 1302 | 554× | next += n as u32; |
| 1303 | | } |
| 1304 | 1003× | for k in deleted { |
| 1305 | 1003× | g.delete( |
| 1306 | 1003× | h, |
| 1307 | 1003× | &AtomRange { |
| 1308 | 1003× | change: k.0, |
| 1309 | 1003× | from: k.1, |
| 1310 | 1003× | to: k.1 + 1, |
| 1311 | 1003× | }, |
| 1312 | 1003× | ); |
| 1313 | 1003× | } |
| 1314 | 491× | if let Some(o) = order { |
| 1315 | 458× | g.keep_spliced(o, h, &runs, deleted); |
| 1316 | 458× | } |
| 1317 | 491× | } |
| 1318 | |
|
| 1319 | | |
| 1320 | | |
| 1321 | 457× | fn random_edits( |
| 1322 | 457× | l: &[AtomKey], |
| 1323 | 457× | rnd: &mut impl FnMut(usize) -> usize, |
| 1324 | 457× | ) -> (Vec<(Vertex, Vertex, usize)>, Vec<AtomKey>) { |
| 1325 | 457× | let (mut inserts, mut deleted) = (Vec::new(), Vec::new()); |
| 1326 | 457× | let mut i = rnd(3); |
| 1327 | 1247× | while i <= l.len() { |
| 1328 | 790× | let del = rnd(3).min(l.len() - i); |
| 1329 | 790× | let add = rnd(3); |
| 1330 | 790× | let after = if i == 0 { |
| 1331 | 169× | Vertex::Start |
| 1332 | | } else { |
| 1333 | 621× | Vertex::Atom(l[i - 1]) |
| 1334 | | }; |
| 1335 | 790× | let before = l.get(i).map(|k| Vertex::Atom(*k)).unwrap_or(Vertex::End); |
| 1336 | 790× | deleted.extend(&l[i..i + del]); |
| 1337 | 790× | if add > 0 { |
| 1338 | 522× | inserts.push((after, before, add)); |
| 1339 | 522× | } |
| 1340 | 790× | i += del + 1 + rnd(6); |
| 1341 | | } |
| 1342 | 457× | (inserts, deleted) |
| 1343 | 457× | } |
| 1344 | |
|
| 1345 | | |
| 1346 | | |
| 1347 | | |
| 1348 | | #[test] |
| 1349 | 1× | fn the_kept_order_follows_edits() { |
| 1350 | 490× | let hn = |i: usize| { |
| 1351 | 490× | let mut b = [0u8; 32]; |
| 1352 | 490× | b[..2].copy_from_slice(&(i as u16).to_be_bytes()); |
| 1353 | 490× | ObjectRef(b) |
| 1354 | 490× | }; |
| 1355 | 1× | let mut seed = 7u64; |
| 1356 | 2827× | let mut rnd = move |n: usize| { |
| 1357 | 2827× | seed = seed |
| 1358 | 2827× | .wrapping_mul(6364136223846793005) |
| 1359 | 2827× | .wrapping_add(1442695040888963407); |
| 1360 | 2827× | ((seed >> 33) as usize) % n.max(1) |
| 1361 | 2827× | }; |
| 1362 | 1× | let mut deps = Deps(HashMap::default()); |
| 1363 | 1× | let mut g = FileGraph::new(); |
| 1364 | 40× | let first: Vec<String> = (0..40).map(|i| format!("{i}\n")).collect(); |
| 1365 | 1× | let first: Vec<&str> = first.iter().map(String::as_str).collect(); |
| 1366 | 1× | g.insert(hn(1), 0, Vertex::Start, Vertex::End, &lines(&first)); |
| 1367 | 1× | let mut known: Vec<ChangeHash> = vec![hn(1)]; |
| 1368 | 1× | let mut c = 2; |
| 1369 | 1× | let (mut spliced, mut conflicted) = (0, 0); |
| 1370 | 400× | for step in 0..400 { |
| 1371 | 400× | let l = g.materialize(&deps).live_atoms(); |
| 1372 | | |
| 1373 | | |
| 1374 | 400× | let pair = step % 7 == 3; |
| 1375 | 400× | let base = known.clone(); |
| 1376 | 400× | for _ in 0..if pair { 2 } else { 1 } { |
| 1377 | 457× | let h = hn(c); |
| 1378 | 457× | c += 1; |
| 1379 | 457× | let (inserts, deleted) = random_edits(&l, &mut rnd); |
| 1380 | 457× | deps.0.insert(h, base.clone()); |
| 1381 | 457× | apply_spliced(&mut g, h, &inserts, &deleted); |
| 1382 | 457× | known.push(h); |
| 1383 | 457× | if g.order.get().is_some() { |
| 1384 | 433× | spliced += 1; |
| 1385 | 433× | } |
| 1386 | 457× | let m = g.materialize(&deps); |
| 1387 | 457× | assert_eq!(m, g.materialize_general(&deps), "step {step}"); |
| 1388 | 457× | if m.is_conflicted() { |
| 1389 | 31× | conflicted += 1; |
| 1390 | 426× | } |
| 1391 | | } |
| 1392 | | |
| 1393 | | |
| 1394 | 400× | if g.materialize(&deps).is_conflicted() { |
| 1395 | 31× | let h = hn(c); |
| 1396 | 31× | c += 1; |
| 1397 | 31× | let all = g.materialize(&deps).live_atoms(); |
| 1398 | 31× | deps.0.insert(h, known.clone()); |
| 1399 | 31× | apply_spliced(&mut g, h, &[(Vertex::Start, Vertex::End, 5)], &all); |
| 1400 | 31× | known.push(h); |
| 1401 | 31× | assert_eq!(g.materialize(&deps), g.materialize_general(&deps)); |
| 1402 | 369× | } |
| 1403 | | } |
| 1404 | 1× | assert!( |
| 1405 | 1× | spliced > 100 && conflicted > 0, |
| 1406 | | "{spliced} spliced, {conflicted} conflicted" |
| 1407 | | ); |
| 1408 | 1× | } |
| 1409 | |
|
| 1410 | | |
| 1411 | | |
| 1412 | | |
| 1413 | | #[test] |
| 1414 | 1× | fn the_kept_order_takes_no_line_that_arrives_deleted() { |
| 1415 | 1× | let mut deps = Deps(HashMap::default()); |
| 1416 | 1× | let mut g = FileGraph::new(); |
| 1417 | 1× | g.insert( |
| 1418 | 1× | h(1), |
| 1419 | | 0, |
| 1420 | 1× | Vertex::Start, |
| 1421 | 1× | Vertex::End, |
| 1422 | 1× | &lines(&["a\n", "b\n", "c\n"]), |
| 1423 | | ); |
| 1424 | | |
| 1425 | 1× | apply_spliced(&mut g, h(2), &[], &[(h(1), 2)]); |
| 1426 | 1× | deps.0.insert(h(2), vec![h(1)]); |
| 1427 | 1× | let _ = g.materialize(&deps); |
| 1428 | 1× | assert!(g.order.get().is_some()); |
| 1429 | 1× | let (a, b) = (Vertex::Atom((h(1), 0)), Vertex::Atom((h(1), 1))); |
| 1430 | | |
| 1431 | 1× | deps.0.insert(h(3), vec![h(2)]); |
| 1432 | 1× | deps.0.insert(h(4), vec![h(3)]); |
| 1433 | 1× | apply_spliced(&mut g, h(4), &[], &[(h(3), 0)]); |
| 1434 | 1× | apply_spliced(&mut g, h(3), &[(a, b, 1)], &[]); |
| 1435 | 1× | let m = g.materialize(&deps); |
| 1436 | 1× | assert_eq!(m, g.materialize_general(&deps)); |
| 1437 | 1× | assert_eq!(text(&g, &m), "a\nb\n"); |
| 1438 | 1× | } |
| 1439 | |
|
| 1440 | | |
| 1441 | | #[test] |
| 1442 | | #[ignore] |
| 1443 | 0× | fn bench_materialize_edited_file() { |
| 1444 | 0× | let g = edited_file(20_000, 50, 100); |
| 1445 | 0× | let t = std::time::Instant::now(); |
| 1446 | 0× | let mut n = 0; |
| 1447 | 0× | for _ in 0..20 { |
| 1448 | 0× | n += g.materialize(&Linear).live_atoms().len(); |
| 1449 | 0× | } |
| 1450 | 0× | eprintln!( |
| 1451 | | "materialize: {:.2} ms each ({n} atoms)", |
| 1452 | 0× | t.elapsed().as_secs_f64() * 1000.0 / 20.0 |
| 1453 | | ); |
| 1454 | 0× | } |
| 1455 | |
|
| 1456 | | |
| 1457 | | |
| 1458 | | #[test] |
| 1459 | 1× | fn the_chain_shortcut_agrees_with_the_general_order() { |
| 1460 | 1× | let mut g = FileGraph::new(); |
| 1461 | 1× | g.insert( |
| 1462 | 1× | h(1), |
| 1463 | | 0, |
| 1464 | 1× | Vertex::Start, |
| 1465 | 1× | Vertex::End, |
| 1466 | 1× | &lines(&["a\n", "b\n", "c\n"]), |
| 1467 | | ); |
| 1468 | 1× | assert!(g.as_chain().is_some()); |
| 1469 | 1× | assert_eq!(g.materialize(&NoDeps), g.materialize_general(&NoDeps)); |
| 1470 | | |
| 1471 | 1× | g.insert( |
| 1472 | 1× | h(2), |
| 1473 | | 0, |
| 1474 | 1× | Vertex::Atom((h(1), 2)), |
| 1475 | 1× | Vertex::End, |
| 1476 | 1× | &lines(&["d\n"]), |
| 1477 | | ); |
| 1478 | 1× | assert!(g.as_chain().is_some()); |
| 1479 | 1× | assert_eq!(g.materialize(&NoDeps), g.materialize_general(&NoDeps)); |
| 1480 | | |
| 1481 | 1× | g.insert( |
| 1482 | 1× | h(4), |
| 1483 | | 0, |
| 1484 | 1× | Vertex::Atom((h(1), 0)), |
| 1485 | 1× | Vertex::Atom((h(1), 1)), |
| 1486 | 1× | &lines(&["x\n"]), |
| 1487 | | ); |
| 1488 | 1× | assert!(g.as_chain().is_none()); |
| 1489 | 1× | assert_eq!(text(&g, &g.materialize(&NoDeps)), "a\nx\nb\nc\nd\n"); |
| 1490 | | |
| 1491 | 1× | let mut c = FileGraph::new(); |
| 1492 | 1× | c.insert(h(1), 0, Vertex::Start, Vertex::End, &lines(&["a\n"])); |
| 1493 | 1× | c.insert(h(2), 0, Vertex::Start, Vertex::End, &lines(&["b\n"])); |
| 1494 | 1× | assert!(c.as_chain().is_none()); |
| 1495 | 1× | assert!(c.materialize(&NoDeps).is_conflicted()); |
| 1496 | | |
| 1497 | 1× | let mut d = FileGraph::new(); |
| 1498 | 1× | d.insert(h(1), 0, Vertex::Start, Vertex::End, &lines(&["a\n", "b\n"])); |
| 1499 | 1× | d.delete( |
| 1500 | 1× | h(3), |
| 1501 | 1× | &AtomRange { |
| 1502 | 1× | change: h(1), |
| 1503 | 1× | from: 0, |
| 1504 | 1× | to: 1, |
| 1505 | 1× | }, |
| 1506 | | ); |
| 1507 | 1× | assert!(d.as_chain().is_none()); |
| 1508 | 1× | assert_eq!(d.materialize(&NoDeps), d.materialize_general(&NoDeps)); |
| 1509 | | |
| 1510 | 1× | let e = FileGraph::new(); |
| 1511 | 1× | assert_eq!(e.materialize(&NoDeps), e.materialize_general(&NoDeps)); |
| 1512 | 1× | } |
| 1513 | |
|
| 1514 | | #[test] |
| 1515 | 1× | fn sequential_inserts_and_deletes() { |
| 1516 | 1× | let mut g = FileGraph::new(); |
| 1517 | 1× | g.insert( |
| 1518 | 1× | h(1), |
| 1519 | | 0, |
| 1520 | 1× | Vertex::Start, |
| 1521 | 1× | Vertex::End, |
| 1522 | 1× | &lines(&["a\n", "b\n", "c\n"]), |
| 1523 | | ); |
| 1524 | 1× | let m = g.materialize(&NoDeps); |
| 1525 | 1× | assert!(!m.is_conflicted()); |
| 1526 | 1× | assert_eq!(text(&g, &m), "a\nb\nc\n"); |
| 1527 | |
|
| 1528 | | |
| 1529 | 1× | g.insert( |
| 1530 | 1× | h(2), |
| 1531 | | 0, |
| 1532 | 1× | Vertex::Atom((h(1), 1)), |
| 1533 | 1× | Vertex::Atom((h(1), 2)), |
| 1534 | 1× | &lines(&["x\n"]), |
| 1535 | | ); |
| 1536 | 1× | g.delete( |
| 1537 | 1× | h(2), |
| 1538 | 1× | &AtomRange { |
| 1539 | 1× | change: h(1), |
| 1540 | 1× | from: 0, |
| 1541 | 1× | to: 1, |
| 1542 | 1× | }, |
| 1543 | | ); |
| 1544 | 1× | let deps = Deps([(h(2), vec![h(1)])].into_iter().collect()); |
| 1545 | 1× | let m = g.materialize(&deps); |
| 1546 | 1× | assert!(!m.is_conflicted()); |
| 1547 | 1× | assert_eq!(text(&g, &m), "b\nx\nc\n"); |
| 1548 | 1× | assert_eq!(m.live_atoms(), vec![(h(1), 1), (h(2), 0), (h(1), 2)]); |
| 1549 | 1× | } |
| 1550 | |
|
| 1551 | | #[test] |
| 1552 | 1× | fn concurrent_inserts_at_same_anchor_conflict() { |
| 1553 | 1× | let mut g = FileGraph::new(); |
| 1554 | 1× | g.insert(h(1), 0, Vertex::Start, Vertex::End, &lines(&["a\n", "b\n"])); |
| 1555 | 1× | g.insert( |
| 1556 | 1× | h(2), |
| 1557 | | 0, |
| 1558 | 1× | Vertex::Atom((h(1), 0)), |
| 1559 | 1× | Vertex::Atom((h(1), 1)), |
| 1560 | 1× | &lines(&["from-2\n"]), |
| 1561 | | ); |
| 1562 | 1× | g.insert( |
| 1563 | 1× | h(3), |
| 1564 | | 0, |
| 1565 | 1× | Vertex::Atom((h(1), 0)), |
| 1566 | 1× | Vertex::Atom((h(1), 1)), |
| 1567 | 1× | &lines(&["from-3\n", "more-3\n"]), |
| 1568 | | ); |
| 1569 | 1× | let deps = Deps( |
| 1570 | 1× | [(h(2), vec![h(1)]), (h(3), vec![h(1)])] |
| 1571 | 1× | .into_iter() |
| 1572 | 1× | .collect(), |
| 1573 | 1× | ); |
| 1574 | 1× | let m = g.materialize(&deps); |
| 1575 | 1× | assert!(m.is_conflicted()); |
| 1576 | 1× | assert_eq!(m.conflict_count(), 1); |
| 1577 | 1× | assert_eq!( |
| 1578 | | m.segments, |
| 1579 | 1× | vec![ |
| 1580 | 1× | Segment::Lines(vec![(h(1), 0)]), |
| 1581 | 1× | Segment::Conflict(vec![vec![(h(2), 0)], vec![(h(3), 0), (h(3), 1)]]), |
| 1582 | 1× | Segment::Lines(vec![(h(1), 1)]), |
| 1583 | | ] |
| 1584 | | ); |
| 1585 | 1× | let t = text(&g, &m); |
| 1586 | 1× | assert!(t.starts_with("a\n<<<<<<< oxy conflict 1 of 1, side 1 of 2: change 0202\nfrom-2\n======= oxy conflict 1 of 1, side 2 of 2: change 0303\nfrom-3\nmore-3\n>>>>>>> oxy conflict 1 of 1 end\nb\n"), "{t}"); |
| 1587 | 1× | } |
| 1588 | |
|
| 1589 | | #[test] |
| 1590 | 1× | fn concurrent_inserts_at_different_anchors_do_not_conflict() { |
| 1591 | 1× | let mut g = FileGraph::new(); |
| 1592 | 1× | g.insert( |
| 1593 | 1× | h(1), |
| 1594 | | 0, |
| 1595 | 1× | Vertex::Start, |
| 1596 | 1× | Vertex::End, |
| 1597 | 1× | &lines(&["a\n", "b\n", "c\n"]), |
| 1598 | | ); |
| 1599 | 1× | g.insert( |
| 1600 | 1× | h(2), |
| 1601 | | 0, |
| 1602 | 1× | Vertex::Start, |
| 1603 | 1× | Vertex::Atom((h(1), 0)), |
| 1604 | 1× | &lines(&["top\n"]), |
| 1605 | | ); |
| 1606 | 1× | g.insert( |
| 1607 | 1× | h(3), |
| 1608 | | 0, |
| 1609 | 1× | Vertex::Atom((h(1), 2)), |
| 1610 | 1× | Vertex::End, |
| 1611 | 1× | &lines(&["bottom\n"]), |
| 1612 | | ); |
| 1613 | 1× | let deps = Deps( |
| 1614 | 1× | [(h(2), vec![h(1)]), (h(3), vec![h(1)])] |
| 1615 | 1× | .into_iter() |
| 1616 | 1× | .collect(), |
| 1617 | 1× | ); |
| 1618 | 1× | let m = g.materialize(&deps); |
| 1619 | 1× | assert!(!m.is_conflicted()); |
| 1620 | 1× | assert_eq!(text(&g, &m), "top\na\nb\nc\nbottom\n"); |
| 1621 | 1× | } |
| 1622 | |
|
| 1623 | | #[test] |
| 1624 | 1× | fn concurrent_deletes_of_the_same_line_agree() { |
| 1625 | 1× | let mut g = FileGraph::new(); |
| 1626 | 1× | g.insert(h(1), 0, Vertex::Start, Vertex::End, &lines(&["a\n", "b\n"])); |
| 1627 | 1× | g.delete( |
| 1628 | 1× | h(2), |
| 1629 | 1× | &AtomRange { |
| 1630 | 1× | change: h(1), |
| 1631 | 1× | from: 0, |
| 1632 | 1× | to: 1, |
| 1633 | 1× | }, |
| 1634 | | ); |
| 1635 | 1× | g.delete( |
| 1636 | 1× | h(3), |
| 1637 | 1× | &AtomRange { |
| 1638 | 1× | change: h(1), |
| 1639 | 1× | from: 0, |
| 1640 | 1× | to: 1, |
| 1641 | 1× | }, |
| 1642 | | ); |
| 1643 | 1× | let deps = Deps( |
| 1644 | 1× | [(h(2), vec![h(1)]), (h(3), vec![h(1)])] |
| 1645 | 1× | .into_iter() |
| 1646 | 1× | .collect(), |
| 1647 | 1× | ); |
| 1648 | 1× | let m = g.materialize(&deps); |
| 1649 | 1× | assert!(!m.is_conflicted()); |
| 1650 | 1× | assert_eq!(text(&g, &m), "b\n"); |
| 1651 | 1× | } |
| 1652 | |
|
| 1653 | | #[test] |
| 1654 | 1× | fn delete_insert_conflict_is_a_zombie() { |
| 1655 | 1× | let mut g = FileGraph::new(); |
| 1656 | 1× | g.insert(h(1), 0, Vertex::Start, Vertex::End, &lines(&["a\n", "b\n"])); |
| 1657 | | |
| 1658 | 1× | g.delete( |
| 1659 | 1× | h(2), |
| 1660 | 1× | &AtomRange { |
| 1661 | 1× | change: h(1), |
| 1662 | 1× | from: 0, |
| 1663 | 1× | to: 1, |
| 1664 | 1× | }, |
| 1665 | | ); |
| 1666 | 1× | g.insert( |
| 1667 | 1× | h(3), |
| 1668 | | 0, |
| 1669 | 1× | Vertex::Atom((h(1), 0)), |
| 1670 | 1× | Vertex::Atom((h(1), 1)), |
| 1671 | 1× | &lines(&["after-a\n"]), |
| 1672 | | ); |
| 1673 | 1× | let deps = Deps( |
| 1674 | 1× | [(h(2), vec![h(1)]), (h(3), vec![h(1)])] |
| 1675 | 1× | .into_iter() |
| 1676 | 1× | .collect(), |
| 1677 | 1× | ); |
| 1678 | 1× | let m = g.materialize(&deps); |
| 1679 | 1× | assert!(m.is_conflicted()); |
| 1680 | 1× | assert_eq!( |
| 1681 | | m.segments, |
| 1682 | 1× | vec![ |
| 1683 | 1× | Segment::Zombie { |
| 1684 | 1× | deleted: vec![(h(1), 0)], |
| 1685 | 1× | inserted: vec![(h(3), 0)] |
| 1686 | 1× | }, |
| 1687 | 1× | Segment::Lines(vec![(h(1), 1)]), |
| 1688 | | ] |
| 1689 | | ); |
| 1690 | 1× | let t = text(&g, &m); |
| 1691 | 1× | assert!(t.contains("-------\na\n======="), "{t}"); |
| 1692 | 1× | assert!(t.contains("after-a\n>>>>>>>"), "{t}"); |
| 1693 | |
|
| 1694 | | |
| 1695 | 1× | let deps = Deps( |
| 1696 | 1× | [(h(2), vec![h(1), h(3)]), (h(3), vec![h(1)])] |
| 1697 | 1× | .into_iter() |
| 1698 | 1× | .collect(), |
| 1699 | 1× | ); |
| 1700 | 1× | let m = g.materialize(&deps); |
| 1701 | 1× | assert!(!m.is_conflicted()); |
| 1702 | 1× | assert_eq!(text(&g, &m), "after-a\nb\n"); |
| 1703 | 1× | } |
| 1704 | |
|
| 1705 | | #[test] |
| 1706 | 1× | fn insert_after_a_line_deleted_by_a_dependency_is_not_a_conflict() { |
| 1707 | | |
| 1708 | | |
| 1709 | 1× | let mut g = FileGraph::new(); |
| 1710 | 1× | g.insert(h(1), 0, Vertex::Start, Vertex::End, &lines(&["a\n", "b\n"])); |
| 1711 | 1× | g.delete( |
| 1712 | 1× | h(2), |
| 1713 | 1× | &AtomRange { |
| 1714 | 1× | change: h(1), |
| 1715 | 1× | from: 1, |
| 1716 | 1× | to: 2, |
| 1717 | 1× | }, |
| 1718 | | ); |
| 1719 | 1× | g.insert( |
| 1720 | 1× | h(3), |
| 1721 | | 0, |
| 1722 | 1× | Vertex::Atom((h(1), 1)), |
| 1723 | 1× | Vertex::End, |
| 1724 | 1× | &lines(&["c\n"]), |
| 1725 | | ); |
| 1726 | 1× | let deps = Deps( |
| 1727 | 1× | [(h(2), vec![h(1)]), (h(3), vec![h(2)])] |
| 1728 | 1× | .into_iter() |
| 1729 | 1× | .collect(), |
| 1730 | 1× | ); |
| 1731 | 1× | let m = g.materialize(&deps); |
| 1732 | 1× | assert!(!m.is_conflicted(), "{:?}", m.segments); |
| 1733 | 1× | assert_eq!(text(&g, &m), "a\nc\n"); |
| 1734 | | |
| 1735 | | |
| 1736 | 1× | let mut g = FileGraph::new(); |
| 1737 | 1× | g.insert(h(1), 0, Vertex::Start, Vertex::End, &lines(&["x\n"])); |
| 1738 | 1× | g.delete( |
| 1739 | 1× | h(1), |
| 1740 | 1× | &AtomRange { |
| 1741 | 1× | change: h(1), |
| 1742 | 1× | from: 0, |
| 1743 | 1× | to: 1, |
| 1744 | 1× | }, |
| 1745 | | ); |
| 1746 | 1× | g.insert( |
| 1747 | 1× | h(2), |
| 1748 | | 0, |
| 1749 | 1× | Vertex::Atom((h(1), 0)), |
| 1750 | 1× | Vertex::End, |
| 1751 | 1× | &lines(&["y\n"]), |
| 1752 | | ); |
| 1753 | 1× | let deps = Deps([(h(2), vec![h(1)])].into_iter().collect()); |
| 1754 | 1× | let m = g.materialize(&deps); |
| 1755 | 1× | assert!(!m.is_conflicted()); |
| 1756 | 1× | assert_eq!(text(&g, &m), "y\n"); |
| 1757 | 1× | } |
| 1758 | |
|
| 1759 | | #[test] |
| 1760 | 1× | fn resolution_collapses_a_conflict() { |
| 1761 | 1× | let mut g = FileGraph::new(); |
| 1762 | 1× | g.insert(h(1), 0, Vertex::Start, Vertex::End, &lines(&["a\n"])); |
| 1763 | 1× | g.insert( |
| 1764 | 1× | h(2), |
| 1765 | | 0, |
| 1766 | 1× | Vertex::Start, |
| 1767 | 1× | Vertex::Atom((h(1), 0)), |
| 1768 | 1× | &lines(&["x\n"]), |
| 1769 | | ); |
| 1770 | 1× | g.insert( |
| 1771 | 1× | h(3), |
| 1772 | | 0, |
| 1773 | 1× | Vertex::Start, |
| 1774 | 1× | Vertex::Atom((h(1), 0)), |
| 1775 | 1× | &lines(&["y\n"]), |
| 1776 | | ); |
| 1777 | 1× | let mut deps: HashMap<_, _> = [(h(2), vec![h(1)]), (h(3), vec![h(1)])] |
| 1778 | 1× | .into_iter() |
| 1779 | 1× | .collect(); |
| 1780 | 1× | assert!(g.materialize(&Deps(deps.clone())).is_conflicted()); |
| 1781 | | |
| 1782 | 1× | g.delete( |
| 1783 | 1× | h(4), |
| 1784 | 1× | &AtomRange { |
| 1785 | 1× | change: h(2), |
| 1786 | 1× | from: 0, |
| 1787 | 1× | to: 1, |
| 1788 | 1× | }, |
| 1789 | | ); |
| 1790 | 1× | deps.insert(h(4), vec![h(2), h(3)]); |
| 1791 | 1× | let m = g.materialize(&Deps(deps)); |
| 1792 | 1× | assert!(!m.is_conflicted()); |
| 1793 | 1× | assert_eq!(text(&g, &m), "y\na\n"); |
| 1794 | 1× | } |
| 1795 | |
|
| 1796 | | #[test] |
| 1797 | 1× | fn empty_and_unterminated_files() { |
| 1798 | 1× | let g = FileGraph::new(); |
| 1799 | 1× | let m = g.materialize(&NoDeps); |
| 1800 | 1× | assert_eq!(m.segments, vec![]); |
| 1801 | 1× | assert_eq!(text(&g, &m), ""); |
| 1802 | 1× | let mut g = FileGraph::new(); |
| 1803 | 1× | g.insert(h(1), 0, Vertex::Start, Vertex::End, &lines(&["no newline"])); |
| 1804 | 1× | assert_eq!(text(&g, &g.materialize(&NoDeps)), "no newline"); |
| 1805 | 1× | } |
| 1806 | |
|
| 1807 | | #[test] |
| 1808 | 1× | fn serialization_round_trip() { |
| 1809 | 1× | let mut g = FileGraph::new(); |
| 1810 | 1× | g.insert(h(1), 0, Vertex::Start, Vertex::End, &lines(&["a\n", "b\n"])); |
| 1811 | 1× | g.insert( |
| 1812 | 1× | h(2), |
| 1813 | | 0, |
| 1814 | 1× | Vertex::Atom((h(1), 0)), |
| 1815 | 1× | Vertex::Atom((h(1), 1)), |
| 1816 | 1× | &lines(&["x\n"]), |
| 1817 | | ); |
| 1818 | 1× | g.delete( |
| 1819 | 1× | h(3), |
| 1820 | 1× | &AtomRange { |
| 1821 | 1× | change: h(1), |
| 1822 | 1× | from: 1, |
| 1823 | 1× | to: 2, |
| 1824 | 1× | }, |
| 1825 | | ); |
| 1826 | 1× | let v = g.to_value(); |
| 1827 | 1× | let bytes = crate::cbor::encode(&v); |
| 1828 | 1× | let back = FileGraph::from_value(&crate::cbor::decode(&bytes).unwrap()).unwrap(); |
| 1829 | 1× | let deps = Deps( |
| 1830 | 1× | [(h(2), vec![h(1)]), (h(3), vec![h(1)])] |
| 1831 | 1× | .into_iter() |
| 1832 | 1× | .collect(), |
| 1833 | 1× | ); |
| 1834 | 1× | assert_eq!(back.materialize(&deps), g.materialize(&deps)); |
| 1835 | 1× | assert_eq!(text(&back, &back.materialize(&deps)), "a\nx\n"); |
| 1836 | 1× | assert_eq!( |
| 1837 | 1× | crate::cbor::encode(&back.to_value()), |
| 1838 | | bytes, |
| 1839 | | "deterministic" |
| 1840 | | ); |
| 1841 | 1× | } |
| 1842 | |
|
| 1843 | | |
| 1844 | | #[test] |
| 1845 | 1× | fn application_order_is_irrelevant() { |
| 1846 | | use rand::seq::SliceRandom; |
| 1847 | | type Op = ( |
| 1848 | | ChangeHash, |
| 1849 | | Vec<(u32, Vertex, Vertex, Lines)>, |
| 1850 | | Vec<AtomRange>, |
| 1851 | | ); |
| 1852 | 1× | let ops: Vec<Op> = vec![ |
| 1853 | 1× | ( |
| 1854 | 1× | h(1), |
| 1855 | 1× | vec![(0, Vertex::Start, Vertex::End, lines(&["a\n", "b\n", "c\n"]))], |
| 1856 | 1× | vec![], |
| 1857 | 1× | ), |
| 1858 | 1× | ( |
| 1859 | 1× | h(2), |
| 1860 | 1× | vec![( |
| 1861 | 1× | 0, |
| 1862 | 1× | Vertex::Atom((h(1), 0)), |
| 1863 | 1× | Vertex::Atom((h(1), 1)), |
| 1864 | 1× | lines(&["p\n"]), |
| 1865 | 1× | )], |
| 1866 | 1× | vec![], |
| 1867 | 1× | ), |
| 1868 | 1× | ( |
| 1869 | 1× | h(3), |
| 1870 | 1× | vec![( |
| 1871 | 1× | 0, |
| 1872 | 1× | Vertex::Atom((h(1), 0)), |
| 1873 | 1× | Vertex::Atom((h(1), 1)), |
| 1874 | 1× | lines(&["q\n", "r\n"]), |
| 1875 | 1× | )], |
| 1876 | 1× | vec![AtomRange { |
| 1877 | 1× | change: h(1), |
| 1878 | 1× | from: 2, |
| 1879 | 1× | to: 3, |
| 1880 | 1× | }], |
| 1881 | 1× | ), |
| 1882 | 1× | ( |
| 1883 | 1× | h(4), |
| 1884 | 1× | vec![( |
| 1885 | 1× | 0, |
| 1886 | 1× | Vertex::Atom((h(3), 1)), |
| 1887 | 1× | Vertex::Atom((h(1), 1)), |
| 1888 | 1× | lines(&["s\n"]), |
| 1889 | 1× | )], |
| 1890 | 1× | vec![AtomRange { |
| 1891 | 1× | change: h(1), |
| 1892 | 1× | from: 1, |
| 1893 | 1× | to: 2, |
| 1894 | 1× | }], |
| 1895 | 1× | ), |
| 1896 | 1× | ( |
| 1897 | 1× | h(5), |
| 1898 | 1× | vec![(0, Vertex::Atom((h(1), 2)), Vertex::End, lines(&["z\n"]))], |
| 1899 | 1× | vec![], |
| 1900 | 1× | ), |
| 1901 | | ]; |
| 1902 | 1× | let deps = Deps( |
| 1903 | 1× | [ |
| 1904 | 1× | (h(2), vec![h(1)]), |
| 1905 | 1× | (h(3), vec![h(1)]), |
| 1906 | 1× | (h(4), vec![h(3), h(1)]), |
| 1907 | 1× | (h(5), vec![h(1)]), |
| 1908 | 1× | ] |
| 1909 | 1× | .into_iter() |
| 1910 | 1× | .collect(), |
| 1911 | 1× | ); |
| 1912 | 51× | let build = |order: &[usize]| { |
| 1913 | 51× | let mut g = FileGraph::new(); |
| 1914 | 255× | for &i in order { |
| 1915 | 255× | let (c, ins, dels) = &ops[i]; |
| 1916 | 255× | for (idx, a, b, l) in ins { |
| 1917 | 255× | g.insert(*c, *idx, *a, *b, l); |
| 1918 | 255× | } |
| 1919 | 255× | for d in dels { |
| 1920 | 102× | g.delete(*c, d); |
| 1921 | 102× | } |
| 1922 | | } |
| 1923 | 51× | let m = g.materialize(&deps); |
| 1924 | 51× | (m.clone(), text(&g, &m)) |
| 1925 | 51× | }; |
| 1926 | 1× | let reference = build(&[0, 1, 2, 3, 4]); |
| 1927 | 1× | assert!(reference.0.is_conflicted()); |
| 1928 | 1× | let mut rng = rand::thread_rng(); |
| 1929 | 1× | let mut order: Vec<usize> = (0..ops.len()).collect(); |
| 1930 | 1× | for _ in 0..50 { |
| 1931 | 50× | order.shuffle(&mut rng); |
| 1932 | 50× | assert_eq!(build(&order), reference, "order {order:?}"); |
| 1933 | | } |
| 1934 | 1× | } |
| 1935 | | } |