1#[path = "support/ops.rs"]
2mod ops;
3
4use onestore::{
5 ExGuid, PageEdit, RevisionIndex, Store,
6 document::{Document, Kind},
7 op::{PageOp, SectionOp},
8};
9
10#[path = "support/current.rs"]
11mod current;
12#[path = "support/disk.rs"]
13mod disk;
14#[path = "support/sweep.rs"]
15mod sweep;
16
17const SOURCE: &[u8] =
18 include_bytes!("../../../corpus/page-lifecycle/04-nested/notebook/Lifecycle.one");
19
20fn pages(source: &[u8]) -> Vec<ExGuid> {
21 let store = Store::parse(source).unwrap();
22 let index = RevisionIndex::parse(&store).unwrap();
23 Document::parse(&index)
24 .unwrap()
25 .pages()
26 .unwrap()
27 .into_iter()
28 .map(|(sid, _)| sid)
29 .collect()
30}
31
32fn export(name: &str, bytes: &[u8]) {
33 if let Some(output) = std::env::var_os("ONESTORE_PAGE_MOVEMENT_OUTPUT") {
34 assert!(
35 std::path::Path::new(&output).is_absolute(),
36 "Use an absolute fixture output directory"
37 );
38 let path = std::path::Path::new(&output).join(name);
39 std::fs::create_dir_all(&path).unwrap();
40 std::fs::write(path.join("Lifecycle.one"), bytes).unwrap();
41 }
42}
43
44fn verify(before: &[u8], after: &[u8], native: &[u8]) {
45 let stores = [before, after, native].map(|bytes| Store::parse(bytes).unwrap());
46 let indexes = stores
47 .each_ref()
48 .map(|store| RevisionIndex::parse(store).unwrap());
49 indexes[1].validate_current().unwrap();
50 let documents = indexes
51 .each_ref()
52 .map(|index| Document::parse(index).unwrap());
53 let expected = documents[2].pages().unwrap();
54 assert_eq!(documents[1].pages().unwrap(), expected);
55 for (sid, _) in &expected {
56 let levels = [&documents[1], &documents[2]].map(|document| {
57 let space = &document.spaces[sid];
58 let view = &space.revisions[&space.contexts[&ExGuid::default()]];
59 let Kind::Metadata { level, .. } = view.nodes[&view.roots[&2]].kind else {
60 panic!()
61 };
62 level
63 });
64 assert_eq!(levels[0], levels[1]);
65 }
66 let groups = [&documents[1], &documents[2]].map(|document| {
67 let space = &document.spaces[&document.root];
68 let view = &space.revisions[&space.contexts[&ExGuid::default()]];
69 view.nodes[&view.roots[&1]]
70 .children
71 .iter()
72 .map(|id| view.nodes[id].spaces.clone())
73 .collect::<Vec<_>>()
74 });
75 assert_eq!(groups[0], groups[1]);
76 assert_eq!(
77 stores[1].header.transaction_count,
78 stores[0].header.transaction_count + 1
79 );
80 for (sid, space) in &indexes[0].spaces {
81 for rid in space.revisions.keys() {
82 assert_eq!(
83 format!("{:?}", indexes[0].resolve(*sid, *rid).unwrap()),
84 format!("{:?}", indexes[1].resolve(*sid, *rid).unwrap())
85 );
86 }
87 if *sid != indexes[0].root {
88 let old = indexes[0]
89 .resolve(*sid, space.labels[&(ExGuid::default(), 1)])
90 .unwrap();
91 let new = indexes[1]
92 .resolve(*sid, indexes[1].spaces[sid].labels[&(ExGuid::default(), 1)])
93 .unwrap();
94 assert_eq!(old.roots, new.roots);
95 assert_eq!(
96 old.objects.keys().collect::<Vec<_>>(),
97 new.objects.keys().collect::<Vec<_>>()
98 );
99 for (id, object) in &old.objects {
100 if *id != old.roots[&2] {
101 assert_eq!(format!("{object:?}"), format!("{:?}", new.objects[id]));
102 }
103 }
104 }
105 }
106 let mut previous = after.to_vec();
107 previous[96..100].copy_from_slice(&before[96..100]);
108 assert_eq!(current::current(&previous), current::current(before));
109}
110
111#[test]
112fn explicit_page_edits_match_native_selection_and_indentation() {
113 let ids = pages(SOURCE);
114 let level = |i, n| PageEdit::set_level(ids[i], n).unwrap();
115 let move_to =
116 |i, before: Option<usize>, n| PageEdit::move_to(ids[i], before.map(|i| ids[i]), n).unwrap();
117 let cases: [(&str, Vec<PageEdit>, &[u8]); 8] = [
118 (
119 "01-demoted-parent",
120 vec![level(3, 2)],
121 include_bytes!(
122 "../../../corpus/page-lifecycle/movement/01-demoted-parent/notebook/Lifecycle.one"
123 ),
124 ),
125 (
126 "02-promoted-parent",
127 vec![level(3, 1)],
128 include_bytes!(
129 "../../../corpus/page-lifecycle/movement/02-promoted-parent/notebook/Lifecycle.one"
130 ),
131 ),
132 (
133 "03-selected-group-move",
134 vec![
135 move_to(3, None, 1),
136 move_to(4, None, 2),
137 move_to(5, None, 3),
138 ],
139 include_bytes!(
140 "../../../corpus/page-lifecycle/movement/03-selected-group-move/notebook/Lifecycle.one"
141 ),
142 ),
143 (
144 "04-subpage-move",
145 vec![move_to(4, Some(6), 1)],
146 include_bytes!(
147 "../../../corpus/page-lifecycle/movement/04-subpage-move/notebook/Lifecycle.one"
148 ),
149 ),
150 (
151 "05-insert-before-subpage",
152 vec![move_to(4, Some(5), 3)],
153 include_bytes!(
154 "../../../corpus/page-lifecycle/movement/05-insert-before-subpage/notebook/Lifecycle.one"
155 ),
156 ),
157 (
158 "06-promoted-level-two",
159 vec![level(4, 2)],
160 include_bytes!(
161 "../../../corpus/page-lifecycle/movement/06-promoted-level-two/notebook/Lifecycle.one"
162 ),
163 ),
164 (
165 "07-promoted-root",
166 vec![level(4, 1)],
167 include_bytes!(
168 "../../../corpus/page-lifecycle/movement/07-promoted-root/notebook/Lifecycle.one"
169 ),
170 ),
171 (
172 "08-collapsed-group-move",
173 vec![move_to(4, Some(0), 1), move_to(5, Some(0), 3)],
174 include_bytes!(
175 "../../../corpus/page-lifecycle/movement/08-collapsed-group-move/notebook/Lifecycle.one"
176 ),
177 ),
178 ];
179 let mut source = SOURCE.to_vec();
180 for (name, edits, native) in cases {
181 let serialized = serde_json::to_vec(&edits).unwrap();
182 let retained: Vec<PageEdit> = serde_json::from_slice(&serialized).unwrap();
183 assert_eq!(edits, retained);
184 let prepared = ops::section_op(&source, SectionOp::Pages(retained.to_vec())).unwrap();
185 verify(&source, prepared.as_bytes(), native);
186 assert_eq!(
187 ops::section_op(prepared.as_bytes(), SectionOp::Pages(retained.to_vec()))
188 .unwrap()
189 .as_bytes(),
190 prepared.as_bytes()
191 );
192 export(name, prepared.as_bytes());
193 source = prepared.as_bytes().to_vec();
194 }
195 let edit = PageEdit::move_to(ids[3], None, 1).unwrap();
196 let single = ops::section_op(SOURCE, SectionOp::Pages([edit].to_vec())).unwrap();
197 verify(
198 SOURCE,
199 single.as_bytes(),
200 include_bytes!(
201 "../../../corpus/page-lifecycle/movement/single-page/notebook/Lifecycle.one"
202 ),
203 );
204 export("single-page", single.as_bytes());
205}
206
207#[test]
208fn invalid_and_empty_page_edits_do_not_publish() {
209 let ids = pages(SOURCE);
210 for level in [0, 4, u32::MAX] {
211 assert!(PageEdit::set_level(ids[0], level).is_err());
212 }
213 assert!(PageEdit::move_to(ids[0], Some(ids[0]), 1).is_err());
214 assert!(PageEdit::set_level(ExGuid::default(), 1).is_err());
215 assert_eq!(
216 ops::section_op(SOURCE, SectionOp::Pages([].to_vec()))
217 .unwrap()
218 .as_bytes(),
219 SOURCE
220 );
221 assert_eq!(
222 ops::section_op(
223 SOURCE,
224 SectionOp::Pages([PageEdit::set_level(ids[0], 1).unwrap()].to_vec())
225 )
226 .unwrap()
227 .as_bytes(),
228 SOURCE
229 );
230 assert!(
231 ops::section_op(
232 SOURCE,
233 SectionOp::Pages([PageEdit::set_level(ids[0], 2).unwrap()].to_vec())
234 )
235 .is_err()
236 );
237 let duplicate = PageEdit::set_level(ids[3], 2).unwrap();
238 assert!(
239 ops::section_op(
240 SOURCE,
241 SectionOp::Pages([duplicate.clone(), duplicate].to_vec())
242 )
243 .is_err()
244 );
245 let missing = ExGuid {
246 guid: [0x77; 16],
247 n: 1,
248 };
249 assert!(
250 ops::section_op(
251 SOURCE,
252 SectionOp::Pages([PageEdit::set_level(missing, 1).unwrap()].to_vec())
253 )
254 .is_err()
255 );
256 assert!(
257 ops::section_op(
258 SOURCE,
259 SectionOp::Pages([PageEdit::move_to(ids[3], Some(missing), 1).unwrap()].to_vec())
260 )
261 .is_err()
262 );
263 let mut value = serde_json::to_value(PageEdit::set_level(ids[3], 2).unwrap()).unwrap();
264 value["level"] = 4.into();
265 let invalid: PageEdit = serde_json::from_value(value.clone()).unwrap();
266 assert!(ops::section_op(SOURCE, SectionOp::Pages([invalid].to_vec())).is_err());
267 value["unknown"] = true.into();
268 assert!(serde_json::from_value::<PageEdit>(value).is_err());
269}
270
271#[test]
272fn moving_pages_preserves_ink_file_data_and_native_feature_objects() {
273 for source in [
274 include_bytes!("../../../corpus/native-ink/20260905-ui/notebook/synthetic.one").as_slice(),
275 include_bytes!("../../../corpus/native-external-assets/notebook/synthetic.one").as_slice(),
276 include_bytes!("../../../corpus/m6/native-features-01/notebook/Features.one").as_slice(),
277 ] {
278 let first = pages(source)[0];
279 let created =
280 onestore::PageCreation::new(Some(first), Some("Movement anchor"), "Author").unwrap();
281 let source = ops::section_op(source, SectionOp::Create(created.clone()))
282 .unwrap()
283 .as_bytes()
284 .to_vec();
285 let moved = ops::section_op(
286 &source,
287 SectionOp::Pages([PageEdit::move_to(first, None, 1).unwrap()].to_vec()),
288 )
289 .unwrap();
290 assert_eq!(pages(moved.as_bytes()).last(), Some(&first));
291 let stores = [&source, moved.as_bytes()].map(|bytes| Store::parse(bytes).unwrap());
292 let indexes = stores
293 .each_ref()
294 .map(|store| RevisionIndex::parse(store).unwrap());
295 for (sid, space) in &indexes[0].spaces {
296 for rid in space.revisions.keys() {
297 assert_eq!(
298 format!("{:?}", indexes[0].resolve(*sid, *rid).unwrap()),
299 format!("{:?}", indexes[1].resolve(*sid, *rid).unwrap())
300 );
301 }
302 if *sid != indexes[0].root {
303 assert_eq!(space.labels, indexes[1].spaces[sid].labels);
304 let revision = indexes[0]
305 .resolve(*sid, space.labels[&(ExGuid::default(), 1)])
306 .unwrap();
307 for object in revision.objects.values() {
308 if let Some(onestore::FileDataReference::Internal(guid)) =
309 object.file_reference().unwrap()
310 {
311 assert_eq!(
312 stores[0].file_data(guid).unwrap(),
313 stores[1].file_data(guid).unwrap()
314 );
315 }
316 }
317 }
318 }
319 }
320}
321
322#[test]
323fn section_reordering_refreshes_stale_native_metadata_levels() {
324 let source = include_bytes!(
325 "../../../corpus/page-lifecycle/movement/02-promoted-parent/notebook/Lifecycle.one"
326 );
327 let ids = pages(source);
328 let edits: Vec<_> = ids[3..6]
329 .iter()
330 .enumerate()
331 .map(|(i, sid)| PageEdit::move_to(*sid, None, u32::try_from(i + 1).unwrap()).unwrap())
332 .collect();
333 let prepared = ops::section_op(source, SectionOp::Pages(edits.to_vec())).unwrap();
334 let stores = [source.as_slice(), prepared.as_bytes()].map(|b| Store::parse(b).unwrap());
335 let indexes = stores.each_ref().map(|s| RevisionIndex::parse(s).unwrap());
336 let documents = indexes.each_ref().map(|i| Document::parse(i).unwrap());
337 let levels = documents.each_ref().map(|document| {
338 let section = &document.spaces[&document.root];
339 let view = &section.revisions[&section.contexts[&ExGuid::default()]];
340 let copies: Vec<_> = view
341 .nodes
342 .iter()
343 .filter_map(|(id, node)| match &node.kind {
344 Kind::Metadata {
345 title: Some(title),
346 level,
347 } if title == "Same title" => Some((*id, *level)),
348 _ => None,
349 })
350 .collect();
351 assert_eq!(copies.len(), 1);
352 copies[0]
353 });
354 assert_eq!(levels[0].0, levels[1].0);
355 assert_eq!(levels[0].1, Some(2));
356 assert_eq!(levels[1].1, Some(1));
357 for (sid, space) in &indexes[0].spaces {
358 for rid in space.revisions.keys() {
359 assert_eq!(
360 format!("{:?}", indexes[0].resolve(*sid, *rid).unwrap()),
361 format!("{:?}", indexes[1].resolve(*sid, *rid).unwrap())
362 );
363 }
364 }
365}
366
367#[test]
368fn metadata_copy_order_does_not_change_page_identity() {
369 let source = include_bytes!(
370 "../../../corpus/page-lifecycle/page-edits/optional-cache/source-cold/notebook/Lifecycle.one"
371 );
372 let sid = pages(source)[3];
373 let edited = ops::section_op(
374 source,
375 SectionOp::Pages([PageEdit::set_level(sid, 2).unwrap()].to_vec()),
376 )
377 .unwrap();
378 let expected = include_bytes!(
379 "../../../corpus/page-lifecycle/page-edits/optional-cache/candidate/Lifecycle.one"
380 );
381 export("reordered-metadata", edited.as_bytes());
382 verify(source, edited.as_bytes(), expected);
383 let stores = [edited.as_bytes(), expected.as_slice()].map(|b| Store::parse(b).unwrap());
384 let indexes = stores.each_ref().map(|s| RevisionIndex::parse(s).unwrap());
385 let documents = indexes.each_ref().map(|i| Document::parse(i).unwrap());
386 for sid in documents[0].spaces.keys() {
387 let nodes = documents.each_ref().map(|d| {
388 let space = &d.spaces[sid];
389 serde_json::to_value(&space.revisions[&space.contexts[&ExGuid::default()]].nodes)
390 .unwrap()
391 });
392 assert_eq!(nodes[0], nodes[1], "{sid}");
393 }
394}
395
396#[test]
397fn native_edits_on_moved_pages_support_more_rust_changes() {
398 let source = include_bytes!(
399 "../../../corpus/page-lifecycle/page-edits/native/cold/notebook/Lifecycle.one"
400 );
401 let store = Store::parse(source).unwrap();
402 let index = RevisionIndex::parse(&store).unwrap();
403 let document = Document::parse(&index).unwrap();
404 let (sid, _) = *document.pages().unwrap().last().unwrap();
405 let space = &document.spaces[&sid];
406 let view = &space.revisions[&space.contexts[&ExGuid::default()]];
407 assert!(
408 matches!(&view.nodes[&view.roots[&2]].kind, Kind::Metadata { title: Some(title), level: Some(1) } if title == "Native moved 🦋 é")
409 );
410 let bodies: Vec<_> = view.nodes.iter().filter_map(|(id, node)|
411 matches!(&node.kind, Kind::RichText { text, .. } if text == "Native body after a Rust page move.").then_some(*id)).collect();
412 let [body] = bodies.as_slice() else { panic!() };
413 let moved = ops::section_op(
414 source,
415 SectionOp::Pages([PageEdit::set_level(sid, 2).unwrap()].to_vec()),
416 )
417 .unwrap();
418 let edited = ops::page_op(
419 moved.as_bytes(),
420 sid,
421 PageOp::Text {
422 text: *body,
423 range: 0..0,
424 with: "Rust + ".into(),
425 },
426 )
427 .unwrap();
428 let after_store = Store::parse(edited.as_bytes()).unwrap();
429 let after_index = RevisionIndex::parse(&after_store).unwrap();
430 let after = Document::parse(&after_index).unwrap();
431 assert_eq!(after.pages().unwrap(), document.pages().unwrap());
432 let space = &after.spaces[&sid];
433 let view = &space.revisions[&space.contexts[&ExGuid::default()]];
434 assert!(
435 matches!(&view.nodes[&view.roots[&2]].kind, Kind::Metadata { title: Some(title), level: Some(2) } if title == "Native moved 🦋 é")
436 );
437 assert!(
438 matches!(&view.nodes[body].kind, Kind::RichText { text, .. } if text == "Rust + Native body after a Rust page move.")
439 );
440 for (sid, space) in &index.spaces {
441 for rid in space.revisions.keys() {
442 assert_eq!(
443 format!("{:?}", index.resolve(*sid, *rid).unwrap()),
444 format!("{:?}", after_index.resolve(*sid, *rid).unwrap())
445 );
446 }
447 }
448 export("rust-followup", edited.as_bytes());
449}
450
451#[test]
452fn grouped_page_levels_and_membership_survive_each_storage_interruption() {
453 let mut source = onestore::create_section("movement.one", "Original", "Author").unwrap();
454 for _ in 0..2 {
455 let page = onestore::PageCreation::new(None, Some("Same title"), "Author").unwrap();
456 source = ops::section_op(&source, SectionOp::Create(page.clone()))
457 .unwrap()
458 .as_bytes()
459 .to_vec();
460 }
461 for (source, first) in [
462 (source.as_slice(), 1),
463 (
464 include_bytes!(
465 "../../../corpus/page-lifecycle/page-edits/optional-cache/source/Lifecycle.one"
466 )
467 .as_slice(),
468 3,
469 ),
470 ] {
471 let ids = pages(source);
472 let edits = [
473 PageEdit::set_level(ids[first], 2).unwrap(),
474 PageEdit::set_level(ids[first + 1], 3).unwrap(),
475 ];
476 let prepared = ops::section_op(source, SectionOp::Pages(edits.to_vec())).unwrap();
477 let old = current::current(source);
478 let new = current::current(prepared.as_bytes());
479 assert_eq!(new.len(), old.len());
480 // Byte-at-a-time writes tear the commit at every byte, a full sweep's matrix.
481 let limits: &[usize] = if sweep::full().is_some() {
482 &[1, 17, 4096]
483 } else {
484 &[17, 4096]
485 };
486 for &write_limit in limits {
487 let mut complete = disk::Disk {
488 visible: source.to_vec(),
489 durable: source.to_vec(),
490 operation: 0,
491 fail_at: None,
492 write_limit,
493 random: 1957,
494 };
495 prepared.commit(&mut complete).unwrap();
496 assert_eq!(complete.durable, prepared.as_bytes());
497 for fail_at in 1..=complete.operation {
498 let mut interrupted = disk::Disk {
499 visible: source.to_vec(),
500 durable: source.to_vec(),
501 operation: 0,
502 fail_at: Some(fail_at),
503 write_limit,
504 random: 1957 + fail_at as u64,
505 };
506 prepared.commit(&mut interrupted).unwrap_err();
507 let observed = current::current(&interrupted.durable);
508 assert!(
509 observed == old || observed == new,
510 "{write_limit}:{fail_at}"
511 );
512 }
513 }
514 }
515}
516
517#[test]
518fn repeated_nesting_retains_series_history_across_revision_checkpoints() {
519 let mut source = onestore::create_section("movement.one", "Original", "Author").unwrap();
520 let page = onestore::PageCreation::new(None, Some("Child"), "Author").unwrap();
521 source = ops::section_op(&source, SectionOp::Create(page.clone()))
522 .unwrap()
523 .as_bytes()
524 .to_vec();
525 let initial = source.clone();
526 for step in 0..520 {
527 let level = if step % 2 == 0 { 2 } else { 1 };
528 let edit = PageEdit::set_level(page.space(), level).unwrap();
529 let written = ops::section_op(&source, SectionOp::Pages([edit].to_vec()))
530 .unwrap()
531 .as_bytes()
532 .to_vec();
533 let store = Store::parse(&written).unwrap();
534 let index = RevisionIndex::parse(&store).unwrap();
535 index.validate_current().unwrap();
536 let document = Document::parse(&index).unwrap();
537 assert_eq!(document.pages().unwrap().len(), 2);
538 let space = &document.spaces[&page.space()];
539 let view = &space.revisions[&space.contexts[&ExGuid::default()]];
540 assert!(
541 matches!(view.nodes[&view.roots[&2]].kind, Kind::Metadata { level: Some(n), .. } if n == level)
542 );
543 if step % 64 == 0 || matches!(step, 255 | 511 | 519) {
544 let mut old = written.clone();
545 old[96..100].copy_from_slice(&source[96..100]);
546 assert_eq!(current::current(&old), current::current(&source));
547 }
548 source = written;
549 }
550 let stores = [&initial, &source].map(|bytes| Store::parse(bytes).unwrap());
551 let indexes = stores
552 .each_ref()
553 .map(|store| RevisionIndex::parse(store).unwrap());
554 assert_eq!(
555 stores[1].header.transaction_count,
556 stores[0].header.transaction_count + 520
557 );
558 for (sid, space) in &indexes[0].spaces {
559 for rid in space.revisions.keys() {
560 assert_eq!(
561 format!("{:?}", indexes[0].resolve(*sid, *rid).unwrap()),
562 format!("{:?}", indexes[1].resolve(*sid, *rid).unwrap())
563 );
564 }
565 let current = &indexes[1].spaces[sid];
566 let rid = current.labels[&(ExGuid::default(), 1)];
567 assert!(
568 std::iter::successors(Some(rid), |rid| current.revisions[rid].dependency).count() < 20
569 );
570 }
571 if let Some(output) = std::env::var_os("ONESTORE_PAGE_MOVEMENT_STRESS_OUTPUT") {
572 assert!(
573 std::path::Path::new(&output).is_absolute(),
574 "Use an absolute fixture output directory"
575 );
576 std::fs::create_dir(&output).unwrap();
577 std::fs::write(std::path::Path::new(&output).join("movement.one"), &source).unwrap();
578 }
579}
580
581/// A move on a section OneNote merged, which holds empty page series, rewrites the series
582/// without them and gives the moved page one of its own.
583#[test]
584fn a_move_after_a_native_merge_drops_its_empty_series() {
585 let merged =
586 include_bytes!("../../../corpus/conflict-page/native-pages/merged/notebook/synthetic.one");
587 let series = |bytes: &[u8]| {
588 let store = Store::parse(bytes).unwrap();
589 let index = RevisionIndex::parse(&store).unwrap();
590 let document = Document::parse(&index).unwrap();
591 let space = &document.spaces[&document.root];
592 let view = &space.revisions[&space.contexts[&ExGuid::default()]];
593 view.nodes[&view.roots[&1]]
594 .children
595 .iter()
596 .map(|id| (*id, view.nodes[id].spaces.clone()))
597 .collect::<Vec<_>>()
598 };
599 let before = series(merged);
600 assert!(before.iter().any(|(_, spaces)| spaces.is_empty()));
601 let ids = pages(merged);
602 let moved = ops::section_op(
603 merged,
604 SectionOp::Pages(vec![PageEdit::move_to(ids[1], None, 1).unwrap()]),
605 )
606 .unwrap();
607 let after = series(moved.as_bytes());
608 assert!(after.iter().all(|(_, spaces)| !spaces.is_empty()));
609 let (id, spaces) = after.last().unwrap();
610 assert_eq!(spaces, &vec![ids[1]]);
611 assert!(before.iter().all(|(old, _)| old != id));
612 assert_eq!(
613 pages(moved.as_bytes()),
614 [&ids[..1], &ids[2..], &ids[1..2]].concat()
615 );
616 if let Some(output) = std::env::var_os("ONESTORE_EMPTY_SERIES_EXPORT") {
617 let output = std::path::Path::new(&output);
618 std::fs::create_dir(output).unwrap();
619 std::fs::write(output.join("synthetic.one"), moved.as_bytes()).unwrap();
620 }
621}