Skip to main content

commonware_runtime/utils/buffer/
mod.rs

1//! Buffers for reading and writing to [crate::Blob]s.
2
3use crate::WriteOptions;
4use futures::future::{BoxFuture, FutureExt as _, Shared};
5
6pub mod paged;
7mod read;
8mod tip;
9mod write;
10
11pub use read::Read;
12pub use write::Write;
13
14/// A shared sync result.
15///
16/// Handles returned by [Completion::handle] are detached observers of the same shared result:
17/// dropping one neither cancels the underlying sync nor consumes its result, and every
18/// observer sees the same outcome.
19#[derive(Clone)]
20struct Completion(Shared<BoxFuture<'static, Result<(), crate::Error>>>);
21
22impl Completion {
23    /// Return a handle for the sync result.
24    fn handle(&self) -> crate::Handle<()> {
25        crate::Handle::from_future(self.0.clone())
26    }
27
28    /// Wait for the sync result.
29    async fn wait(&self) -> Result<(), crate::Error> {
30        self.0.clone().await
31    }
32}
33
34impl From<crate::Handle<()>> for Completion {
35    fn from(handle: crate::Handle<()>) -> Self {
36        Self(handle.boxed().shared())
37    }
38}
39
40/// Tracks whether blob mutations still need a sync.
41///
42/// Callers rely on three properties:
43/// - Every operation that mutates the blob first waits for an in-flight sync, so a started
44///   sync's coverage is never disturbed by later writes.
45/// - [SyncState::start_sync] on a [SyncState::Pending] state returns the in-flight sync's
46///   handle (completed syncs resolve immediately), so re-requesting a sync is a cheap way to
47///   observe outstanding work.
48/// - A failure is never lost: every handle cloned from the shared completion reports it, and
49///   an unobserved failure surfaces on the next [SyncState::wait_for_pending] call. Callers
50///   propagate that error through the consuming writer operation.
51///
52/// The owner must be dropped after any failed operation.
53enum SyncState {
54    // No unsynced mutations.
55    Clean,
56    // Unsynced mutations need a sync.
57    Dirty,
58    // A started sync is in flight or awaits observation.
59    Pending(Completion),
60}
61
62impl SyncState {
63    /// Whether no mutation still needs a sync.
64    const fn is_clean(&self) -> bool {
65        matches!(self, Self::Clean)
66    }
67
68    /// Wait for an in-flight sync before reusing or mutating the blob.
69    async fn wait_for_pending(&mut self) -> Result<(), crate::Error> {
70        let Self::Pending(pending) = self else {
71            return Ok(());
72        };
73        pending.wait().await?;
74        *self = Self::Clean;
75        Ok(())
76    }
77
78    /// Write data with the provided options while tracking durability.
79    async fn write_at(
80        &mut self,
81        blob: &impl crate::Blob,
82        offset: u64,
83        bufs: impl Into<crate::IoBufs> + Send,
84        options: WriteOptions,
85    ) -> Result<(), crate::Error> {
86        self.wait_for_pending().await?;
87        let bufs = bufs.into();
88        if !options.contains(WriteOptions::SYNC) {
89            blob.write_at(offset, bufs, options).await?;
90            *self = Self::Dirty;
91            return Ok(());
92        }
93
94        match self {
95            Self::Dirty => {
96                // Earlier mutations need a full durability barrier too.
97                blob.write_at(offset, bufs, options.without(WriteOptions::SYNC))
98                    .await?;
99                blob.sync().await?;
100                *self = Self::Clean;
101                Ok(())
102            }
103            Self::Clean => blob.write_at(offset, bufs, options).await,
104            Self::Pending(_) => unreachable!("pending sync waited above"),
105        }
106    }
107
108    /// Resize the blob and require a later sync.
109    async fn resize(&mut self, blob: &impl crate::Blob, len: u64) -> Result<(), crate::Error> {
110        self.wait_for_pending().await?;
111        blob.resize(len).await?;
112        *self = Self::Dirty;
113        Ok(())
114    }
115
116    /// Make all pending mutations durable before returning.
117    async fn sync(&mut self, blob: &impl crate::Blob) -> Result<(), crate::Error> {
118        self.wait_for_pending().await?;
119        if matches!(self, Self::Clean) {
120            return Ok(());
121        }
122        blob.sync().await?;
123        *self = Self::Clean;
124        Ok(())
125    }
126
127    /// Start making pending mutations durable and return a handle for completion.
128    async fn start_sync(&mut self, blob: &impl crate::Blob) -> crate::Handle<()> {
129        match self {
130            Self::Clean => crate::Handle::ready(Ok(())),
131            Self::Dirty => {
132                // Store a shared completion so repeated calls observe the same sync.
133                let pending = Completion::from(blob.start_sync().await);
134                let handle = pending.handle();
135                *self = Self::Pending(pending);
136                handle
137            }
138            Self::Pending(pending) => pending.handle(),
139        }
140    }
141}
142
143#[cfg(test)]
144mod tests {
145    use super::*;
146    use crate::{
147        Blob as _, Error, Handle, IoBufMut, IoBufs, IoBufsMut, ReadOptions, Runner, Storage,
148        WriteOptions, deterministic,
149        mocks::{DelayedSyncBlob, WriteFaultContext, WriteFaults, next_pending_sync},
150    };
151    use commonware_macros::test_traced;
152    use commonware_utils::{NZUsize, sync::Mutex};
153    use futures::FutureExt;
154    use std::sync::Arc;
155
156    #[derive(Default)]
157    struct RangeSyncState {
158        /// All data currently stored in the blob.
159        ///
160        /// This includes every durable byte plus any newer bytes that have not
161        /// been made durable yet.
162        data: Vec<u8>,
163
164        /// Prefix/ranges of `data` that would survive a crash.
165        durable: Vec<u8>,
166
167        /// Number of write operations.
168        writes: usize,
169
170        /// Number of full sync barriers.
171        full_syncs: usize,
172
173        /// Number of range-scoped write syncs.
174        range_syncs: usize,
175
176        /// Number of writes carrying the uncached hint.
177        uncached_writes: usize,
178
179        /// Number of range-scoped write syncs carrying the uncached hint.
180        uncached_range_syncs: usize,
181    }
182
183    /// Test blob with separate visible and durable state.
184    ///
185    /// Writes and resizes only update `data`. `write_at(SYNC)` updates `data`
186    /// and then copies only that submitted range into `durable`. `sync` copies all
187    /// of `data` to `durable`. This lets tests assert that `Write::sync` uses range
188    /// sync only when no earlier unsynced mutation needs a full durability barrier.
189    pub struct SyncTrackingBlob {
190        state: Mutex<RangeSyncState>,
191    }
192
193    impl SyncTrackingBlob {
194        pub fn new() -> Self {
195            Self {
196                state: Mutex::new(RangeSyncState::default()),
197            }
198        }
199
200        pub fn snapshot(&self) -> (Vec<u8>, usize, usize, usize) {
201            let state = self.state.lock();
202            (
203                state.durable.clone(),
204                state.writes,
205                state.full_syncs,
206                state.range_syncs,
207            )
208        }
209
210        pub fn size(&self) -> u64 {
211            self.state.lock().data.len() as u64
212        }
213
214        pub fn uncached_snapshot(&self) -> (usize, usize) {
215            let state = self.state.lock();
216            (state.uncached_writes, state.uncached_range_syncs)
217        }
218
219        fn write(data: &mut Vec<u8>, offset: u64, buf: &[u8]) -> Result<(), Error> {
220            let start = usize::try_from(offset).map_err(|_| Error::OffsetOverflow)?;
221            let end = start.checked_add(buf.len()).ok_or(Error::OffsetOverflow)?;
222            if end > data.len() {
223                data.resize(end, 0);
224            }
225            data[start..end].copy_from_slice(buf);
226            Ok(())
227        }
228    }
229
230    impl crate::Blob for SyncTrackingBlob {
231        async fn read_at(
232            &self,
233            offset: u64,
234            len: usize,
235            options: ReadOptions,
236        ) -> Result<IoBufsMut, Error> {
237            self.read_at_buf(offset, len, IoBufMut::with_capacity(len), options)
238                .await
239        }
240
241        async fn read_at_buf(
242            &self,
243            offset: u64,
244            len: usize,
245            buf: impl Into<IoBufsMut> + Send,
246            _options: ReadOptions,
247        ) -> Result<IoBufsMut, Error> {
248            let start = usize::try_from(offset).map_err(|_| Error::OffsetOverflow)?;
249            let end = start.checked_add(len).ok_or(Error::OffsetOverflow)?;
250            let state = self.state.lock();
251            if end > state.data.len() {
252                return Err(Error::BlobInsufficientLength);
253            }
254
255            let mut out = buf.into();
256            assert!(out.capacity() >= len);
257            // SAFETY: `len` bytes are filled by copy_from_slice below.
258            unsafe { out.set_len(len) };
259            out.copy_from_slice(&state.data[start..end]);
260            Ok(out)
261        }
262
263        async fn write_at(
264            &self,
265            offset: u64,
266            buf: impl Into<IoBufs> + Send,
267            options: WriteOptions,
268        ) -> Result<(), Error> {
269            let buf = buf.into().coalesce();
270            let mut state = self.state.lock();
271            Self::write(&mut state.data, offset, buf.as_ref())?;
272            state.writes += 1;
273
274            let sync = options.contains(WriteOptions::SYNC);
275            if sync {
276                Self::write(&mut state.durable, offset, buf.as_ref())?;
277                state.range_syncs += 1;
278            }
279            if options.contains(WriteOptions::DONT_CACHE) {
280                if sync {
281                    state.uncached_range_syncs += 1;
282                } else {
283                    state.uncached_writes += 1;
284                }
285            }
286            Ok(())
287        }
288
289        async fn resize(&self, len: u64) -> Result<(), Error> {
290            let len = usize::try_from(len).map_err(|_| Error::OffsetOverflow)?;
291            self.state.lock().data.resize(len, 0);
292            Ok(())
293        }
294
295        async fn sync(&self) -> Result<(), Error> {
296            let mut state = self.state.lock();
297            state.durable = state.data.clone();
298            state.full_syncs += 1;
299            Ok(())
300        }
301
302        async fn start_sync(&self) -> Handle<()> {
303            Handle::ready(self.sync().await)
304        }
305    }
306
307    #[test_traced]
308    fn test_read_basic() {
309        let executor = deterministic::Runner::default();
310        executor.start(|context| async move {
311            // Test basic buffered reading functionality with sequential reads
312            let data = b"Hello, world! This is a test.";
313            let (blob, size) = context.open("partition", b"test").await.unwrap();
314            assert_eq!(size, 0);
315            blob.write_at(0, data, WriteOptions::default())
316                .await
317                .unwrap();
318            let size = data.len() as u64;
319
320            // Create a buffered reader with small buffer to test refilling
321            let mut reader = Read::new(blob, size, NZUsize!(10));
322
323            // Read some data
324            let read = reader.read(5).await.unwrap().coalesce();
325            assert_eq!(read.as_ref(), b"Hello");
326
327            // Read more data that requires a buffer refill
328            let read = reader.read(14).await.unwrap().coalesce();
329            assert_eq!(read.as_ref(), b", world! This ");
330
331            // Verify position tracking
332            assert_eq!(reader.position(), 19);
333
334            // Read the remaining data
335            let read = reader.read(7).await.unwrap().coalesce();
336            assert_eq!(read.as_ref(), b"is a te");
337
338            // Attempt to read beyond the end should fail
339            let result = reader.read(5).await;
340            assert!(matches!(result, Err(Error::BlobInsufficientLength)));
341        });
342    }
343
344    #[test_traced]
345    fn test_read_cross_boundary() {
346        let executor = deterministic::Runner::default();
347        executor.start(|context| async move {
348            // Test reading data that spans multiple buffer refills
349            let data = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ";
350            let (blob, size) = context.open("partition", b"test").await.unwrap();
351            assert_eq!(size, 0);
352            blob.write_at(0, data, WriteOptions::default())
353                .await
354                .unwrap();
355            let size = data.len() as u64;
356
357            // Use a buffer smaller than the total data size
358            let mut reader = Read::new(blob, size, NZUsize!(10));
359
360            // Read data that crosses buffer boundaries
361            let read = reader.read(15).await.unwrap().coalesce();
362            assert_eq!(read.as_ref(), b"ABCDEFGHIJKLMNO");
363
364            // Verify position tracking
365            assert_eq!(reader.position(), 15);
366
367            // Read the remaining data
368            let read = reader.read(11).await.unwrap().coalesce();
369            assert_eq!(read.as_ref(), b"PQRSTUVWXYZ");
370
371            // Verify we're at the end
372            assert_eq!(reader.position(), 26);
373            assert_eq!(reader.blob_remaining(), 0);
374        });
375    }
376
377    // Regression test for https://github.com/commonwarexyz/monorepo/issues/1348
378    #[test_traced]
379    fn test_read_to_end_then_rewind_and_read_again() {
380        let executor = deterministic::Runner::default();
381        executor.start(|context| async move {
382            let data = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ";
383            let (blob, size) = context.open("partition", b"test").await.unwrap();
384            assert_eq!(size, 0);
385            blob.write_at(0, data, WriteOptions::default())
386                .await
387                .unwrap();
388            let size = data.len() as u64;
389
390            let mut reader = Read::new(blob, size, NZUsize!(20));
391
392            // Read data that crosses buffer boundaries
393            let read = reader.read(21).await.unwrap().coalesce();
394            assert_eq!(read.as_ref(), b"ABCDEFGHIJKLMNOPQRSTU");
395
396            // Verify position tracking
397            assert_eq!(reader.position(), 21);
398
399            // Read the remaining data
400            let read = reader.read(5).await.unwrap().coalesce();
401            assert_eq!(read.as_ref(), b"VWXYZ");
402
403            // Rewind and read again
404            reader.seek_to(0).unwrap();
405            let read = reader.read(21).await.unwrap().coalesce();
406            assert_eq!(read.as_ref(), b"ABCDEFGHIJKLMNOPQRSTU");
407        });
408    }
409
410    #[test_traced]
411    fn test_read_with_known_size() {
412        let executor = deterministic::Runner::default();
413        executor.start(|context| async move {
414            // Test reader behavior with known blob size limits
415            let data = b"This is a test with known size limitations.";
416            let (blob, size) = context.open("partition", b"test").await.unwrap();
417            assert_eq!(size, 0);
418            blob.write_at(0, data, WriteOptions::default())
419                .await
420                .unwrap();
421            let size = data.len() as u64;
422
423            // Create a buffered reader with buffer smaller than total data
424            let mut reader = Read::new(blob, size, NZUsize!(10));
425
426            // Check initial remaining bytes
427            assert_eq!(reader.blob_remaining(), size);
428
429            // Read partial data
430            let read = reader.read(5).await.unwrap().coalesce();
431            assert_eq!(read.as_ref(), b"This ");
432
433            // Check remaining bytes after partial read
434            assert_eq!(reader.blob_remaining(), size - 5);
435
436            // Read exactly up to the size limit
437            let read = reader.read((size - 5) as usize).await.unwrap().coalesce();
438            assert_eq!(read.as_ref(), b"is a test with known size limitations.");
439
440            // Verify we're at the end
441            assert_eq!(reader.blob_remaining(), 0);
442
443            // Reading beyond the end should fail
444            let result = reader.read(1).await;
445            assert!(matches!(result, Err(Error::BlobInsufficientLength)));
446        });
447    }
448
449    #[test_traced]
450    fn test_read_oversized_request_does_not_consume_buffered_bytes() {
451        let executor = deterministic::Runner::default();
452        executor.start(|context| async move {
453            let data = b"abcdefghij";
454            let (blob, size) = context
455                .open("partition", b"double-count-regression")
456                .await
457                .unwrap();
458            assert_eq!(size, 0);
459            blob.write_at(0, data, WriteOptions::default())
460                .await
461                .unwrap();
462
463            let mut reader = Read::new(blob, data.len() as u64, NZUsize!(8));
464
465            // Fill the internal buffer and consume most of it (2 bytes remain buffered).
466            let first = reader.read(6).await.unwrap().coalesce();
467            assert_eq!(first.as_ref(), b"abcdef");
468            assert_eq!(reader.position(), 6);
469
470            // Only 4 bytes remain total, so this must fail without consuming anything.
471            let err = reader.read(5).await.unwrap_err();
472            assert!(matches!(err, Error::BlobInsufficientLength));
473            assert_eq!(reader.position(), 6);
474
475            // Remaining bytes should still be readable in full.
476            let tail = reader.read(4).await.unwrap().coalesce();
477            assert_eq!(tail.as_ref(), b"ghij");
478            assert_eq!(reader.position(), 10);
479        });
480    }
481
482    #[test_traced]
483    fn test_read_large_data() {
484        let executor = deterministic::Runner::default();
485        executor.start(|context| async move {
486            // Test reading large amounts of data in chunks
487            let data_size = 1024 * 256; // 256KB of data
488            let data = vec![0x42; data_size];
489            let (blob, size) = context.open("partition", b"test").await.unwrap();
490            assert_eq!(size, 0);
491            blob.write_at(0, data.clone(), WriteOptions::default())
492                .await
493                .unwrap();
494            let size = data.len() as u64;
495
496            // Use a buffer much smaller than the total data
497            let mut reader = Read::new(blob, size, NZUsize!(64 * 1024));
498
499            // Read all data in smaller chunks
500            let mut total_read = 0;
501            let chunk_size = 8 * 1024; // 8KB chunks
502
503            while total_read < data_size {
504                let to_read = std::cmp::min(chunk_size, data_size - total_read);
505                let read = reader.read(to_read).await.unwrap().coalesce();
506
507                // Verify data integrity
508                assert!(
509                    read.as_ref().iter().all(|&b| b == 0x42),
510                    "Data at position {total_read} is not correct"
511                );
512
513                total_read += to_read;
514            }
515
516            // Verify we read everything
517            assert_eq!(total_read, data_size);
518
519            // Reading beyond the end should fail
520            let result = reader.read(1).await;
521            assert!(matches!(result, Err(Error::BlobInsufficientLength)));
522        });
523    }
524
525    #[test_traced]
526    fn test_read_exact_size_reads() {
527        let executor = deterministic::Runner::default();
528        executor.start(|context| async move {
529            // Create a blob with exactly 2.5 buffer sizes of data
530            let buffer_size = 1024;
531            let data_size = buffer_size * 5 / 2; // 2.5 buffers
532            let data = vec![0x37; data_size];
533
534            let (blob, size) = context.open("partition", b"test").await.unwrap();
535            assert_eq!(size, 0);
536            blob.write_at(0, data.clone(), WriteOptions::default())
537                .await
538                .unwrap();
539            let size = data.len() as u64;
540
541            let mut reader = Read::new(blob, size, NZUsize!(buffer_size));
542
543            // Read exactly one buffer size
544            let read = reader.read(buffer_size).await.unwrap().coalesce();
545            assert!(read.as_ref().iter().all(|&b| b == 0x37));
546
547            // Read exactly one buffer size more
548            let read = reader.read(buffer_size).await.unwrap().coalesce();
549            assert!(read.as_ref().iter().all(|&b| b == 0x37));
550
551            // Read the remaining half buffer
552            let half_buffer = buffer_size / 2;
553            let read = reader.read(half_buffer).await.unwrap().coalesce();
554            assert!(read.as_ref().iter().all(|&b| b == 0x37));
555
556            // Verify we're at the end
557            assert_eq!(reader.blob_remaining(), 0);
558            assert_eq!(reader.position(), size);
559        });
560    }
561
562    #[test_traced]
563    fn test_read_structure_single_vs_chunked() {
564        let executor = deterministic::Runner::default();
565        executor.start(|context| async move {
566            let data = b"ABCDEFGHIJKL";
567            let (blob, size) = context.open("partition", b"structural").await.unwrap();
568            assert_eq!(size, 0);
569            blob.write_at(0, data, WriteOptions::default())
570                .await
571                .unwrap();
572
573            let mut reader = Read::new(blob, data.len() as u64, NZUsize!(5));
574
575            // First read fits in one fetched chunk.
576            let first = reader.read(3).await.unwrap();
577            assert!(first.is_single());
578            assert_eq!(first.coalesce().as_ref(), b"ABC");
579
580            // This read spans refill boundaries and should be represented as multiple chunks.
581            let second = reader.read(7).await.unwrap();
582            assert!(!second.is_single());
583            assert_eq!(second.coalesce().as_ref(), b"DEFGHIJ");
584        });
585    }
586
587    #[test_traced]
588    fn test_read_seek_to() {
589        let executor = deterministic::Runner::default();
590        executor.start(|context| async move {
591            // Create a memory blob with some test data
592            let data = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ";
593            let (blob, size) = context.open("partition", b"test").await.unwrap();
594            assert_eq!(size, 0);
595            blob.write_at(0, data, WriteOptions::default())
596                .await
597                .unwrap();
598            let size = data.len() as u64;
599
600            // Create a buffer reader
601            let mut reader = Read::new(blob, size, NZUsize!(10));
602
603            // Read some data to advance the position
604            let read = reader.read(5).await.unwrap().coalesce();
605            assert_eq!(read.as_ref(), b"ABCDE");
606            assert_eq!(reader.position(), 5);
607
608            // Seek to a specific position
609            reader.seek_to(10).unwrap();
610            assert_eq!(reader.position(), 10);
611
612            // Read data from the new position
613            let read = reader.read(5).await.unwrap().coalesce();
614            assert_eq!(read.as_ref(), b"KLMNO");
615
616            // Seek to beginning
617            reader.seek_to(0).unwrap();
618            assert_eq!(reader.position(), 0);
619
620            let read = reader.read(5).await.unwrap().coalesce();
621            assert_eq!(read.as_ref(), b"ABCDE");
622
623            // Seek to end
624            reader.seek_to(size).unwrap();
625            assert_eq!(reader.position(), size);
626
627            // Trying to read should fail
628            let result = reader.read(1).await;
629            assert!(matches!(result, Err(Error::BlobInsufficientLength)));
630
631            // Seek beyond end should fail
632            let result = reader.seek_to(size + 10);
633            assert!(matches!(result, Err(Error::BlobInsufficientLength)));
634        });
635    }
636
637    #[test_traced]
638    fn test_read_seek_with_refill() {
639        let executor = deterministic::Runner::default();
640        executor.start(|context| async move {
641            // Create a memory blob with longer data
642            let data = vec![0x41; 1000]; // 1000 'A' characters
643            let (blob, size) = context.open("partition", b"test").await.unwrap();
644            assert_eq!(size, 0);
645            blob.write_at(0, data.clone(), WriteOptions::default())
646                .await
647                .unwrap();
648            let size = data.len() as u64;
649
650            // Create a buffer reader with small buffer
651            let mut reader = Read::new(blob, size, NZUsize!(10));
652
653            // Read some data
654            let _ = reader.read(5).await.unwrap().coalesce();
655
656            // Seek far ahead, past the current buffer
657            reader.seek_to(500).unwrap();
658
659            // Read data - should get data from position 500
660            let read = reader.read(5).await.unwrap().coalesce();
661            assert_eq!(read.as_ref(), b"AAAAA"); // Should still be 'A's);
662            assert_eq!(reader.position(), 505);
663
664            // Seek backwards
665            reader.seek_to(100).unwrap();
666
667            // Read again - should be at position 100
668            let _ = reader.read(5).await.unwrap().coalesce();
669            assert_eq!(reader.position(), 105);
670        });
671    }
672
673    #[test_traced]
674    fn test_read_seek_within_buffered_range() {
675        let executor = deterministic::Runner::default();
676        executor.start(|context| async move {
677            let data = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ";
678            let (blob, size) = context.open("partition", b"test").await.unwrap();
679            assert_eq!(size, 0);
680            blob.write_at(0, data, WriteOptions::default())
681                .await
682                .unwrap();
683
684            let mut reader = Read::new(blob, data.len() as u64, NZUsize!(10));
685
686            // Reads 0..=5, while the internal fetch cursor advances to 10.
687            let read = reader.read(6).await.unwrap().coalesce();
688            assert_eq!(read.as_ref(), b"ABCDEF");
689            assert_eq!(reader.position(), 6);
690            assert_eq!(reader.buffer_remaining(), 4);
691
692            // Seek back within [buffer_start, fetch_position).
693            reader.seek_to(3).unwrap();
694            assert_eq!(reader.position(), 3);
695            assert_eq!(reader.buffer_remaining(), 7);
696
697            let read = reader.read(5).await.unwrap().coalesce();
698            assert_eq!(read.as_ref(), b"DEFGH");
699            assert_eq!(reader.position(), 8);
700            assert_eq!(reader.buffer_remaining(), 2);
701        });
702    }
703
704    #[test_traced]
705    fn test_read_seek_within_unread_buffer_does_not_refill() {
706        let executor = deterministic::Runner::default();
707        executor.start(|context| async move {
708            let data = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ";
709            let (blob, size) = context
710                .open("partition", b"seek_unread_no_refill")
711                .await
712                .unwrap();
713            assert_eq!(size, 0);
714            blob.write_at(0, data, WriteOptions::default())
715                .await
716                .unwrap();
717
718            let mut reader = Read::new(blob, data.len() as u64, NZUsize!(10));
719
720            // First read triggers a single refill of 10 bytes.
721            let first = reader.read(6).await.unwrap();
722            assert_eq!(first.coalesce().as_ref(), b"ABCDEF");
723            assert_eq!(reader.position(), 6);
724            assert_eq!(reader.buffer_remaining(), 4);
725
726            // Seek within the unread buffered window [6, 10).
727            reader.seek_to(7).unwrap();
728            assert_eq!(reader.position(), 7);
729            assert_eq!(reader.buffer_remaining(), 3);
730
731            // Consume only from the already buffered window.
732            let second = reader.read(3).await.unwrap();
733            assert_eq!(second.coalesce().as_ref(), b"HIJ");
734            assert_eq!(reader.position(), 10);
735            assert_eq!(reader.buffer_remaining(), 0);
736
737            // Refill should happen only now (at exhaustion), not at seek/read above.
738            let third = reader.read(1).await.unwrap();
739            assert_eq!(third.coalesce().as_ref(), b"K");
740            assert_eq!(reader.position(), 11);
741            assert_eq!(reader.buffer_remaining(), 9);
742        });
743    }
744
745    #[test_traced]
746    fn test_read_resize() {
747        let executor = deterministic::Runner::default();
748        executor.start(|context| async move {
749            // Create a memory blob with some test data
750            let data = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ";
751            let (blob, size) = context.open("partition", b"test").await.unwrap();
752            assert_eq!(size, 0);
753            blob.write_at(0, data, WriteOptions::default())
754                .await
755                .unwrap();
756            let data_len = data.len() as u64;
757
758            // Create a buffer reader
759            let reader = Read::new(blob, data_len, NZUsize!(10));
760
761            // Resize the blob to half its size
762            let resize_len = data_len / 2;
763            reader.resize(resize_len).await.unwrap();
764
765            // Reopen to check truncation
766            let (blob, size) = context.open("partition", b"test").await.unwrap();
767            assert_eq!(size, resize_len, "Blob should be resized to half size");
768
769            // Create a new buffer and read to verify truncation
770            let mut new_reader = Read::new(blob, size, NZUsize!(10));
771
772            // Read the content
773            let read = new_reader.read(size as usize).await.unwrap().coalesce();
774            assert_eq!(
775                read.as_ref(),
776                b"ABCDEFGHIJKLM",
777                "Resized content should match"
778            );
779
780            // Reading beyond resized size should fail
781            let result = new_reader.read(1).await;
782            assert!(matches!(result, Err(Error::BlobInsufficientLength)));
783
784            // Test resize to larger size
785            new_reader.resize(data_len * 2).await.unwrap();
786
787            // Reopen to check resize
788            let (blob, new_size) = context.open("partition", b"test").await.unwrap();
789            assert_eq!(new_size, data_len * 2);
790
791            // Create a new buffer and read to verify resize
792            let mut new_reader = Read::new(blob, new_size, NZUsize!(10));
793            let read = new_reader.read(new_size as usize).await.unwrap().coalesce();
794            assert_eq!(&read.as_ref()[..size as usize], b"ABCDEFGHIJKLM");
795            assert_eq!(
796                &read.as_ref()[size as usize..],
797                vec![0u8; new_size as usize - size as usize]
798            );
799        });
800    }
801
802    #[test_traced]
803    fn test_read_resize_to_zero() {
804        let executor = deterministic::Runner::default();
805        executor.start(|context| async move {
806            // Create a memory blob with some test data
807            let data = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ";
808            let data_len = data.len() as u64;
809            let (blob, size) = context.open("partition", b"test").await.unwrap();
810            assert_eq!(size, 0);
811            blob.write_at(0, data, WriteOptions::default())
812                .await
813                .unwrap();
814
815            // Create a buffer reader
816            let reader = Read::new(blob, data_len, NZUsize!(10));
817
818            // Resize the blob to zero
819            reader.resize(0).await.unwrap();
820
821            // Reopen to check truncation
822            let (blob, size) = context.open("partition", b"test").await.unwrap();
823            assert_eq!(size, 0, "Blob should be resized to zero");
824
825            // Create a new buffer and try to read (should fail)
826            let mut new_reader = Read::new(blob, size, NZUsize!(10));
827
828            // Reading from resized blob should fail
829            let result = new_reader.read(1).await;
830            assert!(matches!(result, Err(Error::BlobInsufficientLength)));
831        });
832    }
833
834    #[test_traced]
835    fn test_write_basic() {
836        let executor = deterministic::Runner::default();
837        executor.start(|context| async move {
838            // Test basic buffered write and sync functionality
839            let (blob, size) = context.open("partition", b"write_basic").await.unwrap();
840            assert_eq!(size, 0);
841
842            let mut writer = Write::new(blob, size, NZUsize!(8));
843            writer = writer.write_at(0, b"hello").await.unwrap();
844            assert_eq!(writer.size(), 5);
845            writer = writer.sync().await.unwrap();
846            assert_eq!(writer.size(), 5);
847
848            // Verify data was written correctly
849            drop(writer);
850            let (blob, size) = context.open("partition", b"write_basic").await.unwrap();
851            assert_eq!(size, 5);
852            let mut reader = Read::new(blob, size, NZUsize!(8));
853            let read = reader.read(5).await.unwrap().coalesce();
854            assert_eq!(read.as_ref(), b"hello");
855        });
856    }
857
858    #[test_traced]
859    fn test_write_multiple_flushes() {
860        let executor = deterministic::Runner::default();
861        executor.start(|context| async move {
862            // Test writes that cause buffer flushes due to capacity limits
863            let (blob, size) = context.open("partition", b"write_multi").await.unwrap();
864            assert_eq!(size, 0);
865
866            let mut writer = Write::new(blob, size, NZUsize!(4));
867            writer = writer.write_at(0, b"abc").await.unwrap();
868            assert_eq!(writer.size(), 3);
869            writer = writer.write_at(3, b"defg").await.unwrap();
870            assert_eq!(writer.size(), 7);
871            writer = writer.sync().await.unwrap();
872
873            // Verify the final result
874            drop(writer);
875            let (blob, size) = context.open("partition", b"write_multi").await.unwrap();
876            assert_eq!(size, 7);
877            let mut reader = Read::new(blob, size, NZUsize!(4));
878            let read = reader.read(7).await.unwrap().coalesce();
879            assert_eq!(read.as_ref(), b"abcdefg");
880        });
881    }
882
883    #[test_traced]
884    fn test_write_large_data() {
885        let executor = deterministic::Runner::default();
886        executor.start(|context| async move {
887            // Test writing data larger than buffer capacity (direct write)
888            let (blob, size) = context.open("partition", b"write_large").await.unwrap();
889            assert_eq!(size, 0);
890
891            let mut writer = Write::new(blob, size, NZUsize!(4));
892            writer = writer.write_at(0, b"abc").await.unwrap();
893            assert_eq!(writer.size(), 3);
894            writer = writer
895                .write_at(3, b"defghijklmnopqrstuvwxyz")
896                .await
897                .unwrap();
898            assert_eq!(writer.size(), 26);
899            writer = writer.sync().await.unwrap();
900            assert_eq!(writer.size(), 26);
901
902            // Verify the complete data
903            drop(writer);
904            let (blob, size) = context.open("partition", b"write_large").await.unwrap();
905            assert_eq!(size, 26);
906            let mut reader = Read::new(blob, size, NZUsize!(4));
907            let read = reader.read(26).await.unwrap().coalesce();
908            assert_eq!(read.as_ref(), b"abcdefghijklmnopqrstuvwxyz");
909        });
910    }
911
912    #[test_traced]
913    fn test_write_append_to_buffer() {
914        let executor = deterministic::Runner::default();
915        executor.start(|context| async move {
916            // Test sequential appends that exceed buffer capacity
917            let (blob, size) = context.open("partition", b"append_buf").await.unwrap();
918            let mut writer = Write::new(blob, size, NZUsize!(10));
919
920            // Write data that fits in buffer
921            writer = writer.write_at(0, b"hello").await.unwrap();
922            assert_eq!(writer.size(), 5);
923
924            // Append data that causes buffer flush
925            writer = writer.write_at(5, b" world").await.unwrap();
926            writer = writer.sync().await.unwrap();
927            assert_eq!(writer.size(), 11);
928
929            // Verify the complete result
930            drop(writer);
931            let (blob, size) = context.open("partition", b"append_buf").await.unwrap();
932            assert_eq!(size, 11);
933            let mut reader = Read::new(blob, size, NZUsize!(10));
934            let read = reader.read(11).await.unwrap().coalesce();
935            assert_eq!(read.as_ref(), b"hello world");
936        });
937    }
938
939    #[test_traced]
940    fn test_write_into_middle_of_buffer() {
941        let executor = deterministic::Runner::default();
942        executor.start(|context| async move {
943            // Test overwriting data within the buffer and extending it
944            let (blob, size) = context.open("partition", b"middle_buf").await.unwrap();
945            let mut writer = Write::new(blob, size, NZUsize!(20));
946
947            // Initial write
948            writer = writer.write_at(0, b"abcdefghij").await.unwrap();
949            assert_eq!(writer.size(), 10);
950
951            // Overwrite middle section
952            writer = writer.write_at(2, b"01234").await.unwrap();
953            assert_eq!(writer.size(), 10);
954            writer = writer.sync().await.unwrap();
955
956            // Verify overwrite result
957            drop(writer);
958            let (blob, size) = context.open("partition", b"middle_buf").await.unwrap();
959            let blob = Arc::new(blob);
960            assert_eq!(size, 10);
961            let mut reader = Read::new(blob.clone(), size, NZUsize!(10));
962            let read = reader.read(10).await.unwrap().coalesce();
963            assert_eq!(read.as_ref(), b"ab01234hij");
964            drop(reader);
965            let mut writer = Write::new(blob, size, NZUsize!(20));
966
967            // Extend buffer and do partial overwrite
968            writer = writer.write_at(10, b"klmnopqrst").await.unwrap();
969            assert_eq!(writer.size(), 20);
970            writer = writer.write_at(9, b"wxyz").await.unwrap();
971            assert_eq!(writer.size(), 20);
972            writer = writer.sync().await.unwrap();
973
974            // Verify final result
975            drop(writer);
976            let (blob, size) = context.open("partition", b"middle_buf").await.unwrap();
977            assert_eq!(size, 20);
978            let mut reader = Read::new(blob, size, NZUsize!(20));
979            let read = reader.read(20).await.unwrap().coalesce();
980            assert_eq!(read.as_ref(), b"ab01234hiwxyznopqrst");
981        });
982    }
983
984    #[test_traced]
985    fn test_write_before_buffer() {
986        let executor = deterministic::Runner::default();
987        executor.start(|context| async move {
988            // Test writing at offsets before the current buffer position
989            let (blob, size) = context.open("partition", b"before_buf").await.unwrap();
990            let mut writer = Write::new(blob, size, NZUsize!(10));
991
992            // Write data at a later offset first
993            writer = writer.write_at(10, b"0123456789").await.unwrap();
994            assert_eq!(writer.size(), 20);
995
996            // Write at an earlier offset (should flush buffer first)
997            writer = writer.write_at(0, b"abcde").await.unwrap();
998            assert_eq!(writer.size(), 20);
999            writer = writer.sync().await.unwrap();
1000
1001            // Verify data placement with gap
1002            drop(writer);
1003            let (blob, size) = context.open("partition", b"before_buf").await.unwrap();
1004            let blob = Arc::new(blob);
1005            assert_eq!(size, 20);
1006            let mut reader = Read::new(blob.clone(), size, NZUsize!(20));
1007            let read = reader.read(20).await.unwrap().coalesce();
1008            let mut expected = vec![0u8; 20];
1009            expected[0..5].copy_from_slice("abcde".as_bytes());
1010            expected[10..20].copy_from_slice("0123456789".as_bytes());
1011            assert_eq!(read.as_ref(), expected.as_slice());
1012            drop(reader);
1013            let mut writer = Write::new(blob, size, NZUsize!(10));
1014
1015            // Fill the gap between existing data
1016            writer = writer.write_at(5, b"fghij").await.unwrap();
1017            assert_eq!(writer.size(), 20);
1018            writer = writer.sync().await.unwrap();
1019            assert_eq!(writer.size(), 20);
1020
1021            // Verify gap is filled
1022            drop(writer);
1023            let (blob, size) = context.open("partition", b"before_buf").await.unwrap();
1024            assert_eq!(size, 20);
1025            let mut reader = Read::new(blob, size, NZUsize!(20));
1026            let read = reader.read(20).await.unwrap().coalesce();
1027            expected[0..10].copy_from_slice("abcdefghij".as_bytes());
1028            assert_eq!(read.as_ref(), expected.as_slice());
1029        });
1030    }
1031
1032    #[test_traced]
1033    fn test_write_resize() {
1034        let executor = deterministic::Runner::default();
1035        executor.start(|context| async move {
1036            // Test blob resize functionality and subsequent writes
1037            let (blob, size) = context.open("partition", b"resize_write").await.unwrap();
1038            let mut writer = Write::new(blob, size, NZUsize!(10));
1039
1040            // Write initial data
1041            writer = writer.write_at(0, b"hello world").await.unwrap();
1042            assert_eq!(writer.size(), 11);
1043            writer = writer.sync().await.unwrap();
1044            assert_eq!(writer.size(), 11);
1045
1046            drop(writer);
1047            let (blob_check, size_check) =
1048                context.open("partition", b"resize_write").await.unwrap();
1049            assert_eq!(size_check, 11);
1050            let mut writer = Write::new(blob_check, size_check, NZUsize!(10));
1051
1052            // Resize to smaller size
1053            writer = writer.resize(5).await.unwrap();
1054            assert_eq!(writer.size(), 5);
1055            writer = writer.sync().await.unwrap();
1056
1057            // Verify resize
1058            drop(writer);
1059            let (blob, size) = context.open("partition", b"resize_write").await.unwrap();
1060            let blob = Arc::new(blob);
1061            assert_eq!(size, 5);
1062            let mut reader = Read::new(blob.clone(), size, NZUsize!(5));
1063            let read = reader.read(5).await.unwrap().coalesce();
1064            assert_eq!(read.as_ref(), b"hello");
1065            drop(reader);
1066            let mut writer = Write::new(blob, size, NZUsize!(10));
1067
1068            // Write to resized blob
1069            writer = writer.write_at(0, b"X").await.unwrap();
1070            assert_eq!(writer.size(), 5);
1071            writer = writer.sync().await.unwrap();
1072
1073            // Verify overwrite
1074            drop(writer);
1075            let (blob, size) = context.open("partition", b"resize_write").await.unwrap();
1076            let blob = Arc::new(blob);
1077            assert_eq!(size, 5);
1078            let mut reader = Read::new(blob.clone(), size, NZUsize!(5));
1079            let read = reader.read(5).await.unwrap().coalesce();
1080            assert_eq!(read.as_ref(), b"Xello");
1081            drop(reader);
1082            let mut writer = Write::new(blob, size, NZUsize!(10));
1083
1084            // Test resize to larger size
1085            writer = writer.resize(10).await.unwrap();
1086            assert_eq!(writer.size(), 10);
1087            writer = writer.sync().await.unwrap();
1088
1089            // Verify resize
1090            drop(writer);
1091            let (blob, size) = context.open("partition", b"resize_write").await.unwrap();
1092            assert_eq!(size, 10);
1093            let mut reader = Read::new(blob, size, NZUsize!(10));
1094            let read = reader.read(10).await.unwrap().coalesce();
1095            assert_eq!(&read.as_ref()[0..5], b"Xello");
1096            assert_eq!(&read.as_ref()[5..10], [0u8; 5]);
1097
1098            // Test resize to zero
1099            let (blob_zero, size) = context.open("partition", b"resize_zero").await.unwrap();
1100            let mut writer_zero = Write::new(blob_zero, size, NZUsize!(10));
1101            writer_zero = writer_zero.write_at(0, b"some data").await.unwrap();
1102            assert_eq!(writer_zero.size(), 9);
1103            writer_zero = writer_zero.sync().await.unwrap();
1104            assert_eq!(writer_zero.size(), 9);
1105            writer_zero = writer_zero.resize(0).await.unwrap();
1106            assert_eq!(writer_zero.size(), 0);
1107            writer_zero = writer_zero.sync().await.unwrap();
1108            assert_eq!(writer_zero.size(), 0);
1109
1110            // Ensure the blob is empty
1111            drop(writer_zero);
1112            let (_, size_z) = context.open("partition", b"resize_zero").await.unwrap();
1113            assert_eq!(size_z, 0);
1114        });
1115    }
1116
1117    #[test_traced]
1118    fn test_write_resize_grow_flushes_buffered_data() {
1119        let executor = deterministic::Runner::default();
1120        executor.start(|context| async move {
1121            let (blob, size) = context.open("partition", b"resize_grow").await.unwrap();
1122            let mut writer = Write::new(blob, size, NZUsize!(10));
1123
1124            writer = writer.write_at(0, b"hello").await.unwrap();
1125            writer = writer.resize(10).await.unwrap();
1126            writer = writer.sync().await.unwrap();
1127
1128            let read = writer.read_at(0, 5).await.unwrap().coalesce();
1129            assert_eq!(read.as_ref(), b"hello");
1130        });
1131    }
1132
1133    #[test_traced]
1134    fn test_write_read_at_on_writer() {
1135        let executor = deterministic::Runner::default();
1136        executor.start(|context| async move {
1137            // Test reading through writer's read_at method (buffer + blob reads)
1138            let (blob, size) = context.open("partition", b"read_at_writer").await.unwrap();
1139            let mut writer = Write::new(blob, size, NZUsize!(10));
1140
1141            // Write data that stays in buffer
1142            writer = writer.write_at(0, b"buffered").await.unwrap();
1143            assert_eq!(writer.size(), 8);
1144
1145            // Read from buffer via writer
1146            let read_buf_vec = writer.read_at(0, 4).await.unwrap().coalesce();
1147            assert_eq!(read_buf_vec, b"buff");
1148
1149            let read_buf_vec = writer.read_at(4, 4).await.unwrap().coalesce();
1150            assert_eq!(read_buf_vec, b"ered");
1151
1152            // Reading past buffer end should fail
1153            assert!(writer.read_at(8, 1).await.is_err());
1154
1155            // Write large data that flushes buffer
1156            writer = writer.write_at(8, b" and flushed").await.unwrap();
1157            assert_eq!(writer.size(), 20);
1158            writer = writer.sync().await.unwrap();
1159            assert_eq!(writer.size(), 20);
1160
1161            // Read from underlying blob through writer
1162            let read_buf_vec_2 = writer.read_at(0, 4).await.unwrap().coalesce();
1163            assert_eq!(read_buf_vec_2, b"buff");
1164
1165            let read_buf_7_vec = writer.read_at(13, 7).await.unwrap().coalesce();
1166            assert_eq!(read_buf_7_vec, b"flushed");
1167
1168            // Buffer new data at the end
1169            writer = writer.write_at(20, b" more data").await.unwrap();
1170            assert_eq!(writer.size(), 30);
1171
1172            // Read newly buffered data
1173            let read_buf_vec_3 = writer.read_at(20, 5).await.unwrap().coalesce();
1174            assert_eq!(read_buf_vec_3, b" more");
1175
1176            // Read spanning both blob and buffer
1177            let combo_read_buf_vec = writer.read_at(16, 12).await.unwrap();
1178            assert_eq!(combo_read_buf_vec.coalesce(), b"shed more da");
1179
1180            // Verify complete content by reopening
1181            writer = writer.sync().await.unwrap();
1182            assert_eq!(writer.size(), 30);
1183            drop(writer);
1184            let (final_blob, final_size) =
1185                context.open("partition", b"read_at_writer").await.unwrap();
1186            assert_eq!(final_size, 30);
1187            let mut final_reader = Read::new(final_blob, final_size, NZUsize!(30));
1188            let read = final_reader.read(30).await.unwrap().coalesce();
1189            assert_eq!(read.as_ref(), b"buffered and flushed more data");
1190        });
1191    }
1192
1193    #[test_traced]
1194    fn test_write_zero_length_read_past_eof_errors() {
1195        let executor = deterministic::Runner::default();
1196        executor.start(|context| async move {
1197            let (blob, size) = context.open("partition", b"zero_len_probe").await.unwrap();
1198            let mut writer = Write::new(blob, size, NZUsize!(8));
1199            writer = writer.write_at(0, b"abc").await.unwrap();
1200
1201            let empty = writer.read_at(3, 0).await.unwrap();
1202            assert!(empty.is_empty());
1203
1204            let err = writer.read_at(4, 0).await.unwrap_err();
1205            assert!(matches!(err, Error::BlobInsufficientLength));
1206        });
1207    }
1208
1209    #[test_traced]
1210    fn test_write_straddling_non_mergeable() {
1211        let executor = deterministic::Runner::default();
1212        executor.start(|context| async move {
1213            // Test writes that cannot be merged into buffer (non-contiguous/too large)
1214            let (blob, size) = context.open("partition", b"write_straddle").await.unwrap();
1215            let mut writer = Write::new(blob, size, NZUsize!(10));
1216
1217            // Fill buffer completely
1218            writer = writer.write_at(0, b"0123456789").await.unwrap();
1219            assert_eq!(writer.size(), 10);
1220
1221            // Write at non-contiguous offset (should flush then write directly)
1222            writer = writer.write_at(15, b"abc").await.unwrap();
1223            assert_eq!(writer.size(), 18);
1224            writer = writer.sync().await.unwrap();
1225            assert_eq!(writer.size(), 18);
1226
1227            // Verify data with gap
1228            drop(writer);
1229            let (blob_check, size_check) =
1230                context.open("partition", b"write_straddle").await.unwrap();
1231            assert_eq!(size_check, 18);
1232            let mut reader = Read::new(blob_check, size_check, NZUsize!(20));
1233            let read = reader.read(18).await.unwrap().coalesce();
1234
1235            let mut expected = vec![0u8; 18];
1236            expected[0..10].copy_from_slice(b"0123456789");
1237            expected[15..18].copy_from_slice(b"abc");
1238            assert_eq!(read.as_ref(), expected.as_slice());
1239
1240            // Test write that exceeds buffer capacity
1241            let (blob2, size) = context.open("partition", b"write_straddle2").await.unwrap();
1242            let mut writer2 = Write::new(blob2, size, NZUsize!(10));
1243            writer2 = writer2.write_at(0, b"0123456789").await.unwrap();
1244            assert_eq!(writer2.size(), 10);
1245
1246            // Write large data that exceeds capacity
1247            writer2 = writer2.write_at(5, b"ABCDEFGHIJKL").await.unwrap();
1248            assert_eq!(writer2.size(), 17);
1249            writer2 = writer2.sync().await.unwrap();
1250            assert_eq!(writer2.size(), 17);
1251
1252            // Verify overwrite result
1253            drop(writer2);
1254            let (blob_check2, size_check2) =
1255                context.open("partition", b"write_straddle2").await.unwrap();
1256            assert_eq!(size_check2, 17);
1257            let mut reader2 = Read::new(blob_check2, size_check2, NZUsize!(20));
1258            let read = reader2.read(17).await.unwrap().coalesce();
1259            assert_eq!(read.as_ref(), b"01234ABCDEFGHIJKL");
1260        });
1261    }
1262
1263    #[test_traced]
1264    fn test_write_close() {
1265        let executor = deterministic::Runner::default();
1266        executor.start(|context| async move {
1267            // Buffer bytes to persist before releasing the writer.
1268            let (blob_orig, size) = context.open("partition", b"write_close").await.unwrap();
1269            let mut writer = Write::new(blob_orig, size, NZUsize!(8));
1270            writer = writer.write_at(0, b"pending").await.unwrap();
1271            assert_eq!(writer.size(), 7);
1272
1273            // Sync writer to persist data
1274            writer = writer.sync().await.unwrap();
1275
1276            // Verify data persistence
1277            drop(writer);
1278            let (blob_check, size_check) = context.open("partition", b"write_close").await.unwrap();
1279            assert_eq!(size_check, 7);
1280            let mut reader = Read::new(blob_check, size_check, NZUsize!(8));
1281            let read = reader.read(7).await.unwrap().coalesce();
1282            assert_eq!(read.as_ref(), b"pending");
1283        });
1284    }
1285
1286    #[test_traced]
1287    fn test_write_direct_due_to_size() {
1288        let executor = deterministic::Runner::default();
1289        executor.start(|context| async move {
1290            // Test direct writes when data exceeds buffer capacity
1291            let (blob, size) = context
1292                .open("partition", b"write_direct_size")
1293                .await
1294                .unwrap();
1295            let mut writer = Write::new(blob, size, NZUsize!(5));
1296
1297            // Write data larger than buffer capacity (should write directly)
1298            let data_large = b"0123456789";
1299            writer = writer.write_at(0, data_large).await.unwrap();
1300            assert_eq!(writer.size(), 10);
1301
1302            // Sync to ensure data is persisted
1303            writer = writer.sync().await.unwrap();
1304
1305            // Verify direct write worked
1306            drop(writer);
1307            let (blob_check, size_check) = context
1308                .open("partition", b"write_direct_size")
1309                .await
1310                .unwrap();
1311            let blob_check = Arc::new(blob_check);
1312            assert_eq!(size_check, 10);
1313            let mut reader = Read::new(blob_check.clone(), size_check, NZUsize!(10));
1314            let read = reader.read(10).await.unwrap().coalesce();
1315            assert_eq!(read.as_ref(), data_large.as_slice());
1316            drop(reader);
1317            let mut writer = Write::new(blob_check, size_check, NZUsize!(5));
1318
1319            // Now write small data that should be buffered
1320            writer = writer.write_at(10, b"abc").await.unwrap();
1321            assert_eq!(writer.size(), 13);
1322
1323            // Verify it's in buffer by reading through writer
1324            let read_small_buf_vec = writer.read_at(10, 3).await.unwrap().coalesce();
1325            assert_eq!(read_small_buf_vec, b"abc");
1326
1327            writer = writer.sync().await.unwrap();
1328
1329            // Verify final state
1330            drop(writer);
1331            let (blob_check2, size_check2) = context
1332                .open("partition", b"write_direct_size")
1333                .await
1334                .unwrap();
1335            assert_eq!(size_check2, 13);
1336            let mut reader2 = Read::new(blob_check2, size_check2, NZUsize!(13));
1337            let read = reader2.read(13).await.unwrap().coalesce();
1338            assert_eq!(&read.as_ref()[10..], b"abc".as_slice());
1339        });
1340    }
1341
1342    #[test_traced]
1343    fn test_write_overwrite_and_extend_in_buffer() {
1344        let executor = deterministic::Runner::default();
1345        executor.start(|context| async move {
1346            // Test complex buffer operations: overwrite and extend within capacity
1347            let (blob, size) = context
1348                .open("partition", b"overwrite_extend_buf")
1349                .await
1350                .unwrap();
1351            let mut writer = Write::new(blob, size, NZUsize!(15));
1352
1353            // Write initial data
1354            writer = writer.write_at(0, b"0123456789").await.unwrap();
1355            assert_eq!(writer.size(), 10);
1356
1357            // Overwrite and extend within buffer capacity
1358            writer = writer.write_at(5, b"ABCDEFGHIJ").await.unwrap();
1359            assert_eq!(writer.size(), 15);
1360
1361            // Verify buffer content through writer
1362            let read_buf_vec = writer.read_at(0, 15).await.unwrap().coalesce();
1363            assert_eq!(read_buf_vec, b"01234ABCDEFGHIJ");
1364
1365            writer = writer.sync().await.unwrap();
1366
1367            // Verify persisted result
1368            drop(writer);
1369            let (blob_check, size_check) = context
1370                .open("partition", b"overwrite_extend_buf")
1371                .await
1372                .unwrap();
1373            assert_eq!(size_check, 15);
1374            let mut reader = Read::new(blob_check, size_check, NZUsize!(15));
1375            let read = reader.read(15).await.unwrap().coalesce();
1376            assert_eq!(read.as_ref(), b"01234ABCDEFGHIJ".as_slice());
1377        });
1378    }
1379
1380    #[test_traced]
1381    fn test_write_at_size() {
1382        let executor = deterministic::Runner::default();
1383        executor.start(|context| async move {
1384            // Test writing at the current logical end of the blob
1385            let (blob, size) = context.open("partition", b"write_end").await.unwrap();
1386            let mut writer = Write::new(blob, size, NZUsize!(20));
1387
1388            // Write initial data
1389            writer = writer.write_at(0, b"0123456789").await.unwrap();
1390            assert_eq!(writer.size(), 10);
1391            writer = writer.sync().await.unwrap();
1392
1393            // Append at the current size (logical end)
1394            let end = writer.size();
1395            writer = writer.write_at(end, b"abc").await.unwrap();
1396            assert_eq!(writer.size(), 13);
1397            writer = writer.sync().await.unwrap();
1398
1399            // Verify complete result
1400            drop(writer);
1401            let (blob_check, size_check) = context.open("partition", b"write_end").await.unwrap();
1402            assert_eq!(size_check, 13);
1403            let mut reader = Read::new(blob_check, size_check, NZUsize!(13));
1404            let read = reader.read(13).await.unwrap().coalesce();
1405            assert_eq!(read.as_ref(), b"0123456789abc");
1406        });
1407    }
1408
1409    #[test_traced]
1410    fn test_write_at_size_multiple_appends() {
1411        let executor = deterministic::Runner::default();
1412        executor.start(|context| async move {
1413            // Test multiple appends using writer.size()
1414            let (blob, size) = context
1415                .open("partition", b"write_multiple_appends_at_size")
1416                .await
1417                .unwrap();
1418            let mut writer = Write::new(blob, size, NZUsize!(5));
1419
1420            // First write
1421            writer = writer.write_at(0, b"AAA").await.unwrap();
1422            assert_eq!(writer.size(), 3);
1423            writer = writer.sync().await.unwrap();
1424            assert_eq!(writer.size(), 3);
1425
1426            // Append using size()
1427            let end = writer.size();
1428            writer = writer.write_at(end, b"BBB").await.unwrap();
1429            assert_eq!(writer.size(), 6); // 3 (AAA) + 3 (BBB)
1430            writer = writer.sync().await.unwrap();
1431            assert_eq!(writer.size(), 6);
1432
1433            // Append again using size()
1434            let end = writer.size();
1435            writer = writer.write_at(end, b"CCC").await.unwrap();
1436            assert_eq!(writer.size(), 9); // 6 + 3 (CCC)
1437            writer = writer.sync().await.unwrap();
1438            assert_eq!(writer.size(), 9);
1439
1440            // Verify final content
1441            drop(writer);
1442            let (blob_check, size_check) = context
1443                .open("partition", b"write_multiple_appends_at_size")
1444                .await
1445                .unwrap();
1446            assert_eq!(size_check, 9);
1447            let mut reader = Read::new(blob_check, size_check, NZUsize!(9));
1448            let read = reader.read(9).await.unwrap().coalesce();
1449            assert_eq!(read.as_ref(), b"AAABBBCCC");
1450        });
1451    }
1452
1453    #[test_traced]
1454    fn test_write_non_contiguous_then_append_at_size() {
1455        let executor = deterministic::Runner::default();
1456        executor.start(|context| async move {
1457            // Test writing non-contiguously, then appending at the new size
1458            let (blob, size) = context
1459                .open("partition", b"write_non_contiguous_then_append")
1460                .await
1461                .unwrap();
1462            let mut writer = Write::new(blob, size, NZUsize!(10));
1463
1464            // Buffer the initial seven bytes at the logical tip.
1465            writer = writer.write_at(0, b"INITIAL").await.unwrap();
1466            assert_eq!(writer.size(), 7);
1467
1468            // Non-contiguous write, forces flush of "INITIAL" and direct write of "NONCONTIG"
1469            writer = writer.write_at(20, b"NONCONTIG").await.unwrap();
1470            assert_eq!(writer.size(), 29);
1471            writer = writer.sync().await.unwrap();
1472            assert_eq!(writer.size(), 29);
1473
1474            // Append at the new size
1475            let end = writer.size();
1476            writer = writer.write_at(end, b"APPEND").await.unwrap();
1477            assert_eq!(writer.size(), 35);
1478            writer = writer.sync().await.unwrap();
1479            assert_eq!(writer.size(), 35);
1480
1481            // Verify final content
1482            drop(writer);
1483            let (blob_check, size_check) = context
1484                .open("partition", b"write_non_contiguous_then_append")
1485                .await
1486                .unwrap();
1487            assert_eq!(size_check, 35);
1488            let mut reader = Read::new(blob_check, size_check, NZUsize!(35));
1489            let read = reader.read(35).await.unwrap().coalesce();
1490
1491            let mut expected = vec![0u8; 35];
1492            expected[0..7].copy_from_slice(b"INITIAL");
1493            expected[20..29].copy_from_slice(b"NONCONTIG");
1494            expected[29..35].copy_from_slice(b"APPEND");
1495            assert_eq!(read.as_ref(), expected.as_slice());
1496        });
1497    }
1498
1499    #[test_traced]
1500    fn test_write_resize_then_append_at_size() {
1501        let executor = deterministic::Runner::default();
1502        executor.start(|context| async move {
1503            // Test truncating, then appending at the new size
1504            let (blob, size) = context
1505                .open("partition", b"resize_then_append_at_size")
1506                .await
1507                .unwrap();
1508            let mut writer = Write::new(blob, size, NZUsize!(10));
1509
1510            // Persist the initial 16 bytes, leaving the tip buffer empty.
1511            writer = writer.write_at(0, b"0123456789ABCDEF").await.unwrap();
1512            assert_eq!(writer.size(), 16);
1513            writer = writer.sync().await.unwrap();
1514            assert_eq!(writer.size(), 16);
1515
1516            // Durably shorten the blob and its logical tip to five bytes.
1517            let resize_to = 5;
1518            writer = writer.resize(resize_to).await.unwrap();
1519            assert_eq!(writer.size(), resize_to);
1520            writer = writer.sync().await.unwrap();
1521            assert_eq!(writer.size(), resize_to);
1522
1523            // Buffer five more bytes at the resized tip, then persist the combined contents.
1524            let end = writer.size();
1525            writer = writer.write_at(end, b"XXXXX").await.unwrap();
1526            assert_eq!(writer.size(), 10);
1527            writer = writer.sync().await.unwrap();
1528            assert_eq!(writer.size(), 10);
1529
1530            // Verify final content
1531            drop(writer);
1532            let (blob_check, size_check) = context
1533                .open("partition", b"resize_then_append_at_size")
1534                .await
1535                .unwrap();
1536            assert_eq!(size_check, 10);
1537            let mut reader = Read::new(blob_check, size_check, NZUsize!(10));
1538            let read = reader.read(10).await.unwrap().coalesce();
1539            assert_eq!(read.as_ref(), b"01234XXXXX");
1540        });
1541    }
1542
1543    /// A failed flush inside `start_sync` returns an error.
1544    #[test_traced]
1545    fn test_write_start_sync_flush_failure_returns_err() {
1546        let executor = deterministic::Runner::default();
1547        executor.start(|context| async move {
1548            let faults = WriteFaults::default();
1549            let context = WriteFaultContext {
1550                inner: context,
1551                faults: faults.clone(),
1552            };
1553            let (blob, size) = context
1554                .open("partition", b"start_sync_flush_failure")
1555                .await
1556                .unwrap();
1557            let writer = Write::new(blob, size, NZUsize!(8));
1558
1559            // Buffer a write so start_sync must flush it.
1560            let writer = writer.write_at(0, b"abc").await.unwrap();
1561
1562            // The flush inside start_sync fails.
1563            faults.arm();
1564            assert!(writer.start_sync().await.is_err());
1565        });
1566    }
1567
1568    /// `try_write_at` merges a write that fits the tip buffer and leaves the writer unchanged
1569    /// otherwise.
1570    #[test_traced]
1571    fn test_write_try_write_at_merges_only_when_it_fits() {
1572        let executor = deterministic::Runner::default();
1573        executor.start(|context| async move {
1574            let (blob, _) = context.open("partition", b"try_write_at").await.unwrap();
1575            let mut writer = Write::new(blob, 0, NZUsize!(8));
1576
1577            // A write within the buffer's capacity merges without I/O.
1578            assert!(writer.try_write_at(0, b"abc"));
1579            assert_eq!(writer.size(), 3);
1580
1581            // A write past the buffer's capacity is refused and changes nothing.
1582            assert!(!writer.try_write_at(3, b"0123456789"));
1583            assert_eq!(writer.size(), 3);
1584
1585            // An overflowing offset is refused, leaving write_at to report the overflow.
1586            assert!(!writer.try_write_at(u64::MAX, b"x"));
1587            assert_eq!(writer.size(), 3);
1588
1589            // An empty write leaves the writer unchanged, like write_at.
1590            assert!(writer.try_write_at(6, &[]));
1591            assert_eq!(writer.size(), 3);
1592
1593            // The merged bytes read back and persist through a sync.
1594            let writer = writer.sync().await.unwrap();
1595            let read = writer.read_at(0, 3).await.unwrap().coalesce();
1596            assert_eq!(read.as_ref(), b"abc");
1597        });
1598    }
1599
1600    /// `needs_sync` reports whether `sync` has work: buffered bytes, unsynced mutations, or a
1601    /// started sync that the writer has not observed.
1602    #[test_traced]
1603    fn test_write_needs_sync_tracks_sync_work() {
1604        let executor = deterministic::Runner::default();
1605        executor.start(|_| async move {
1606            let (blob, pending) = DelayedSyncBlob::new(SyncTrackingBlob::new());
1607            let writer = Write::new(blob, 0, NZUsize!(8));
1608
1609            // A fresh writer may wrap unsynced bytes, so it starts dirty. Syncing cleans it.
1610            assert!(writer.needs_sync());
1611            let mut writer = writer.sync().await.unwrap();
1612            assert!(!writer.needs_sync());
1613
1614            // A buffered write is flush work even though the blob is clean.
1615            writer = writer.write_at(0, b"abc").await.unwrap();
1616            assert!(writer.needs_sync());
1617            writer = writer.sync().await.unwrap();
1618            assert!(!writer.needs_sync());
1619
1620            // A started sync needs observation while it is in flight and after it completes.
1621            writer = writer.write_at(3, b"d").await.unwrap();
1622            let (writer, handle) = writer.start_sync().await.unwrap();
1623            assert!(writer.needs_sync());
1624            next_pending_sync(&pending).release.send(Ok(())).unwrap();
1625            handle.await.unwrap();
1626            assert!(writer.needs_sync());
1627            let writer = writer.wait_for_sync().await.unwrap();
1628            assert!(!writer.needs_sync());
1629
1630            // A failed started sync keeps the writer in need of a sync. The handle reports the
1631            // failure, and so does the next sync.
1632            let writer = writer.write_at(4, b"e").await.unwrap();
1633            let (writer, handle) = writer.start_sync().await.unwrap();
1634            next_pending_sync(&pending)
1635                .release
1636                .send(Err(Error::Closed))
1637                .unwrap();
1638            assert!(handle.await.is_err());
1639            assert!(writer.needs_sync());
1640            assert!(writer.sync().await.is_err());
1641        });
1642    }
1643
1644    /// Observing a completed started sync lets the next write use range sync.
1645    #[test_traced]
1646    fn test_write_start_sync_persists_and_marks_clean() {
1647        let executor = deterministic::Runner::default();
1648        executor.start(|_| async move {
1649            let blob = SyncTrackingBlob::new();
1650            let blob = Arc::new(blob);
1651            let mut writer = Write::new(blob.clone(), 0, NZUsize!(8));
1652
1653            // Start a sync for buffered bytes and wait for the returned handle.
1654            writer = writer.write_at(0, b"abc").await.unwrap();
1655            let (mut writer, handle) = writer.start_sync().await.unwrap();
1656            handle.await.unwrap();
1657
1658            // The buffered write required a full sync because the fresh writer starts dirty.
1659            let (durable, writes, full_syncs, range_syncs) = blob.snapshot();
1660            assert_eq!(durable.as_slice(), b"abc");
1661            assert_eq!(writes, 1);
1662            assert_eq!(full_syncs, 1);
1663            assert_eq!(range_syncs, 0);
1664
1665            // The next mutation observes the completed sync and can sync just its new range.
1666            writer = writer.write_at(3, b"d").await.unwrap();
1667            writer = writer.sync().await.unwrap();
1668            let (durable, writes, full_syncs, range_syncs) = blob.snapshot();
1669            assert_eq!(durable.as_slice(), b"abcd");
1670            assert_eq!(writes, 2);
1671            assert_eq!(full_syncs, 1);
1672            assert_eq!(range_syncs, 1);
1673
1674            // Nothing left to sync.
1675            let (_, handle) = writer.start_sync().await.unwrap();
1676            handle.await.unwrap();
1677            let (_, _, full_syncs, range_syncs) = blob.snapshot();
1678            assert_eq!(full_syncs, 1);
1679            assert_eq!(range_syncs, 1);
1680        });
1681    }
1682
1683    // Verifies sync waits for an outstanding start_sync instead of starting new disk work.
1684    #[test_traced]
1685    fn test_write_sync_waits_for_outstanding_start_sync() {
1686        let executor = deterministic::Runner::default();
1687        executor.start(|_| async move {
1688            let inner = SyncTrackingBlob::new();
1689            let inner = Arc::new(inner);
1690            let (blob, pending) = DelayedSyncBlob::new(inner.clone());
1691            let writer = Write::new(blob, 0, NZUsize!(8));
1692
1693            // Hold the started sync open so a later sync cannot finish right away.
1694            let (writer, handle) = writer.start_sync().await.unwrap();
1695            let deferred = next_pending_sync(&pending);
1696
1697            // The attempted sync reaches the pending handle and cannot complete yet.
1698            let mut sync = Box::pin(writer.sync());
1699            assert!(
1700                sync.as_mut().now_or_never().is_none(),
1701                "sync must wait for the outstanding start_sync handle"
1702            );
1703            deferred
1704                .blocked
1705                .await
1706                .expect("sync never waited on start_sync");
1707
1708            let (_, _, full_syncs, range_syncs) = inner.snapshot();
1709            assert_eq!(full_syncs, 0);
1710            assert_eq!(range_syncs, 0);
1711
1712            // Releasing the original handle lets sync observe the completed disk sync.
1713            deferred.release.send(Ok(())).unwrap();
1714            sync.await.unwrap();
1715            handle.await.unwrap();
1716            let (_, _, full_syncs, range_syncs) = inner.snapshot();
1717            assert_eq!(full_syncs, 1);
1718            assert_eq!(range_syncs, 0);
1719        });
1720    }
1721
1722    // Verifies writes made after start_sync wait before they are flushed.
1723    #[test_traced]
1724    fn test_write_sync_after_start_sync_and_small_write_waits_before_range_sync() {
1725        let executor = deterministic::Runner::default();
1726        executor.start(|_| async move {
1727            let inner = SyncTrackingBlob::new();
1728            let inner = Arc::new(inner);
1729            let (blob, pending) = DelayedSyncBlob::new(inner.clone());
1730            let writer = Write::new(blob, 0, NZUsize!(8));
1731
1732            // Begin syncing the initial dirty state and keep that sync blocked.
1733            let (mut writer, handle) = writer.start_sync().await.unwrap();
1734            let deferred = next_pending_sync(&pending);
1735
1736            // The tip must not reach the blob while the earlier sync is pending.
1737            writer = writer.write_at(0, b"abc").await.unwrap();
1738            let mut sync = Box::pin(writer.sync());
1739            assert!(
1740                sync.as_mut().now_or_never().is_none(),
1741                "sync must wait for the outstanding start_sync before flushing the small write"
1742            );
1743            deferred
1744                .blocked
1745                .await
1746                .expect("sync never waited on start_sync");
1747
1748            let (_, writes, full_syncs, range_syncs) = inner.snapshot();
1749            assert_eq!(writes, 0);
1750            assert_eq!(full_syncs, 0);
1751            assert_eq!(range_syncs, 0);
1752
1753            // After the earlier sync completes, the buffered write can be persisted.
1754            deferred.release.send(Ok(())).unwrap();
1755            sync.await.unwrap();
1756            handle.await.unwrap();
1757
1758            let (durable, writes, full_syncs, range_syncs) = inner.snapshot();
1759            assert_eq!(durable.as_slice(), b"abc");
1760            assert_eq!(writes, 1);
1761            assert_eq!(full_syncs, 1);
1762            assert_eq!(range_syncs, 1);
1763        });
1764    }
1765
1766    // Verifies overlapping writes wait before flushing buffered bytes while start_sync is pending.
1767    #[test_traced]
1768    fn test_write_at_overlap_flush_waits_for_outstanding_start_sync() {
1769        let executor = deterministic::Runner::default();
1770        executor.start(|_| async move {
1771            let inner = SyncTrackingBlob::new();
1772            let inner = Arc::new(inner);
1773            inner
1774                .write_at(0, b"xxx", WriteOptions::default())
1775                .await
1776                .unwrap();
1777
1778            let (blob, pending) = DelayedSyncBlob::new(inner.clone());
1779            let writer = Write::new(blob, inner.size(), NZUsize!(8));
1780
1781            let (mut writer, handle) = writer.start_sync().await.unwrap();
1782            let deferred = next_pending_sync(&pending);
1783
1784            // This append is local while the earlier sync is pending.
1785            writer = writer.write_at(3, b"abc").await.unwrap();
1786
1787            // The drained tip must not reach the blob while the earlier sync is pending.
1788            let mut write = Box::pin(writer.write_at(2, b"ZZ"));
1789            assert!(
1790                write.as_mut().now_or_never().is_none(),
1791                "overlapping write must wait for the outstanding start_sync before flushing"
1792            );
1793            deferred
1794                .blocked
1795                .await
1796                .expect("write never waited on start_sync");
1797
1798            let (_, writes, full_syncs, range_syncs) = inner.snapshot();
1799            assert_eq!(writes, 1);
1800            assert_eq!(full_syncs, 0);
1801            assert_eq!(range_syncs, 0);
1802
1803            // Releasing the sync lets the parked write reach the blob.
1804            deferred.release.send(Ok(())).unwrap();
1805            write.await.unwrap();
1806            handle.await.unwrap();
1807
1808            let (_, writes, full_syncs, range_syncs) = inner.snapshot();
1809            assert_eq!(writes, 3);
1810            assert_eq!(full_syncs, 1);
1811            assert_eq!(range_syncs, 0);
1812        });
1813    }
1814
1815    // Verifies resize does not mutate the blob before an outstanding start_sync completes.
1816    #[test_traced]
1817    fn test_write_resize_waits_for_outstanding_start_sync_before_resizing() {
1818        let executor = deterministic::Runner::default();
1819        executor.start(|_| async move {
1820            let inner = SyncTrackingBlob::new();
1821            let inner = Arc::new(inner);
1822            inner
1823                .write_at(0, b"abcdef", WriteOptions::default())
1824                .await
1825                .unwrap();
1826
1827            let (blob, pending) = DelayedSyncBlob::new(inner.clone());
1828            let writer = Write::new(blob, inner.size(), NZUsize!(8));
1829
1830            let (writer, handle) = writer.start_sync().await.unwrap();
1831            let deferred = next_pending_sync(&pending);
1832            let original_size = inner.size();
1833
1834            // Resize must not reach the blob while the earlier sync is pending.
1835            let mut resize = Box::pin(writer.resize(3));
1836            assert!(
1837                resize.as_mut().now_or_never().is_none(),
1838                "resize must wait for the outstanding start_sync handle"
1839            );
1840            deferred
1841                .blocked
1842                .await
1843                .expect("resize never waited on start_sync");
1844            assert_eq!(
1845                inner.size(),
1846                original_size,
1847                "resize must not mutate the blob before the pending sync finishes"
1848            );
1849
1850            // Releasing the sync lets the resize apply.
1851            deferred.release.send(Ok(())).unwrap();
1852            let writer = resize.await.unwrap();
1853            handle.await.unwrap();
1854            assert_eq!(writer.size(), 3);
1855            assert_eq!(inner.size(), 3);
1856        });
1857    }
1858
1859    #[test_traced]
1860    fn test_write_sync_uses_range_sync_for_buffer_only_write() {
1861        let executor = deterministic::Runner::default();
1862        executor.start(|_| async move {
1863            let blob = SyncTrackingBlob::new();
1864            let blob = Arc::new(blob);
1865            let mut writer = Write::new(blob.clone(), 0, NZUsize!(8));
1866
1867            // A fresh writer preserves one sync barrier for mutations that predate wrapping.
1868            writer = writer.sync().await.unwrap();
1869            let (durable, writes, full_syncs, range_syncs) = blob.snapshot();
1870            assert!(durable.is_empty());
1871            assert_eq!(writes, 0);
1872            assert_eq!(full_syncs, 1);
1873            assert_eq!(range_syncs, 0);
1874
1875            // The write remains entirely buffered, so sync can make just this range durable.
1876            writer = writer.write_at(0, b"abc").await.unwrap();
1877            writer = writer.sync().await.unwrap();
1878
1879            // No prior plain blob mutation required another full sync barrier.
1880            let (durable, writes, full_syncs, range_syncs) = blob.snapshot();
1881            assert_eq!(durable.as_slice(), b"abc");
1882            assert_eq!(writes, 1);
1883            assert_eq!(full_syncs, 1);
1884            assert_eq!(range_syncs, 1);
1885
1886            // The prior sync used a range-scoped write, so there is no pending full-sync barrier.
1887            writer.sync().await.unwrap();
1888            let (durable, writes, full_syncs, range_syncs) = blob.snapshot();
1889            assert_eq!(durable.as_slice(), b"abc");
1890            assert_eq!(writes, 1);
1891            assert_eq!(full_syncs, 1);
1892            assert_eq!(range_syncs, 1);
1893        });
1894    }
1895
1896    #[test_traced]
1897    fn test_write_sync_persists_pre_wrapped_blob_mutation() {
1898        let executor = deterministic::Runner::default();
1899        executor.start(|_| async move {
1900            let blob = SyncTrackingBlob::new();
1901            let blob = Arc::new(blob);
1902
1903            // Simulate a plain blob mutation before the writer wraps it.
1904            blob.write_at(0, b"abc", WriteOptions::default())
1905                .await
1906                .unwrap();
1907
1908            let mut writer = Write::new(blob.clone(), 3, NZUsize!(8));
1909            writer = writer.sync().await.unwrap();
1910
1911            // The first sync must use a full barrier to make the pre-wrapped write durable.
1912            let (durable, writes, full_syncs, range_syncs) = blob.snapshot();
1913            assert_eq!(durable.as_slice(), b"abc");
1914            assert_eq!(writes, 1);
1915            assert_eq!(full_syncs, 1);
1916            assert_eq!(range_syncs, 0);
1917
1918            // After the barrier is clear, a buffered tip-only write can use range sync again.
1919            writer = writer.write_at(3, b"d").await.unwrap();
1920            writer.sync().await.unwrap();
1921
1922            let (durable, writes, full_syncs, range_syncs) = blob.snapshot();
1923            assert_eq!(durable.as_slice(), b"abcd");
1924            assert_eq!(writes, 2);
1925            assert_eq!(full_syncs, 1);
1926            assert_eq!(range_syncs, 1);
1927        });
1928    }
1929
1930    #[test_traced]
1931    fn test_write_sync_persists_prior_direct_flushes_with_buffered_tip() {
1932        let executor = deterministic::Runner::default();
1933        executor.start(|_| async move {
1934            let blob = SyncTrackingBlob::new();
1935            let blob = Arc::new(blob);
1936            let mut writer = Write::new(blob.clone(), 0, NZUsize!(4));
1937
1938            // This exceeds the buffer and forces a plain write before the final buffered tip.
1939            writer = writer.write_at(0, b"abcdef").await.unwrap();
1940            writer = writer.write_at(6, b"g").await.unwrap();
1941            writer = writer.sync().await.unwrap();
1942
1943            // The final sync must cover both the prior plain write and the buffered tip.
1944            let (durable, writes, full_syncs, range_syncs) = blob.snapshot();
1945            assert_eq!(durable.as_slice(), b"abcdefg");
1946            assert_eq!(writes, 2);
1947            assert_eq!(full_syncs, 1);
1948            assert_eq!(range_syncs, 0);
1949
1950            // With no new writes, sync has no work left.
1951            writer = writer.sync().await.unwrap();
1952            let (durable, writes, full_syncs, range_syncs) = blob.snapshot();
1953            assert_eq!(durable.as_slice(), b"abcdefg");
1954            assert_eq!(writes, 2);
1955            assert_eq!(full_syncs, 1);
1956            assert_eq!(range_syncs, 0);
1957
1958            // After the full sync, the next buffer-only write can use range sync again.
1959            writer = writer.write_at(7, b"h").await.unwrap();
1960            writer.sync().await.unwrap();
1961
1962            let (durable, writes, full_syncs, range_syncs) = blob.snapshot();
1963            assert_eq!(durable.as_slice(), b"abcdefgh");
1964            assert_eq!(writes, 3);
1965            assert_eq!(full_syncs, 1);
1966            assert_eq!(range_syncs, 1);
1967        });
1968    }
1969
1970    #[test_traced]
1971    fn test_write_sync_uses_full_sync_after_resize() {
1972        let executor = deterministic::Runner::default();
1973        executor.start(|_| async move {
1974            let blob = SyncTrackingBlob::new();
1975            let blob = Arc::new(blob);
1976            let mut writer = Write::new(blob.clone(), 0, NZUsize!(8));
1977            writer = writer.sync().await.unwrap();
1978
1979            // Establish already-durable data with a range sync.
1980            writer = writer.write_at(0, b"abcdef").await.unwrap();
1981            writer = writer.sync().await.unwrap();
1982
1983            // Resize alone is an unsynced blob mutation.
1984            writer = writer.resize(4).await.unwrap();
1985            writer.sync().await.unwrap();
1986
1987            // The resized contents require a full sync barrier to become durable.
1988            let (durable, writes, full_syncs, range_syncs) = blob.snapshot();
1989            assert_eq!(durable.as_slice(), b"abcd");
1990            assert_eq!(writes, 1);
1991            assert_eq!(full_syncs, 2);
1992            assert_eq!(range_syncs, 1);
1993        });
1994    }
1995}