diff --git a/pageserver/src/tenant/storage_layer/inmemory_layer/vectored_dio_read.rs b/pageserver/src/tenant/storage_layer/inmemory_layer/vectored_dio_read.rs index 128328ecc3..3c79b9c3cb 100644 --- a/pageserver/src/tenant/storage_layer/inmemory_layer/vectored_dio_read.rs +++ b/pageserver/src/tenant/storage_layer/inmemory_layer/vectored_dio_read.rs @@ -279,6 +279,99 @@ mod tests { use super::*; use std::{cell::RefCell, collections::VecDeque}; + struct InMemoryFile { + content: Vec, + } + + impl InMemoryFile { + fn new_random(len: usize) -> Self { + Self { + content: rand::thread_rng() + .sample_iter(rand::distributions::Standard) + .take(len) + .collect(), + } + } + fn test_value_read(&self, pos: u32, len: usize) -> TestValueRead { + let expected_result = self.content[pos as usize..pos as usize + len].to_vec(); + TestValueRead { + pos, + expected_result, + } + } + } + + impl File for InMemoryFile { + async fn read_at_to_end<'a, 'b, B: IoBufMut + Send>( + &'b self, + start: u32, + mut dst: Slice, + _ctx: &'a RequestContext, + ) -> std::io::Result<(Slice, usize)> { + let len = std::cmp::min( + dst.bytes_total(), + self.content.len().saturating_sub(start as usize), + ); + let dst_slice: &mut [u8] = dst.as_mut_rust_slice_full_zeroed(); + dst_slice[..len].copy_from_slice(&self.content[start as usize..start as usize + len]); + rand::Rng::fill(&mut rand::thread_rng(), &mut dst_slice[len..]); // to discover bugs + Ok((dst, len)) + } + } + + #[derive(Debug)] + struct TestValueRead { + pos: u32, + expected_result: Vec, + } + + impl TestValueRead { + fn make_value_read(&self) -> ValueRead> { + ValueRead::new(self.pos, Vec::with_capacity(self.expected_result.len())) + } + } + + #[tokio::test] + async fn test_blackbox() { + let ctx = RequestContext::new(TaskKind::UnitTest, DownloadBehavior::Error); + let cs = DIO_CHUNK_SIZE; + let cs_u32 = u32::try_from(cs).unwrap(); + + let file = InMemoryFile::new_random(10 * cs); + + let test_value_reads = vec![ + file.test_value_read(0, 1), + // adjacent to value_read0 + file.test_value_read(1, 2), + // gap + // spans adjacent chunks + file.test_value_read(cs_u32 - 1, 2), + // gap + // tail of chunk 3, all of chunk 4, and 2 bytes of chunk 5 + file.test_value_read(3 * cs_u32 - 1, cs + 2), + // gap + file.test_value_read(5 * cs_u32, 1), + ]; + let test_value_reads_perms = test_value_reads.iter().permutations(test_value_reads.len()); + + // test all orderings of ValueReads, the order shouldn't matter for the results + for test_value_reads in test_value_reads_perms { + let value_reads: Vec> = test_value_reads + .iter() + .map(|tr| tr.make_value_read()) + .collect(); + execute(&file, value_reads.iter(), &ctx).await; + for (value_read, test_value_read) in + value_reads.into_iter().zip(test_value_reads.iter()) + { + let res = value_read + .into_result() + .expect("InMemoryFile is infallible"); + assert_eq!(res, test_value_read.expected_result); + } + } + } + struct ExpectedRead { expect_pos: u32, expect_len: usize, @@ -377,97 +470,4 @@ mod tests { .unwrap(); assert_eq!(err.to_string(), "foo"); } - - struct InMemoryFile { - content: Vec, - } - - impl InMemoryFile { - fn new_random(len: usize) -> Self { - Self { - content: rand::thread_rng() - .sample_iter(rand::distributions::Standard) - .take(len) - .collect(), - } - } - fn test_value_read(&self, pos: u32, len: usize) -> TestValueRead { - let expected_result = self.content[pos as usize..pos as usize + len].to_vec(); - TestValueRead { - pos, - expected_result, - } - } - } - - impl File for InMemoryFile { - async fn read_at_to_end<'a, 'b, B: IoBufMut + Send>( - &'b self, - start: u32, - mut dst: Slice, - _ctx: &'a RequestContext, - ) -> std::io::Result<(Slice, usize)> { - let len = std::cmp::min( - dst.bytes_total(), - self.content.len().saturating_sub(start as usize), - ); - let dst_slice: &mut [u8] = dst.as_mut_rust_slice_full_zeroed(); - dst_slice[..len].copy_from_slice(&self.content[start as usize..start as usize + len]); - rand::Rng::fill(&mut rand::thread_rng(), &mut dst_slice[len..]); // to discover bugs - Ok((dst, len)) - } - } - - #[derive(Debug)] - struct TestValueRead { - pos: u32, - expected_result: Vec, - } - - impl TestValueRead { - fn make_value_read(&self) -> ValueRead> { - ValueRead::new(self.pos, Vec::with_capacity(self.expected_result.len())) - } - } - - #[tokio::test] - async fn test_blackbox() { - let ctx = RequestContext::new(TaskKind::UnitTest, DownloadBehavior::Error); - let cs = DIO_CHUNK_SIZE; - let cs_u32 = u32::try_from(cs).unwrap(); - - let file = InMemoryFile::new_random(10 * cs); - - let test_value_reads = vec![ - file.test_value_read(0, 1), - // adjacent to value_read0 - file.test_value_read(1, 2), - // gap - // spans adjacent chunks - file.test_value_read(cs_u32 - 1, 2), - // gap - // tail of chunk 3, all of chunk 4, and 2 bytes of chunk 5 - file.test_value_read(3 * cs_u32 - 1, cs + 2), - // gap - file.test_value_read(5 * cs_u32, 1), - ]; - let test_value_reads_perms = test_value_reads.iter().permutations(test_value_reads.len()); - - // test all orderings of ValueReads, the order shouldn't matter for the results - for test_value_reads in test_value_reads_perms { - let value_reads: Vec> = test_value_reads - .iter() - .map(|tr| tr.make_value_read()) - .collect(); - execute(&file, value_reads.iter(), &ctx).await; - for (value_read, test_value_read) in - value_reads.into_iter().zip(test_value_reads.iter()) - { - let res = value_read - .into_result() - .expect("InMemoryFile is infallible"); - assert_eq!(res, test_value_read.expected_result); - } - } - } }