blob: 30b05d48ee62a5cf333b68ec7f4b2f6cf72d2d9e [file] [log] [blame]
// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use {
crate::{
range::Range,
repository::{file_system::CHUNK_SIZE, Error},
},
assert_matches::assert_matches,
};
#[async_trait::async_trait]
pub(crate) trait TestEnv {
/// Whether or not the repository backend supports Range requests.
fn supports_range(&self) -> bool;
fn write_metadata(&self, path: &str, bytes: &[u8]);
fn write_blob(&self, path: &str, bytes: &[u8]);
async fn read_metadata(&self, path: &str, range: Range) -> Result<Vec<u8>, Error>;
async fn read_blob(&self, path: &str, range: Range) -> Result<Vec<u8>, Error>;
}
// Helper to check that fetching a non-existing file returns a NotFound.
pub(crate) async fn check_fetch_missing(env: &impl TestEnv) {
assert_matches!(
env.read_metadata("meta-does-not-exist", Range::Full).await,
Err(Error::NotFound)
);
assert_matches!(env.read_blob("blob-does-not-exist", Range::Full).await, Err(Error::NotFound));
}
// Helper to check that fetching an empty file succeeds.
pub(crate) async fn check_fetch_empty(env: &impl TestEnv) {
env.write_metadata("empty-meta", b"");
env.write_blob("empty-blob", b"");
assert_eq!(env.read_metadata("empty-meta", Range::Full).await.unwrap(), b"");
assert_eq!(env.read_blob("empty-blob", Range::Full).await.unwrap(), b"");
}
// Helper to check that we can fetch a small file, which fits in a single chunk.
pub(crate) async fn check_fetch_small(env: &impl TestEnv) {
let meta_body = "hello meta";
let blob_body = "hello blob";
env.write_metadata("small-meta", meta_body.as_bytes());
env.write_blob("small-blob", blob_body.as_bytes());
let actual = env.read_metadata("small-meta", Range::Full).await.unwrap();
assert_eq!(String::from_utf8(actual).unwrap(), meta_body);
let actual = env.read_blob("small-blob", Range::Full).await.unwrap();
assert_eq!(String::from_utf8(actual).unwrap(), blob_body);
}
// Helper to check that we can fetch a range from a small file, which fits in a single chunk.
pub(crate) async fn check_fetch_range_small(env: &impl TestEnv) {
let meta_body = "hello meta";
let blob_body = "hello blob";
env.write_metadata("small-meta", meta_body.as_bytes());
env.write_blob("small-blob", blob_body.as_bytes());
let actual = env
.read_metadata("small-meta", Range::Inclusive { first_byte_pos: 1, last_byte_pos: 7 })
.await
.unwrap();
assert_eq!(
String::from_utf8(actual).unwrap(),
if env.supports_range() { &meta_body[1..=7] } else { &meta_body[..] }
);
let actual = env
.read_blob("small-blob", Range::Inclusive { first_byte_pos: 1, last_byte_pos: 7 })
.await
.unwrap();
assert_eq!(
String::from_utf8(actual).unwrap(),
if env.supports_range() { &blob_body[1..=7] } else { &blob_body[..] }
);
}
// Helper to check that we can fetch a variety of ranges that cross the chunk boundary size.
pub(crate) async fn check_fetch(env: &impl TestEnv) {
for size in [20, CHUNK_SIZE - 1, CHUNK_SIZE, CHUNK_SIZE + 1, CHUNK_SIZE * 2 + 1] {
let path = format!("{}", size);
let body = (0..std::u8::MAX).cycle().take(size).collect::<Vec<_>>();
env.write_metadata(&path, &body);
env.write_blob(&path, &body);
let actual = env.read_metadata(&path, Range::Full).await.unwrap();
assert_eq!(&actual, &body[..], "size: {size}");
let actual = env.read_blob(&path, Range::Full).await.unwrap();
assert_eq!(&actual, &body[..], "size: {size}");
}
}
pub(crate) async fn check_fetch_range(env: &impl TestEnv) {
for size in [20, CHUNK_SIZE - 1, CHUNK_SIZE, CHUNK_SIZE + 1, CHUNK_SIZE * 2 + 1] {
let path = format!("{}", size);
let body = (0..std::u8::MAX).cycle().take(size).collect::<Vec<_>>();
env.write_metadata(&path, &body);
env.write_blob(&path, &body);
for (range, expected) in [
(Range::From { first_byte_pos: 0 }, &body[..]),
(Range::From { first_byte_pos: 5 }, &body[5..]),
(Range::From { first_byte_pos: size as u64 - 1 }, &body[size - 1..]),
(Range::Inclusive { first_byte_pos: 0, last_byte_pos: 0 }, &body[0..=0]),
(Range::Inclusive { first_byte_pos: 0, last_byte_pos: size as u64 - 1 }, &body[..]),
(Range::Inclusive { first_byte_pos: 5, last_byte_pos: 5 }, &body[5..=5]),
(Range::Inclusive { first_byte_pos: 5, last_byte_pos: 15 }, &body[5..=15]),
(
Range::Inclusive { first_byte_pos: 5, last_byte_pos: size as u64 - 5 },
&body[5..=size - 5],
),
(
Range::Inclusive {
first_byte_pos: size as u64 - 1,
last_byte_pos: size as u64 - 1,
},
&body[size - 1..=size - 1],
),
(Range::Suffix { len: 0 }, &[]),
(Range::Suffix { len: 5 }, &body[size - 5..]),
(Range::Suffix { len: size as u64 }, &body[..]),
] {
if env.supports_range() {
let actual = env.read_metadata(&path, range.clone()).await.unwrap();
assert_eq!(&actual, &expected, "size: {size} range: {range:?}");
let actual = env.read_blob(&path, range.clone()).await.unwrap();
assert_eq!(&actual, &expected, "size: {size} range: {range:?}");
} else {
println!("{:?} {:?}", range, expected);
let actual = env.read_metadata(&path, range.clone()).await.unwrap();
assert_eq!(&actual, &body[..], "size: {size} range: {range:?}");
let actual = env.read_blob(&path, range.clone()).await.unwrap();
assert_eq!(&actual, &body[..], "size: {size} range: {range:?}");
}
}
}
}
// Helper to check that fetching an invalid range returns a NotSatisfiable error.
pub(crate) async fn check_fetch_range_not_satisfiable(env: &impl TestEnv) {
for size in [20, CHUNK_SIZE - 1, CHUNK_SIZE, CHUNK_SIZE + 1, CHUNK_SIZE * 2 + 1] {
let path = format!("{}", size);
let body = (0..std::u8::MAX).cycle().take(size).collect::<Vec<_>>();
env.write_metadata(&path, &body);
env.write_blob(&path, &body);
let size = size as u64;
for range in [
Range::From { first_byte_pos: size },
Range::From { first_byte_pos: size + 1 },
Range::From { first_byte_pos: size + 5 },
Range::Inclusive { first_byte_pos: 0, last_byte_pos: size },
Range::Inclusive { first_byte_pos: 5, last_byte_pos: size },
Range::Inclusive { first_byte_pos: size, last_byte_pos: size },
Range::Inclusive { first_byte_pos: size, last_byte_pos: size + 5 },
Range::Inclusive { first_byte_pos: 4, last_byte_pos: 3 },
Range::Inclusive { first_byte_pos: size + 3, last_byte_pos: size + 5 },
Range::Suffix { len: size + 1 },
Range::Suffix { len: size + 5 },
] {
assert_matches!(
env.read_metadata(&path, range.clone()).await,
Err(Error::RangeNotSatisfiable),
"size: {} range: {:?}",
size,
range
);
assert_matches!(
env.read_blob(&path, range.clone()).await,
Err(Error::RangeNotSatisfiable),
"size: {} range: {:?}",
size,
range
);
}
}
}