use gfeh_core::{ObjectStore, Result};
use gfeh_http::conformance::{self, Backend, Target};
use gfeh_store::{LocalStore, MemStore};
use gfeh_vfs::{AllowAll, Vfs};
use std::path::PathBuf;
use std::sync::Arc;
struct Mem;
#[async_trait::async_trait]
impl Backend for Mem {
fn name(&self) -> String {
"MemStore".into()
}
async fn fresh(&self) -> Result<Target> {
let store = MemStore::new();
Ok(Target {
root: store.root(),
partition: store.partition(),
store: Arc::new(store),
})
}
}
struct Local;
#[async_trait::async_trait]
impl Backend for Local {
fn name(&self) -> String {
"LocalStore".into()
}
async fn fresh(&self) -> Result<Target> {
let base = std::env::var("STATE_DIR").map_or_else(
|_| {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("../../.cache")
.join("state-tests")
},
PathBuf::from,
);
let dir = base
.join("gfeh-http-conformance")
.join(format!("pid-{}", std::process::id()))
.join(uuid::Uuid::new_v4().simple().to_string());
std::fs::create_dir_all(&dir)
.map_err(|e| gfeh_core::Error::Storage(format!("scratch: {e}")))?;
let store = LocalStore::open(&dir)?;
Ok(Target {
root: store.root(),
partition: store.partition(),
store: Arc::new(store),
})
}
}
struct Composed;
#[async_trait::async_trait]
impl Backend for Composed {
fn name(&self) -> String {
"Vfs over MemStore".into()
}
async fn fresh(&self) -> Result<Target> {
let vfs = Vfs::builder(Arc::new(MemStore::new()), Arc::new(AllowAll)).build();
Ok(Target {
root: vfs.root(),
partition: vfs.partition(),
store: Arc::new(vfs),
})
}
}
async fn conformant<B: Backend>(backend: &B) {
let report = conformance::run(backend).await;
assert!(report.is_conformant(), "{}", report.summary());
assert_eq!(
report.passed(),
conformance::cases().len(),
"{}",
report.summary()
);
}
#[tokio::test]
async fn the_in_memory_reference_is_conformant() {
conformant(&Mem).await;
}
#[tokio::test]
async fn a_filesystem_partition_is_conformant() {
conformant(&Local).await;
}
#[tokio::test]
async fn the_composed_namespace_is_conformant() {
conformant(&Composed).await;
}
#[tokio::test]
async fn a_failing_backend_is_reported_rather_than_aborting_the_run() {
struct Broken;
#[async_trait::async_trait]
impl Backend for Broken {
fn name(&self) -> String {
"a store that cannot be provisioned".into()
}
async fn fresh(&self) -> Result<Target> {
Err(gfeh_core::Error::Storage("no store today".into()))
}
}
let report = conformance::run(&Broken).await;
assert!(!report.is_conformant());
assert_eq!(report.passed(), 0);
assert_eq!(report.failures().count(), conformance::cases().len());
assert!(
report.summary().contains("no store today"),
"{}",
report.summary()
);
}
#[test]
fn every_case_has_a_distinct_name() {
let mut names: Vec<&str> = conformance::cases().iter().map(|c| c.name()).collect();
let total = names.len();
names.sort_unstable();
names.dedup();
assert_eq!(names.len(), total, "two cases share a name");
assert!(total >= 10, "the suite has shrunk to {total} cases");
}
struct Refuses;
fn refused<T>() -> Result<T> {
Err(gfeh_core::Error::Storage(
"this store refuses everything".into(),
))
}
#[async_trait::async_trait]
impl ObjectStore for Refuses {
fn partition(&self) -> gfeh_core::PartitionId {
gfeh_core::PartitionId::new()
}
async fn stat(&self, _: &gfeh_core::OpCtx, _: &gfeh_core::NodeRef) -> Result<gfeh_core::Meta> {
refused()
}
async fn lookup(
&self,
_: &gfeh_core::OpCtx,
_: &gfeh_core::NodeRef,
_: &str,
) -> Result<gfeh_core::Meta> {
refused()
}
async fn list(
&self,
_: &gfeh_core::OpCtx,
_: gfeh_core::ListRequest,
) -> Result<gfeh_core::ListPage> {
refused()
}
async fn create(
&self,
_: &gfeh_core::OpCtx,
_: &gfeh_core::NodeRef,
_: &str,
_: gfeh_core::NodeKind,
_: gfeh_core::Disposition,
) -> Result<Box<dyn gfeh_core::Handle>> {
refused()
}
async fn open(
&self,
_: &gfeh_core::OpCtx,
_: &gfeh_core::NodeRef,
_: gfeh_core::OpenMode,
) -> Result<Box<dyn gfeh_core::Handle>> {
refused()
}
async fn rename(
&self,
_: &gfeh_core::OpCtx,
_: &gfeh_core::NodeRef,
_: &gfeh_core::NodeRef,
_: &str,
_: bool,
) -> Result<()> {
refused()
}
async fn remove(
&self,
_: &gfeh_core::OpCtx,
_: &gfeh_core::NodeRef,
_: gfeh_core::RemoveOpts,
) -> Result<()> {
refused()
}
async fn set_meta(
&self,
_: &gfeh_core::OpCtx,
_: &gfeh_core::NodeRef,
_: gfeh_core::MetaPatch,
) -> Result<gfeh_core::Meta> {
refused()
}
async fn begin_multipart(
&self,
_: &gfeh_core::OpCtx,
_: &gfeh_core::NodeRef,
) -> Result<gfeh_core::UploadId> {
refused()
}
async fn put_part(
&self,
_: &gfeh_core::OpCtx,
_: &gfeh_core::UploadId,
_: u32,
_: gfeh_core::ByteStream,
) -> Result<gfeh_core::PartTag> {
refused()
}
async fn complete_multipart(
&self,
_: &gfeh_core::OpCtx,
_: gfeh_core::UploadId,
_: Vec<gfeh_core::PartTag>,
) -> Result<gfeh_core::Meta> {
refused()
}
async fn abort_multipart(&self, _: &gfeh_core::OpCtx, _: gfeh_core::UploadId) -> Result<()> {
refused()
}
async fn changes(
&self,
_: &gfeh_core::OpCtx,
_: gfeh_core::PartitionId,
_: gfeh_core::ChangeSeq,
_: usize,
) -> Result<gfeh_core::ChangePage> {
refused()
}
}
struct DoesNothing;
#[async_trait::async_trait]
impl Backend for DoesNothing {
fn name(&self) -> String {
"a store that refuses everything".into()
}
async fn fresh(&self) -> Result<Target> {
let store = MemStore::new();
Ok(Target {
root: store.root(),
partition: store.partition(),
store: Arc::new(Refuses),
})
}
}
#[tokio::test]
async fn only_the_cases_that_never_touch_the_store_survive_one_that_does_nothing() {
let report = conformance::run(&DoesNothing).await;
let survivors: Vec<&str> = report
.outcomes
.iter()
.filter(|o| o.failure.is_none())
.map(|o| o.case)
.collect();
assert_eq!(
survivors, PROTOCOL_ONLY,
"the set of cases that pass without a store has changed"
);
assert!(!report.is_conformant());
}
const PROTOCOL_ONLY: &[&str] = &["an_unknown_token_is_not_found"];