1use alloc::collections::{BTreeMap, VecDeque};
2use core::{marker::PhantomData, mem};
3
4use aranya_buggy::{bug, BugExt};
5
6use crate::{
7 Address, ClientError, Command, CommandId, CommandRecall, Engine, EngineError, GraphId,
8 Location, MergeIds, PeerCache, Perspective, Policy, PolicyId, Prior, Revertable, Segment, Sink,
9 Storage, StorageError, StorageProvider, MAX_COMMAND_LENGTH,
10};
11
12pub struct Transaction<SP: StorageProvider, E> {
19 storage_id: GraphId,
21 perspective: Option<SP::Perspective>,
23 phead: Option<CommandId>,
25 heads: BTreeMap<Address, Location>,
27 _engine: PhantomData<E>,
29}
30
31impl<SP: StorageProvider, E> Transaction<SP, E> {
32 pub(super) const fn new(storage_id: GraphId) -> Self {
33 Self {
34 storage_id,
35 perspective: None,
36 phead: None,
37 heads: BTreeMap::new(),
38 _engine: PhantomData,
39 }
40 }
41}
42
43impl<SP: StorageProvider, E: Engine> Transaction<SP, E> {
44 fn locate(
48 &self,
49 storage: &mut SP::Storage,
50 address: Address,
51 ) -> Result<Option<Location>, ClientError> {
52 if let Some(found) = storage.get_location(address)? {
54 return Ok(Some(found));
55 }
56 for &head in self.heads.values() {
58 if let Some(found) = storage.get_location_from(head, address)? {
59 return Ok(Some(found));
60 }
61 }
62 Ok(None)
63 }
64
65 pub(super) fn commit(
67 &mut self,
68 provider: &mut SP,
69 engine: &mut E,
70 sink: &mut impl Sink<E::Effect>,
71 ) -> Result<(), ClientError> {
72 let storage = provider.get_storage(self.storage_id)?;
73
74 if let Some(p) = Option::take(&mut self.perspective) {
76 self.phead = None;
77 let segment = storage.write(p)?;
78 let head = segment.head()?;
79 self.heads.insert(head.address()?, segment.head_location());
80 }
81
82 let mut heads: VecDeque<_> = mem::take(&mut self.heads).into_iter().collect();
85 let mut merging_head = false;
86 while let Some((left_id, mut left_loc)) = heads.pop_front() {
87 if let Some((right_id, mut right_loc)) = heads.pop_front() {
88 let (policy, policy_id) = choose_policy(storage, engine, left_loc, right_loc)?;
89
90 let mut buffer = [0u8; MAX_COMMAND_LENGTH];
91 let merge_ids = MergeIds::new(left_id, right_id).assume("merging different ids")?;
92 if left_id > right_id {
93 mem::swap(&mut left_loc, &mut right_loc);
94 }
95 let command = policy.merge(&mut buffer, merge_ids)?;
96
97 let (braid, last_common_ancestor) =
98 make_braid_segment::<_, E>(storage, left_loc, right_loc, sink, policy)?;
99
100 let mut perspective = storage
101 .new_merge_perspective(
102 left_loc,
103 right_loc,
104 last_common_ancestor,
105 policy_id,
106 braid,
107 )?
108 .assume("trx heads should exist in storage")?;
109 perspective.add_command(&command)?;
110
111 let segment = storage.write(perspective)?;
112 let head = segment.head()?;
113
114 heads.push_back((head.address()?, segment.head_location()));
115 } else {
116 let segment = storage.get_segment(left_loc)?;
117 match storage.commit(segment) {
120 Ok(()) => break,
121 Err(StorageError::HeadNotAncestor) => {
122 if merging_head {
123 bug!("merging with graph head again, would loop");
124 }
125
126 merging_head = true;
127
128 heads.push_back((left_id, left_loc));
129
130 let head_loc = storage.get_head()?;
131 let segment = storage.get_segment(head_loc)?;
132 let head = segment.head()?;
133 heads.push_back((head.address()?, segment.head_location()));
134 }
135 Err(e) => return Err(e.into()),
136 }
137 }
138 }
139
140 Ok(())
141 }
142
143 pub(super) fn add_commands(
147 &mut self,
148 commands: &[impl Command],
149 provider: &mut SP,
150 engine: &mut E,
151 sink: &mut impl Sink<E::Effect>,
152 request_heads: &mut PeerCache,
153 ) -> Result<usize, ClientError> {
154 let mut commands = commands.iter();
155 let mut count: usize = 0;
156
157 let storage = match provider.get_storage(self.storage_id) {
159 Ok(s) => s,
160 Err(StorageError::NoSuchStorage) => {
161 let command = commands.next().ok_or(ClientError::InitError)?;
162 count = count.checked_add(1).assume("must not overflow")?;
163 self.init(command, engine, provider, sink)?
164 }
165 Err(e) => return Err(e.into()),
166 };
167
168 for command in commands {
170 if self
171 .perspective
172 .as_ref()
173 .is_some_and(|p| p.includes(command.id()))
174 {
175 continue;
177 }
178 if let Some(loc) = self.locate(storage, command.address()?)? {
179 request_heads.add_command(storage, command.address()?, loc)?;
180 continue;
182 }
183 match command.parent() {
184 Prior::None => {
185 if command.id().into_id() == self.storage_id.into_id() {
186 } else {
188 bug!("init command does not belong in graph");
189 }
190 }
191 Prior::Single(parent) => {
192 self.add_single(storage, engine, sink, command, parent)?;
193 count = count.checked_add(1).assume("must not overflow")?;
194 }
195 Prior::Merge(left, right) => {
196 self.add_merge(storage, engine, sink, command, left, right)?;
197 count = count.checked_add(1).assume("must not overflow")?;
198 }
199 };
200 if let Some(loc) = self.locate(storage, command.address()?)? {
201 request_heads.add_command(storage, command.address()?, loc)?;
202 }
203 }
204 let head_location = storage.get_head()?;
205 let cmd_seg = storage.get_segment(head_location)?;
206 let command = cmd_seg.head()?;
207 request_heads.add_command(storage, command.address()?, head_location)?;
208
209 Ok(count)
210 }
211
212 fn add_single(
213 &mut self,
214 storage: &mut <SP as StorageProvider>::Storage,
215 engine: &mut E,
216 sink: &mut impl Sink<E::Effect>,
217 command: &impl Command,
218 parent: Address,
219 ) -> Result<(), ClientError> {
220 let perspective = self.get_perspective(parent, storage)?;
221
222 let policy_id = perspective.policy();
223 let policy = engine.get_policy(policy_id)?;
224
225 sink.begin();
227 let checkpoint = perspective.checkpoint();
228 if let Err(e) = policy.call_rule(command, perspective, sink, CommandRecall::None) {
229 perspective.revert(checkpoint)?;
230 sink.rollback();
231 return Err(e.into());
232 }
233 perspective.add_command(command)?;
234 sink.commit();
235
236 self.phead = Some(command.id());
237
238 Ok(())
239 }
240
241 fn add_merge(
242 &mut self,
243 storage: &mut <SP as StorageProvider>::Storage,
244 engine: &mut E,
245 sink: &mut impl Sink<E::Effect>,
246 command: &impl Command,
247 left: Address,
248 right: Address,
249 ) -> Result<bool, ClientError> {
250 if let Some(p) = Option::take(&mut self.perspective) {
252 let seg = storage.write(p)?;
253 let head = seg.head()?;
254 self.heads.insert(head.address()?, seg.head_location());
255 }
256
257 let left_loc = self
258 .locate(storage, left)?
259 .ok_or(ClientError::NoSuchParent(left.id))?;
260 let right_loc = self
261 .locate(storage, right)?
262 .ok_or(ClientError::NoSuchParent(right.id))?;
263
264 let (policy, policy_id) = choose_policy(storage, engine, left_loc, right_loc)?;
265
266 let (braid, last_common_ancestor) =
268 make_braid_segment::<_, E>(storage, left_loc, right_loc, sink, policy)?;
269
270 let mut perspective = storage
271 .new_merge_perspective(left_loc, right_loc, last_common_ancestor, policy_id, braid)?
272 .assume(
273 "we already found left and right locations above and we only call this with merge command",
274 )?;
275 perspective.add_command(command)?;
276
277 self.heads.remove(&left);
279 self.heads.remove(&right);
280
281 self.perspective = Some(perspective);
282 self.phead = Some(command.id());
283
284 Ok(true)
285 }
286
287 fn get_perspective(
292 &mut self,
293 parent: Address,
294 storage: &mut <SP as StorageProvider>::Storage,
295 ) -> Result<&mut <SP as StorageProvider>::Perspective, ClientError> {
296 if self.phead == Some(parent.id) {
297 return Ok(self
299 .perspective
300 .as_mut()
301 .assume("trx has perspective when has phead")?);
302 }
303
304 if let Some(p) = Option::take(&mut self.perspective) {
306 self.phead = None;
307 let seg = storage.write(p)?;
308 let head = seg.head()?;
309 self.heads.insert(head.address()?, seg.head_location());
310 }
311
312 let loc = self
313 .locate(storage, parent)?
314 .ok_or(ClientError::NoSuchParent(parent.id))?;
315
316 let p = self.perspective.insert(
318 storage
319 .get_linear_perspective(loc)?
320 .assume("location should already be in storage")?,
321 );
322
323 self.phead = Some(parent.id);
324 self.heads.remove(&parent);
325
326 Ok(p)
327 }
328
329 fn init<'sp>(
330 &mut self,
331 command: &impl Command,
332 engine: &mut E,
333 provider: &'sp mut SP,
334 sink: &mut impl Sink<E::Effect>,
335 ) -> Result<&'sp mut <SP as StorageProvider>::Storage, ClientError> {
336 if self.storage_id.into_id() != command.id().into_id() {
338 return Err(ClientError::InitError);
339 }
340
341 if !matches!(command.parent(), Prior::None) {
343 return Err(ClientError::InitError);
344 }
345
346 let Some(policy_data) = command.policy() else {
348 return Err(ClientError::InitError);
349 };
350
351 let policy_id = engine.add_policy(policy_data)?;
352 let policy = engine.get_policy(policy_id)?;
353
354 let mut perspective = provider.new_perspective(policy_id);
356 sink.begin();
357 if let Err(e) = policy.call_rule(command, &mut perspective, sink, CommandRecall::None) {
358 sink.rollback();
359 return Err(e.into());
361 }
362 perspective.add_command(command)?;
363
364 let (_, storage) = provider.new_storage(perspective)?;
365
366 sink.commit();
368
369 Ok(storage)
370 }
371}
372
373fn make_braid_segment<S: Storage, E: Engine>(
375 storage: &mut S,
376 left: Location,
377 right: Location,
378 sink: &mut impl Sink<E::Effect>,
379 policy: &E::Policy,
380) -> Result<(S::FactIndex, (Location, usize)), ClientError> {
381 let order = super::braid(storage, left, right)?;
382 let last_common_ancestor = super::last_common_ancestor(storage, left, right)?;
383
384 let (&first, rest) = order.split_first().assume("braid is non-empty")?;
385
386 let mut braid_perspective = storage.get_fact_perspective(first)?;
387
388 sink.begin();
389
390 for &location in rest {
391 let segment = storage.get_segment(location)?;
392 let command = segment
393 .get_command(location)
394 .assume("braid only contains existing commands")?;
395
396 let result = policy.call_rule(
397 &command,
398 &mut braid_perspective,
399 sink,
400 CommandRecall::OnCheck,
401 );
402
403 if let Err(e) = result {
405 if e != EngineError::Check {
406 sink.rollback();
407 return Err(e.into());
408 }
409 }
410 }
411
412 let braid = storage.write_facts(braid_perspective)?;
413
414 sink.commit();
415
416 Ok((braid, last_common_ancestor))
417}
418
419fn choose_policy<'a, E: Engine>(
421 storage: &impl Storage,
422 engine: &'a E,
423 left: Location,
424 right: Location,
425) -> Result<(&'a E::Policy, PolicyId), ClientError> {
426 Ok(core::cmp::max_by_key(
427 get_policy(storage, engine, left)?,
428 get_policy(storage, engine, right)?,
429 |(p, _)| p.serial(),
430 ))
431}
432
433fn get_policy<'a, E: Engine>(
434 storage: &impl Storage,
435 engine: &'a E,
436 location: Location,
437) -> Result<(&'a E::Policy, PolicyId), ClientError> {
438 let segment = storage.get_segment(location)?;
439 let policy_id = segment.policy();
440 let policy = engine.get_policy(policy_id)?;
441 Ok((policy, policy_id))
442}
443
444#[cfg(test)]
445mod test {
446 use aranya_buggy::Bug;
447 use test_log::test;
448
449 use super::*;
450 use crate::{memory::MemStorageProvider, ClientState, Keys, MergeIds, Priority};
451
452 struct SeqEngine;
453
454 struct SeqPolicy;
459
460 struct SeqCommand {
461 id: CommandId,
462 prior: Prior<Address>,
463 data: Box<str>,
464 max_cut: usize,
465 }
466
467 impl Engine for SeqEngine {
468 type Policy = SeqPolicy;
469 type Effect = ();
470
471 fn add_policy(&mut self, _policy: &[u8]) -> Result<PolicyId, EngineError> {
472 Ok(PolicyId::new(0))
473 }
474
475 fn get_policy(&self, _id: PolicyId) -> Result<&Self::Policy, EngineError> {
476 Ok(&SeqPolicy)
477 }
478 }
479
480 impl Policy for SeqPolicy {
481 type Action<'a> = &'a str;
482 type Effect = ();
483 type Command<'a> = SeqCommand;
484
485 fn serial(&self) -> u32 {
486 0
487 }
488
489 fn call_rule(
490 &self,
491 command: &impl Command,
492 facts: &mut impl crate::FactPerspective,
493 _sink: &mut impl Sink<Self::Effect>,
494 _recall: CommandRecall,
495 ) -> Result<(), EngineError> {
496 assert!(
497 !matches!(command.parent(), Prior::Merge { .. }),
498 "merges shouldn't be evaluated"
499 );
500
501 let data = command.bytes();
503 if let Some(seq) = facts.query("seq", &Keys::default())?.as_deref() {
504 facts.insert(
505 "seq".into(),
506 Keys::default(),
507 [seq, b":", data].concat().into(),
508 );
509 } else {
510 facts.insert("seq".into(), Keys::default(), data.into());
511 };
512 Ok(())
513 }
514
515 fn call_action(
516 &self,
517 _action: Self::Action<'_>,
518 _facts: &mut impl Perspective,
519 _sink: &mut impl Sink<Self::Effect>,
520 ) -> Result<(), EngineError> {
521 unimplemented!()
522 }
523
524 fn merge<'a>(
525 &self,
526 _target: &'a mut [u8],
527 ids: MergeIds,
528 ) -> Result<Self::Command<'a>, EngineError> {
529 let (left, right): (Address, Address) = ids.into();
530 let mut buf = [0u8; 128];
531 buf[..64].copy_from_slice(left.id.as_bytes());
532 buf[64..].copy_from_slice(right.id.as_bytes());
533 let id = CommandId::hash_for_testing_only(&buf);
534
535 Ok(SeqCommand::new(
536 id,
537 Prior::Merge(left, right),
538 left.max_cut
539 .max(right.max_cut)
540 .checked_add(1)
541 .assume("must not overflow")?,
542 ))
543 }
544 }
545
546 impl SeqCommand {
547 fn new(id: CommandId, prior: Prior<Address>, max_cut: usize) -> Self {
548 let data = id.short_b58().into_boxed_str();
549 Self {
550 id,
551 prior,
552 data,
553 max_cut,
554 }
555 }
556 }
557
558 impl Command for SeqCommand {
559 fn priority(&self) -> Priority {
560 match self.prior {
561 Prior::None => Priority::Init,
562 Prior::Single(_) => {
563 let id = self.id.as_bytes();
566 let priority = u32::from(*id.last().unwrap());
567 Priority::Basic(priority)
568 }
569 Prior::Merge(_, _) => Priority::Merge,
570 }
571 }
572
573 fn id(&self) -> CommandId {
574 self.id
575 }
576
577 fn parent(&self) -> Prior<Address> {
578 self.prior
579 }
580
581 fn policy(&self) -> Option<&[u8]> {
582 match self.prior {
585 Prior::None { .. } => Some(b""),
586 _ => None,
587 }
588 }
589
590 fn bytes(&self) -> &[u8] {
591 self.data.as_bytes()
592 }
593
594 fn max_cut(&self) -> Result<usize, Bug> {
595 Ok(self.max_cut)
596 }
597 }
598
599 struct NullSink;
600 impl<E> Sink<E> for NullSink {
601 fn begin(&mut self) {}
602 fn consume(&mut self, _: E) {}
603 fn rollback(&mut self) {}
604 fn commit(&mut self) {}
605 }
606
607 struct GraphBuilder<SP: StorageProvider> {
610 client: ClientState<SeqEngine, SP>,
611 trx: Transaction<SP, SeqEngine>,
612 }
613
614 impl<SP: StorageProvider> GraphBuilder<SP> {
615 pub fn init(mut client: ClientState<SeqEngine, SP>, ids: &[CommandId]) -> Self {
616 let mut trx = Transaction::new(GraphId::from(ids[0].into_id()));
617 let mut prior: Prior<Address> = Prior::None;
618 for &id in ids {
619 let cmd = SeqCommand::new(id, prior, 0);
620 trx.add_commands(
621 &[cmd],
622 &mut client.provider,
623 &mut client.engine,
624 &mut NullSink,
625 &mut PeerCache::new(),
626 )
627 .unwrap();
628 prior = Prior::Single(Address { id, max_cut: 0 });
629 }
630 Self { client, trx }
631 }
632
633 pub fn line(&mut self, mut prev: Address, ids: &[CommandId]) {
634 for &id in ids {
635 let max_cut = prev.max_cut.checked_add(1).unwrap();
636 let cmd = SeqCommand::new(id, Prior::Single(prev), max_cut);
637 self.trx
638 .add_commands(
639 &[cmd],
640 &mut self.client.provider,
641 &mut self.client.engine,
642 &mut NullSink,
643 &mut PeerCache::new(),
644 )
645 .unwrap();
646 prev = Address { id, max_cut };
647 }
648 }
649
650 pub fn merge(&mut self, (left, right): (Address, Address), ids: &[CommandId]) {
651 let prior = Prior::Merge(left, right);
652 let mergecmd = SeqCommand::new(ids[0], prior, prior.next_max_cut().unwrap());
653 let mut prev = Address {
654 id: mergecmd.id,
655 max_cut: mergecmd.max_cut,
656 };
657 self.trx
658 .add_commands(
659 &[mergecmd],
660 &mut self.client.provider,
661 &mut self.client.engine,
662 &mut NullSink,
663 &mut PeerCache::new(),
664 )
665 .unwrap();
666 for &id in &ids[1..] {
667 let cmd = SeqCommand::new(
668 id,
669 Prior::Single(prev),
670 prev.max_cut.checked_add(1).expect("must not overflow"),
671 );
672 prev = Address {
673 id: cmd.id,
674 max_cut: cmd.max_cut,
675 };
676 self.trx
677 .add_commands(
678 &[cmd],
679 &mut self.client.provider,
680 &mut self.client.engine,
681 &mut NullSink,
682 &mut PeerCache::new(),
683 )
684 .unwrap();
685 }
686 }
687
688 pub fn flush(&mut self) {
689 if let Some(p) = Option::take(&mut self.trx.perspective) {
690 self.trx.phead = None;
691 let seg = self
692 .client
693 .provider
694 .get_storage(self.trx.storage_id)
695 .unwrap()
696 .write(p)
697 .unwrap();
698 let head = seg.head().unwrap();
699 self.trx.heads.insert(
700 head.address().expect("address must exist"),
701 seg.head_location(),
702 );
703 }
704 }
705
706 pub fn commit(&mut self) {
707 self.trx
708 .commit(
709 &mut self.client.provider,
710 &mut self.client.engine,
711 &mut NullSink,
712 )
713 .unwrap();
714 }
715 }
716
717 fn mkid(x: &str) -> CommandId {
718 x.parse().unwrap()
719 }
720
721 macro_rules! graph {
723 ( $client:expr ; $init:literal $($inits:literal )* ; $($rest:tt)*) => {{
724 let mut gb = GraphBuilder::init($client, &[mkid($init), $(mkid($inits)),*]);
725 graph!(@ gb, $($rest)*);
726 gb
727 }};
728 (@ $gb:ident, $prev:literal $prev_max_cut:literal < $($id:literal)+; $($rest:tt)*) => {
729 $gb.line(Address {id: mkid($prev), max_cut: $prev_max_cut}, &[$(mkid($id)),+]);
730 graph!(@ $gb, $($rest)*);
731 };
732 (@ $gb:ident, $l:literal $l_max_cut:literal $r:literal $r_max_cut:literal < $($id:literal)+; $($rest:tt)*) => {
733 $gb.merge((Address {id: mkid($l), max_cut: $l_max_cut}, Address {id: mkid($r), max_cut: $r_max_cut}), &[$(mkid($id)),+]);
734 graph!(@ $gb, $($rest)*);
735 };
736 (@ $gb:ident, commit; $($rest:tt)*) => {
737 $gb.commit();
738 graph!(@ $gb, $($rest)*);
739 };
740 (@ $gb:ident, ) => {
741 $gb.flush();
742 };
743 }
744
745 fn lookup(storage: &impl Storage, name: &str) -> Option<Box<[u8]>> {
746 use crate::Query;
747 let head = storage.get_head().unwrap();
748 let p = storage.get_fact_perspective(head).unwrap();
749 p.query(name, &Keys::default()).unwrap()
750 }
751
752 #[test]
753 fn test_simple() -> Result<(), StorageError> {
754 let mut gb = graph! {
755 ClientState::new(SeqEngine, MemStorageProvider::new());
756 "a";
757 "a" 0 < "b";
758 "a" 0 < "c";
759 "b" 1 "c" 1 < "ma";
760 "b" 1 < "d";
761 "ma" 2 "d" 2 < "mb";
762 commit;
763 };
764 let g = gb
765 .client
766 .provider
767 .get_storage("a".parse().unwrap())
768 .unwrap();
769
770 #[cfg(feature = "graphviz")]
771 crate::storage::memory::graphviz::dot(g, "simple");
772
773 assert_eq!(g.get_head().unwrap(), Location::new(5, 0));
774
775 let seq = lookup(g, "seq").unwrap();
776 let seq = std::str::from_utf8(&seq).unwrap();
777 assert_eq!(seq, "a:b:d:c");
778
779 Ok(())
780 }
781
782 #[test]
783 fn test_complex() -> Result<(), StorageError> {
784 let mut gb = graph! {
785 ClientState::new(SeqEngine, MemStorageProvider::new());
786 "a";
787 "a" 0 < "1" "2" "3";
788 "3" 3 < "4" "6" "7";
789 "3" 3 < "5" "8";
790 "6" 5 "8" 5 < "9" "aa"; commit;
791 "7" 6 < "a1" "a2";
792 "aa" 7 "a2" 8 < "a3";
793 "a3" 9 < "a6" "a4";
794 "a3" 9 < "a7" "a5";
795 "a4" 11 "a5" 11 < "a8";
796 "9" 6 < "42" "43";
797 "42" 7 < "45" "46";
798 "45" 8 < "47" "48";
799 commit;
800 };
801
802 let g = gb
803 .client
804 .provider
805 .get_storage("a".parse().unwrap())
806 .unwrap();
807
808 #[cfg(feature = "graphviz")]
809 crate::storage::memory::graphviz::dot(g, "complex");
810
811 assert_eq!(g.get_head().unwrap(), Location::new(15, 0));
812
813 let seq = lookup(g, "seq").unwrap();
814 let seq = std::str::from_utf8(&seq).unwrap();
815 assert_eq!(
816 seq,
817 "a:1:2:3:5:8:4:6:42:45:47:48:46:43:aa:7:a1:a2:a7:a6:a5:a4"
818 );
819
820 Ok(())
821 }
822
823 #[test]
824 fn test_duplicates() {
825 let mut gb = graph! {
826 ClientState::new(SeqEngine, MemStorageProvider::new());
827 "a";
828 "a" 0 < "b" "c";
829 "a" 0 < "b";
830 "b" 1 < "c";
831 "c" 2 < "d";
832 commit;
833 "a" 0 < "b";
834 "b" 1 < "c";
835 "d" 3 < "e";
836 commit;
837 };
838
839 let g = gb
840 .client
841 .provider
842 .get_storage("a".parse().unwrap())
843 .unwrap();
844
845 #[cfg(feature = "graphviz")]
846 crate::storage::memory::graphviz::dot(g, "duplicates");
847
848 assert_eq!(g.get_head().unwrap(), Location::new(2, 0));
849
850 let seq = lookup(g, "seq").unwrap();
851 let seq = std::str::from_utf8(&seq).unwrap();
852 assert_eq!(seq, "a:b:c:d:e");
853 }
854
855 #[test]
856 fn test_mid_braid_1() {
857 let mut gb = graph! {
858 ClientState::new(SeqEngine, MemStorageProvider::new());
859 "a";
860 commit;
861 "a" 0 < "b" "c" "d" "e" "f" "g";
862 "d" 3 < "h" "i" "j";
863 commit;
864 };
865
866 let g = gb
867 .client
868 .provider
869 .get_storage("a".parse().unwrap())
870 .unwrap();
871
872 #[cfg(feature = "graphviz")]
873 crate::storage::memory::graphviz::dot(g, "mid_braid_1");
874
875 assert_eq!(g.get_head().unwrap(), Location::new(3, 0));
876
877 let seq = lookup(g, "seq").unwrap();
878 let seq = std::str::from_utf8(&seq).unwrap();
879 assert_eq!(seq, "a:b:c:d:h:i:j:e:f:g");
880 }
881
882 #[test]
883 fn test_mid_braid_2() {
884 let mut gb = graph! {
885 ClientState::new(SeqEngine, MemStorageProvider::new());
886 "a";
887 commit;
888 "a" 0 < "b" "c" "d" "h" "i" "j";
889 "d" 3 < "e" "f" "g";
890 commit;
891 };
892
893 let g = gb
894 .client
895 .provider
896 .get_storage("a".parse().unwrap())
897 .unwrap();
898
899 #[cfg(feature = "graphviz")]
900 crate::storage::memory::graphviz::dot(g, "mid_braid_2");
901
902 assert_eq!(g.get_head().unwrap(), Location::new(3, 0));
903
904 let seq = lookup(g, "seq").unwrap();
905 let seq = std::str::from_utf8(&seq).unwrap();
906 assert_eq!(seq, "a:b:c:d:h:i:j:e:f:g");
907 }
908}