Loading crates/s3s-test/e2e/main.rs +91 −15 Original line number Diff line number Diff line #![forbid(unsafe_code)] #![deny( clippy::all, // clippy::cargo, // clippy::pedantic, // clippy::self_named_module_files, // )] #![warn( clippy::dbg_macro, // )] #![allow( clippy::module_name_repetitions, // clippy::missing_errors_doc, // TODO clippy::missing_panics_doc, // TODO clippy::multiple_crate_versions, // TODO: check later )] use s3s_test::Result; use s3s_test::TestFixture; use s3s_test::TestSuite; use tracing::debug; use std::fmt; use std::sync::Arc; struct Basic { client: aws_sdk_s3::Client, use aws_sdk_s3::error::ProvideErrorMetadata; use aws_sdk_s3::error::SdkError; use aws_sdk_s3::Client; use tracing::error; fn check<T, E>(result: Result<T, SdkError<E>>, allowed_codes: &[&str]) -> Result<Option<T>, SdkError<E>> where E: fmt::Debug + ProvideErrorMetadata, { if let Err(SdkError::ServiceError(ref err)) = result { if let Some(code) = err.err().code() { if allowed_codes.contains(&code) { return Ok(None); } } } if let Err(ref err) = result { error!(?err); } match result { Ok(val) => Ok(Some(val)), Err(err) => Err(err), } } #[tracing::instrument(skip(c))] async fn delete_bucket(c: &Client, bucket: &str) -> Result<()> { let result = c.delete_bucket().bucket(bucket).send().await; check(result, &["NoSuchBucket"])?; Ok(()) } #[tracing::instrument(skip(c))] async fn create_bucket(c: &Client, bucket: &str) -> Result<()> { c.create_bucket().bucket(bucket).send().await?; Ok(()) } impl TestSuite for Basic { struct E2E { client: Client, } impl TestSuite for E2E { async fn setup() -> Result<Self> { let sdk_conf = aws_config::from_env().load().await; let s3_conf = aws_sdk_s3::config::Builder::from(&sdk_conf) .force_path_style(true) // FIXME: remove force_path_style .build(); let client = aws_sdk_s3::Client::from_conf(s3_conf); let client = Client::from_conf(s3_conf); Ok(Self { client }) } } struct BasicOps { client: aws_sdk_s3::Client, struct Basic { client: Client, } impl TestFixture<Basic> for BasicOps { async fn setup(suite: &Basic) -> Result<Self> { impl TestFixture<E2E> for Basic { async fn setup(suite: Arc<E2E>) -> Result<Self> { Ok(Self { client: suite.client.clone(), }) } } impl BasicOps { async fn list_buckets(&self) -> Result<()> { let resp = self.client.list_buckets().send().await?; debug!(?resp); impl Basic { async fn test_list_buckets(self: Arc<Self>) -> Result<()> { let c = &self.client; let buckets = ["test-list-buckets-1", "test-list-buckets-2"]; for &bucket in &buckets { delete_bucket(c, bucket).await?; } for &bucket in &buckets { create_bucket(c, bucket).await?; } let resp = c.list_buckets().send().await?; let bucket_list: Vec<_> = resp.buckets.as_deref().unwrap().iter().filter_map(|b| b.name()).collect(); for &bucket in &buckets { assert!(bucket_list.contains(&bucket)); } for &bucket in &buckets { delete_bucket(c, bucket).await?; } Ok(()) } } Loading @@ -49,6 +125,6 @@ fn main() { }}; } case!(Basic, BasicOps, list_buckets); }) case!(E2E, Basic, test_list_buckets); }); } crates/s3s-test/src/error.rs +8 −0 Original line number Diff line number Diff line Loading @@ -27,3 +27,11 @@ impl fmt::Display for Failed { } } } impl Failed { pub fn from_string(s: impl Into<String>) -> Self { Self { source: Some(s.into().into()), } } } crates/s3s-test/src/runner.rs +26 −13 Original line number Diff line number Diff line #![allow(clippy::wildcard_imports)] #![allow(clippy::cast_possible_truncation, clippy::cast_precision_loss)] use std::time::Instant; use crate::report::*; use crate::tcx::*; use std::sync::Arc; use std::time::Instant; use tokio::spawn; use tracing::info; use tracing::instrument; use tracing::Instrument; macro_rules! run_fn { ($call:expr) => {{ let t0 = std::time::Instant::now(); let result = $call.await; let result = spawn($call.in_current_span()).await; let duration_ns = t0.elapsed().as_nanos() as u64; let duration_ms = duration_ns as f64 / 1e6; let summary = match result { Ok(_) => FnSummary { Ok(Ok(_)) => FnSummary { result: FnResult::Ok, duration_ns, duration_ms, }, Ok(Err(ref e)) => FnSummary { result: FnResult::Err(e.to_string()), duration_ns, duration_ms, }, Err(ref e) if e.is_panic() => FnSummary { result: FnResult::Panicked, duration_ns, duration_ms, }, Err(ref e) => FnSummary { result: FnResult::Err(e.to_string()), duration_ns, Loading Loading @@ -74,7 +87,7 @@ pub async fn run(tcx: &mut TestContext) -> Report { } } #[instrument(skip(suite), fields(suite_name = suite.name))] #[instrument(skip(suite), fields(name = suite.name))] async fn run_suite(suite: &SuiteInfo) -> SuiteReport { let total_fixtures = suite.fixtures.len(); info!(total_fixtures, "Test suite start"); Loading @@ -88,14 +101,14 @@ async fn run_suite(suite: &SuiteInfo) -> SuiteReport { 'run: { let (result, summary) = run_fn!((suite.setup)()); setup_summary = Some(summary); let Ok(mut suite_data) = result else { break 'run }; let Ok(Ok(suite_data)) = result else { break 'run }; for fixture in suite.fixtures.values() { let report = run_fixture(fixture, &suite_data).await; fixtures.push(report); } let (_, summary) = run_fn!((suite.teardown)(&mut suite_data)); let (_, summary) = run_fn!((suite.teardown)(suite_data)); teardown_summary = Some(summary); } Loading @@ -115,7 +128,7 @@ async fn run_suite(suite: &SuiteInfo) -> SuiteReport { } } #[instrument(skip(fixture, suite_data), fields(fixture_name = fixture.name))] #[instrument(skip(fixture, suite_data), fields(name = fixture.name))] async fn run_fixture(fixture: &FixtureInfo, suite_data: &ArcAny) -> FixtureReport { let total_cases = fixture.cases.len(); info!(total_cases, "Test fixture start"); Loading @@ -128,9 +141,9 @@ async fn run_fixture(fixture: &FixtureInfo, suite_data: &ArcAny) -> FixtureRepor 'run: { info!("Test fixture setup"); let (result, summary) = run_fn!((fixture.setup)(suite_data)); let (result, summary) = run_fn!((fixture.setup)(Arc::clone(suite_data))); setup_summary = Some(summary); let Ok(mut fixture_data) = result else { break 'run }; let Ok(Ok(fixture_data)) = result else { break 'run }; for case in fixture.cases.values() { let report = run_case(case, &fixture_data).await; Loading @@ -138,7 +151,7 @@ async fn run_fixture(fixture: &FixtureInfo, suite_data: &ArcAny) -> FixtureRepor } info!("Test fixture teardown"); let (_, summary) = run_fn!((fixture.teardown)(&mut fixture_data)); let (_, summary) = run_fn!((fixture.teardown)(fixture_data)); teardown_summary = Some(summary); } Loading @@ -158,12 +171,12 @@ async fn run_fixture(fixture: &FixtureInfo, suite_data: &ArcAny) -> FixtureRepor } } #[instrument(skip(case, fixture_data), fields(case_name = case.name))] #[instrument(skip(case, fixture_data), fields(name = case.name))] async fn run_case(case: &CaseInfo, fixture_data: &ArcAny) -> CaseReport { info!("Test case start"); let t0 = Instant::now(); let (_, summary) = run_fn!((case.run)(fixture_data)); let (_, summary) = run_fn!((case.run)(Arc::clone(fixture_data))); info!(?summary, "Test case end"); Loading crates/s3s-test/src/tcx.rs +25 −15 Original line number Diff line number Diff line Loading @@ -4,6 +4,7 @@ use crate::traits::TestCase; use crate::traits::TestFixture; use crate::traits::TestSuite; use std::any::type_name; use std::future::Future; use std::marker::PhantomData; use std::pin::Pin; Loading @@ -15,12 +16,12 @@ pub(crate) type ArcAny = Arc<dyn std::any::Any + Send + Sync + 'static>; type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>; type SuiteSetupFn = Box<dyn Fn() -> BoxFuture<'static, Result<ArcAny, Failed>>>; type SuiteTeardownFn = Box<dyn for<'a> Fn(&'a mut ArcAny) -> BoxFuture<'a, Result>>; type SuiteTeardownFn = Box<dyn Fn(ArcAny) -> BoxFuture<'static, Result>>; type FixtureSetupFn = Box<dyn for<'a> Fn(&'a ArcAny) -> BoxFuture<'a, Result<ArcAny, Failed>>>; type FixtureTeardownFn = Box<dyn for<'a> Fn(&'a mut ArcAny) -> BoxFuture<'a, Result>>; type FixtureSetupFn = Box<dyn Fn(ArcAny) -> BoxFuture<'static, Result<ArcAny, Failed>>>; type FixtureTeardownFn = Box<dyn Fn(ArcAny) -> BoxFuture<'static, Result>>; type CaseRunFn = Box<dyn for<'a> Fn(&'a ArcAny) -> BoxFuture<'a, Result>>; type CaseRunFn = Box<dyn Fn(ArcAny) -> BoxFuture<'static, Result>>; pub struct TestContext { pub(crate) suites: IndexMap<String, SuiteInfo>, Loading @@ -28,6 +29,7 @@ pub struct TestContext { pub(crate) struct SuiteInfo { pub(crate) name: String, // pub(crate) type_id: TypeId, pub(crate) setup: SuiteSetupFn, pub(crate) teardown: SuiteTeardownFn, pub(crate) fixtures: IndexMap<String, FixtureInfo>, Loading @@ -35,6 +37,7 @@ pub(crate) struct SuiteInfo { pub(crate) struct FixtureInfo { pub(crate) name: String, // pub(crate) type_id: TypeId, pub(crate) setup: FixtureSetupFn, pub(crate) teardown: FixtureTeardownFn, pub(crate) cases: IndexMap<String, CaseInfo>, Loading @@ -52,12 +55,19 @@ pub enum CaseTag { ShouldPanic, } fn downcast_ref<T: 'static>(any: &ArcAny) -> &T { (*any).downcast_ref().unwrap() fn wrap<T: Send + Sync + 'static>(x: T) -> ArcAny { Arc::new(x) } fn downcast_mut<T: 'static>(any: &mut ArcAny) -> &mut T { Arc::get_mut(any).unwrap().downcast_mut().unwrap() fn downcast<T: Send + Sync + 'static>(any: ArcAny) -> Arc<T> { Arc::downcast(any).unwrap() } fn unwrap<T: Send + Sync + 'static>(any: ArcAny) -> Result<T> { match Arc::try_unwrap(downcast::<T>(any)) { Ok(x) => Ok(x), Err(_) => Err(Failed::from_string(format!("Arc<{}> is leaked", type_name::<T>()))), } } impl TestContext { Loading @@ -72,8 +82,9 @@ impl TestContext { name.clone(), SuiteInfo { name: name.clone(), setup: Box::new(|| Box::pin(async { S::setup().await.map(|x| Arc::new(x) as ArcAny) })), teardown: Box::new(|any| Box::pin(S::teardown(downcast_mut(any)))), // type_id: TypeId::of::<S>(), setup: Box::new(|| Box::pin(async { S::setup().await.map(wrap) })), teardown: Box::new(|any| Box::pin(async move { S::teardown(unwrap(any)?).await })), fixtures: IndexMap::new(), }, ); Loading @@ -98,10 +109,9 @@ impl<S: TestSuite> SuiteBuilder<'_, S> { name.clone(), FixtureInfo { name: name.clone(), setup: Box::new(|any| { Box::pin(async move { X::setup(downcast_ref(any)).await.map(|x| Arc::new(x) as ArcAny) }) }), teardown: Box::new(|any| Box::pin(X::teardown(downcast_mut(any)))), // type_id: TypeId::of::<X>(), setup: Box::new(|any| Box::pin(async move { X::setup(downcast(any)).await.map(wrap) })), teardown: Box::new(|any| Box::pin(async move { X::teardown(unwrap(any)?).await })), cases: IndexMap::new(), }, ); Loading Loading @@ -129,7 +139,7 @@ where name.clone(), CaseInfo { name: name.clone(), run: Box::new(move |any| Box::pin(case.run(downcast_ref(any)))), run: Box::new(move |any| Box::pin(case.run(downcast(any)))), tags: Vec::new(), }, ); Loading crates/s3s-test/src/traits.rs +7 −6 Original line number Diff line number Diff line use crate::error::Result; use std::future::Future; use std::sync::Arc; pub trait TestSuite: Sized + Send + Sync + 'static { fn setup() -> impl Future<Output = Result<Self>> + Send + 'static; fn teardown(&mut self) -> impl Future<Output = Result> + Send + '_ { fn teardown(self) -> impl Future<Output = Result> + Send + 'static { async { Ok(()) } } } pub trait TestFixture<S: TestSuite>: Sized + Send + Sync + 'static { fn setup(suite: &S) -> impl Future<Output = Result<Self>> + Send + '_; fn setup(suite: Arc<S>) -> impl Future<Output = Result<Self>> + Send + 'static; fn teardown(&mut self) -> impl Future<Output = Result> + Send + '_ { fn teardown(self) -> impl Future<Output = Result> + Send + 'static { async { Ok(()) } } } Loading @@ -24,7 +25,7 @@ where X: TestFixture<S>, S: TestSuite, { fn run<'a>(&self, fixture: &'a X) -> impl Future<Output = Result> + Send + 'a; fn run(&self, fixture: Arc<X>) -> impl Future<Output = Result> + Send + 'static; } trait AsyncFn<'a, A> { Loading @@ -50,12 +51,12 @@ where impl<C, X, S> TestCase<X, S> for C where C: for<'a> AsyncFn<'a, (&'a X,), Output = Result>, C: for<'a> AsyncFn<'a, (Arc<X>,), Output = Result>, C: Send + Sync + 'static, X: TestFixture<S>, S: TestSuite, { fn run<'a>(&self, fixture: &'a X) -> impl Future<Output = Result> + Send + 'a { fn run(&self, fixture: Arc<X>) -> impl Future<Output = Result> + Send + 'static { AsyncFn::call(self, (fixture,)) } } Loading
crates/s3s-test/e2e/main.rs +91 −15 Original line number Diff line number Diff line #![forbid(unsafe_code)] #![deny( clippy::all, // clippy::cargo, // clippy::pedantic, // clippy::self_named_module_files, // )] #![warn( clippy::dbg_macro, // )] #![allow( clippy::module_name_repetitions, // clippy::missing_errors_doc, // TODO clippy::missing_panics_doc, // TODO clippy::multiple_crate_versions, // TODO: check later )] use s3s_test::Result; use s3s_test::TestFixture; use s3s_test::TestSuite; use tracing::debug; use std::fmt; use std::sync::Arc; struct Basic { client: aws_sdk_s3::Client, use aws_sdk_s3::error::ProvideErrorMetadata; use aws_sdk_s3::error::SdkError; use aws_sdk_s3::Client; use tracing::error; fn check<T, E>(result: Result<T, SdkError<E>>, allowed_codes: &[&str]) -> Result<Option<T>, SdkError<E>> where E: fmt::Debug + ProvideErrorMetadata, { if let Err(SdkError::ServiceError(ref err)) = result { if let Some(code) = err.err().code() { if allowed_codes.contains(&code) { return Ok(None); } } } if let Err(ref err) = result { error!(?err); } match result { Ok(val) => Ok(Some(val)), Err(err) => Err(err), } } #[tracing::instrument(skip(c))] async fn delete_bucket(c: &Client, bucket: &str) -> Result<()> { let result = c.delete_bucket().bucket(bucket).send().await; check(result, &["NoSuchBucket"])?; Ok(()) } #[tracing::instrument(skip(c))] async fn create_bucket(c: &Client, bucket: &str) -> Result<()> { c.create_bucket().bucket(bucket).send().await?; Ok(()) } impl TestSuite for Basic { struct E2E { client: Client, } impl TestSuite for E2E { async fn setup() -> Result<Self> { let sdk_conf = aws_config::from_env().load().await; let s3_conf = aws_sdk_s3::config::Builder::from(&sdk_conf) .force_path_style(true) // FIXME: remove force_path_style .build(); let client = aws_sdk_s3::Client::from_conf(s3_conf); let client = Client::from_conf(s3_conf); Ok(Self { client }) } } struct BasicOps { client: aws_sdk_s3::Client, struct Basic { client: Client, } impl TestFixture<Basic> for BasicOps { async fn setup(suite: &Basic) -> Result<Self> { impl TestFixture<E2E> for Basic { async fn setup(suite: Arc<E2E>) -> Result<Self> { Ok(Self { client: suite.client.clone(), }) } } impl BasicOps { async fn list_buckets(&self) -> Result<()> { let resp = self.client.list_buckets().send().await?; debug!(?resp); impl Basic { async fn test_list_buckets(self: Arc<Self>) -> Result<()> { let c = &self.client; let buckets = ["test-list-buckets-1", "test-list-buckets-2"]; for &bucket in &buckets { delete_bucket(c, bucket).await?; } for &bucket in &buckets { create_bucket(c, bucket).await?; } let resp = c.list_buckets().send().await?; let bucket_list: Vec<_> = resp.buckets.as_deref().unwrap().iter().filter_map(|b| b.name()).collect(); for &bucket in &buckets { assert!(bucket_list.contains(&bucket)); } for &bucket in &buckets { delete_bucket(c, bucket).await?; } Ok(()) } } Loading @@ -49,6 +125,6 @@ fn main() { }}; } case!(Basic, BasicOps, list_buckets); }) case!(E2E, Basic, test_list_buckets); }); }
crates/s3s-test/src/error.rs +8 −0 Original line number Diff line number Diff line Loading @@ -27,3 +27,11 @@ impl fmt::Display for Failed { } } } impl Failed { pub fn from_string(s: impl Into<String>) -> Self { Self { source: Some(s.into().into()), } } }
crates/s3s-test/src/runner.rs +26 −13 Original line number Diff line number Diff line #![allow(clippy::wildcard_imports)] #![allow(clippy::cast_possible_truncation, clippy::cast_precision_loss)] use std::time::Instant; use crate::report::*; use crate::tcx::*; use std::sync::Arc; use std::time::Instant; use tokio::spawn; use tracing::info; use tracing::instrument; use tracing::Instrument; macro_rules! run_fn { ($call:expr) => {{ let t0 = std::time::Instant::now(); let result = $call.await; let result = spawn($call.in_current_span()).await; let duration_ns = t0.elapsed().as_nanos() as u64; let duration_ms = duration_ns as f64 / 1e6; let summary = match result { Ok(_) => FnSummary { Ok(Ok(_)) => FnSummary { result: FnResult::Ok, duration_ns, duration_ms, }, Ok(Err(ref e)) => FnSummary { result: FnResult::Err(e.to_string()), duration_ns, duration_ms, }, Err(ref e) if e.is_panic() => FnSummary { result: FnResult::Panicked, duration_ns, duration_ms, }, Err(ref e) => FnSummary { result: FnResult::Err(e.to_string()), duration_ns, Loading Loading @@ -74,7 +87,7 @@ pub async fn run(tcx: &mut TestContext) -> Report { } } #[instrument(skip(suite), fields(suite_name = suite.name))] #[instrument(skip(suite), fields(name = suite.name))] async fn run_suite(suite: &SuiteInfo) -> SuiteReport { let total_fixtures = suite.fixtures.len(); info!(total_fixtures, "Test suite start"); Loading @@ -88,14 +101,14 @@ async fn run_suite(suite: &SuiteInfo) -> SuiteReport { 'run: { let (result, summary) = run_fn!((suite.setup)()); setup_summary = Some(summary); let Ok(mut suite_data) = result else { break 'run }; let Ok(Ok(suite_data)) = result else { break 'run }; for fixture in suite.fixtures.values() { let report = run_fixture(fixture, &suite_data).await; fixtures.push(report); } let (_, summary) = run_fn!((suite.teardown)(&mut suite_data)); let (_, summary) = run_fn!((suite.teardown)(suite_data)); teardown_summary = Some(summary); } Loading @@ -115,7 +128,7 @@ async fn run_suite(suite: &SuiteInfo) -> SuiteReport { } } #[instrument(skip(fixture, suite_data), fields(fixture_name = fixture.name))] #[instrument(skip(fixture, suite_data), fields(name = fixture.name))] async fn run_fixture(fixture: &FixtureInfo, suite_data: &ArcAny) -> FixtureReport { let total_cases = fixture.cases.len(); info!(total_cases, "Test fixture start"); Loading @@ -128,9 +141,9 @@ async fn run_fixture(fixture: &FixtureInfo, suite_data: &ArcAny) -> FixtureRepor 'run: { info!("Test fixture setup"); let (result, summary) = run_fn!((fixture.setup)(suite_data)); let (result, summary) = run_fn!((fixture.setup)(Arc::clone(suite_data))); setup_summary = Some(summary); let Ok(mut fixture_data) = result else { break 'run }; let Ok(Ok(fixture_data)) = result else { break 'run }; for case in fixture.cases.values() { let report = run_case(case, &fixture_data).await; Loading @@ -138,7 +151,7 @@ async fn run_fixture(fixture: &FixtureInfo, suite_data: &ArcAny) -> FixtureRepor } info!("Test fixture teardown"); let (_, summary) = run_fn!((fixture.teardown)(&mut fixture_data)); let (_, summary) = run_fn!((fixture.teardown)(fixture_data)); teardown_summary = Some(summary); } Loading @@ -158,12 +171,12 @@ async fn run_fixture(fixture: &FixtureInfo, suite_data: &ArcAny) -> FixtureRepor } } #[instrument(skip(case, fixture_data), fields(case_name = case.name))] #[instrument(skip(case, fixture_data), fields(name = case.name))] async fn run_case(case: &CaseInfo, fixture_data: &ArcAny) -> CaseReport { info!("Test case start"); let t0 = Instant::now(); let (_, summary) = run_fn!((case.run)(fixture_data)); let (_, summary) = run_fn!((case.run)(Arc::clone(fixture_data))); info!(?summary, "Test case end"); Loading
crates/s3s-test/src/tcx.rs +25 −15 Original line number Diff line number Diff line Loading @@ -4,6 +4,7 @@ use crate::traits::TestCase; use crate::traits::TestFixture; use crate::traits::TestSuite; use std::any::type_name; use std::future::Future; use std::marker::PhantomData; use std::pin::Pin; Loading @@ -15,12 +16,12 @@ pub(crate) type ArcAny = Arc<dyn std::any::Any + Send + Sync + 'static>; type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>; type SuiteSetupFn = Box<dyn Fn() -> BoxFuture<'static, Result<ArcAny, Failed>>>; type SuiteTeardownFn = Box<dyn for<'a> Fn(&'a mut ArcAny) -> BoxFuture<'a, Result>>; type SuiteTeardownFn = Box<dyn Fn(ArcAny) -> BoxFuture<'static, Result>>; type FixtureSetupFn = Box<dyn for<'a> Fn(&'a ArcAny) -> BoxFuture<'a, Result<ArcAny, Failed>>>; type FixtureTeardownFn = Box<dyn for<'a> Fn(&'a mut ArcAny) -> BoxFuture<'a, Result>>; type FixtureSetupFn = Box<dyn Fn(ArcAny) -> BoxFuture<'static, Result<ArcAny, Failed>>>; type FixtureTeardownFn = Box<dyn Fn(ArcAny) -> BoxFuture<'static, Result>>; type CaseRunFn = Box<dyn for<'a> Fn(&'a ArcAny) -> BoxFuture<'a, Result>>; type CaseRunFn = Box<dyn Fn(ArcAny) -> BoxFuture<'static, Result>>; pub struct TestContext { pub(crate) suites: IndexMap<String, SuiteInfo>, Loading @@ -28,6 +29,7 @@ pub struct TestContext { pub(crate) struct SuiteInfo { pub(crate) name: String, // pub(crate) type_id: TypeId, pub(crate) setup: SuiteSetupFn, pub(crate) teardown: SuiteTeardownFn, pub(crate) fixtures: IndexMap<String, FixtureInfo>, Loading @@ -35,6 +37,7 @@ pub(crate) struct SuiteInfo { pub(crate) struct FixtureInfo { pub(crate) name: String, // pub(crate) type_id: TypeId, pub(crate) setup: FixtureSetupFn, pub(crate) teardown: FixtureTeardownFn, pub(crate) cases: IndexMap<String, CaseInfo>, Loading @@ -52,12 +55,19 @@ pub enum CaseTag { ShouldPanic, } fn downcast_ref<T: 'static>(any: &ArcAny) -> &T { (*any).downcast_ref().unwrap() fn wrap<T: Send + Sync + 'static>(x: T) -> ArcAny { Arc::new(x) } fn downcast_mut<T: 'static>(any: &mut ArcAny) -> &mut T { Arc::get_mut(any).unwrap().downcast_mut().unwrap() fn downcast<T: Send + Sync + 'static>(any: ArcAny) -> Arc<T> { Arc::downcast(any).unwrap() } fn unwrap<T: Send + Sync + 'static>(any: ArcAny) -> Result<T> { match Arc::try_unwrap(downcast::<T>(any)) { Ok(x) => Ok(x), Err(_) => Err(Failed::from_string(format!("Arc<{}> is leaked", type_name::<T>()))), } } impl TestContext { Loading @@ -72,8 +82,9 @@ impl TestContext { name.clone(), SuiteInfo { name: name.clone(), setup: Box::new(|| Box::pin(async { S::setup().await.map(|x| Arc::new(x) as ArcAny) })), teardown: Box::new(|any| Box::pin(S::teardown(downcast_mut(any)))), // type_id: TypeId::of::<S>(), setup: Box::new(|| Box::pin(async { S::setup().await.map(wrap) })), teardown: Box::new(|any| Box::pin(async move { S::teardown(unwrap(any)?).await })), fixtures: IndexMap::new(), }, ); Loading @@ -98,10 +109,9 @@ impl<S: TestSuite> SuiteBuilder<'_, S> { name.clone(), FixtureInfo { name: name.clone(), setup: Box::new(|any| { Box::pin(async move { X::setup(downcast_ref(any)).await.map(|x| Arc::new(x) as ArcAny) }) }), teardown: Box::new(|any| Box::pin(X::teardown(downcast_mut(any)))), // type_id: TypeId::of::<X>(), setup: Box::new(|any| Box::pin(async move { X::setup(downcast(any)).await.map(wrap) })), teardown: Box::new(|any| Box::pin(async move { X::teardown(unwrap(any)?).await })), cases: IndexMap::new(), }, ); Loading Loading @@ -129,7 +139,7 @@ where name.clone(), CaseInfo { name: name.clone(), run: Box::new(move |any| Box::pin(case.run(downcast_ref(any)))), run: Box::new(move |any| Box::pin(case.run(downcast(any)))), tags: Vec::new(), }, ); Loading
crates/s3s-test/src/traits.rs +7 −6 Original line number Diff line number Diff line use crate::error::Result; use std::future::Future; use std::sync::Arc; pub trait TestSuite: Sized + Send + Sync + 'static { fn setup() -> impl Future<Output = Result<Self>> + Send + 'static; fn teardown(&mut self) -> impl Future<Output = Result> + Send + '_ { fn teardown(self) -> impl Future<Output = Result> + Send + 'static { async { Ok(()) } } } pub trait TestFixture<S: TestSuite>: Sized + Send + Sync + 'static { fn setup(suite: &S) -> impl Future<Output = Result<Self>> + Send + '_; fn setup(suite: Arc<S>) -> impl Future<Output = Result<Self>> + Send + 'static; fn teardown(&mut self) -> impl Future<Output = Result> + Send + '_ { fn teardown(self) -> impl Future<Output = Result> + Send + 'static { async { Ok(()) } } } Loading @@ -24,7 +25,7 @@ where X: TestFixture<S>, S: TestSuite, { fn run<'a>(&self, fixture: &'a X) -> impl Future<Output = Result> + Send + 'a; fn run(&self, fixture: Arc<X>) -> impl Future<Output = Result> + Send + 'static; } trait AsyncFn<'a, A> { Loading @@ -50,12 +51,12 @@ where impl<C, X, S> TestCase<X, S> for C where C: for<'a> AsyncFn<'a, (&'a X,), Output = Result>, C: for<'a> AsyncFn<'a, (Arc<X>,), Output = Result>, C: Send + Sync + 'static, X: TestFixture<S>, S: TestSuite, { fn run<'a>(&self, fixture: &'a X) -> impl Future<Output = Result> + Send + 'a { fn run(&self, fixture: Arc<X>) -> impl Future<Output = Result> + Send + 'static { AsyncFn::call(self, (fixture,)) } }