| ... | ... | @@ -305,6 +305,96 @@ fn large_files_travel_in_chunks() { |
| 305 | 305 | ); |
| 306 | 306 | } |
| 307 | 307 | |
| 308 | #[test] |
| 309 | fn large_section_appends_keep_the_room_connected_and_guests_converge() { |
| 310 | use notebook::{EditStatus, Replica, live::share::HostedRemote}; |
| 311 | use onestore::op::{Edit, Op, PageOp}; |
| 312 | use std::time::{Duration, Instant}; |
| 313 | |
| 314 | let directory = tempfile::tempdir().unwrap(); |
| 315 | let folder = notebook(directory.path()); |
| 316 | let file = folder.join("Garden.one"); |
| 317 | let mut original = "A".repeat(512 << 10); |
| 318 | let image = onestore::create_section("Garden.one", &original, "Fixture").unwrap(); |
| 319 | std::fs::write(&file, &image).unwrap(); |
| 320 | let (space, text, _) = server::text(&image); |
| 321 | let arena = Arena::default(); |
| 322 | let mut section = onestore::Section::open(&arena, image).unwrap(); |
| 323 | section |
| 324 | .apply( |
| 325 | "Fixture", |
| 326 | &Edit { |
| 327 | at: 134_000_000_000_000_000, |
| 328 | ops: vec![Op::Page { |
| 329 | space, |
| 330 | op: PageOp::Text { |
| 331 | text, |
| 332 | range: 0..1, |
| 333 | with: "a".into(), |
| 334 | }, |
| 335 | }], |
| 336 | }, |
| 337 | ) |
| 338 | .unwrap(); |
| 339 | section.seal().unwrap().unwrap().commit_file(&file).unwrap(); |
| 340 | original.replace_range(0..1, "a"); |
| 341 | let url = relay(Default::default()); |
| 342 | let sharing = Sharing::new("").unwrap(); |
| 343 | let host = host(&folder, &directory.path().join("host"), &sharing, &url); |
| 344 | let code = code(&host); |
| 345 | let (alice, alice_notebook) = guest("Alice", &code, &url, &directory.path().join("alice")); |
| 346 | let (bob, bob_notebook) = guest("Bob", &code, &url, &directory.path().join("bob")); |
| 347 | until("the guests never joined the room", || { |
| 348 | host.guests().len() == 2 |
| 349 | }); |
| 350 | let image = bob_notebook.read_section("Garden.one").unwrap(); |
| 351 | assert_eq!(alice_notebook.read_section("Garden.one").unwrap(), image); |
| 352 | let (space, text, _) = server::text(&image); |
| 353 | let writer = Replica::create(directory.path().join("writer.sqlite"), &image).unwrap(); |
| 354 | let reader = Replica::create(directory.path().join("reader.sqlite"), &image).unwrap(); |
| 355 | let append = "B".repeat(192 << 10); |
| 356 | let expected = format!("{original}{append}"); |
| 357 | let id = writer |
| 358 | .apply( |
| 359 | "Alice", |
| 360 | Edit { |
| 361 | at: 134_000_000_000_000_000, |
| 362 | ops: vec![Op::Page { |
| 363 | space, |
| 364 | op: PageOp::Text { |
| 365 | text, |
| 366 | range: original.len() as u32..original.len() as u32, |
| 367 | with: append, |
| 368 | }, |
| 369 | }], |
| 370 | }, |
| 371 | ) |
| 372 | .unwrap(); |
| 373 | writer |
| 374 | .sync_once(&mut HostedRemote::new(&alice, "Garden.one")) |
| 375 | .unwrap(); |
| 376 | let stored = std::fs::read(&file).unwrap(); |
| 377 | let added = stored.len() - image.len(); |
| 378 | assert!(added > relay::server::Config::default().max_message); |
| 379 | assert!(added + 1024 < stored.len() / 2); |
| 380 | assert_eq!(server::text(&stored).2, expected); |
| 381 | let Some(EditStatus::Published { revision }) = writer.status(id).unwrap() else { |
| 382 | panic!("the append has no durable receipt"); |
| 383 | }; |
| 384 | let store = onestore::Store::parse(&stored).unwrap(); |
| 385 | let index = onestore::RevisionIndex::parse(&store).unwrap(); |
| 386 | assert!(index.spaces[&space].revisions.contains_key(&revision)); |
| 387 | assert!(writer.pending().unwrap().is_empty()); |
| 388 | let mut remote = HostedRemote::new(&bob, "Garden.one"); |
| 389 | reader.sync_once(&mut remote).unwrap(); |
| 390 | assert_eq!(reader.snapshot().unwrap(), stored); |
| 391 | let deadline = Instant::now() + Duration::from_millis(1200); |
| 392 | while Instant::now() < deadline { |
| 393 | assert_eq!(host.guests().len(), 2, "the section append broke the room"); |
| 394 | std::thread::sleep(Duration::from_millis(10)); |
| 395 | } |
| 396 | } |
| 397 | |
| 308 | 398 | /// A guest that floods its host with requests is hung up on once too many wait, having had |
| 309 | 399 | /// answers to few of them, and the host goes on serving the others. |
| 310 | 400 | #[test] |