Skip to content

Commit

Permalink
feat(s3s-test): use arc (#196)
Browse files Browse the repository at this point in the history
  • Loading branch information
Nugine authored Oct 17, 2024
1 parent 0caf798 commit e5db7a0
Show file tree
Hide file tree
Showing 7 changed files with 181 additions and 49 deletions.
106 changes: 91 additions & 15 deletions crates/s3s-test/e2e/main.rs
Original file line number Diff line number Diff line change
@@ -1,40 +1,116 @@
#![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(())
}

struct E2E {
client: Client,
}

impl TestSuite for Basic {
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(())
}
}
Expand All @@ -49,6 +125,6 @@ fn main() {
}};
}

case!(Basic, BasicOps, list_buckets);
})
case!(E2E, Basic, test_list_buckets);
});
}
8 changes: 8 additions & 0 deletions crates/s3s-test/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()),
}
}
}
39 changes: 26 additions & 13 deletions crates/s3s-test/src/runner.rs
Original file line number Diff line number Diff line change
@@ -1,26 +1,39 @@
#![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,
Expand Down Expand Up @@ -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");
Expand All @@ -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);
}

Expand All @@ -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");
Expand All @@ -128,17 +141,17 @@ 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;
cases.push(report);
}

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);
}

Expand All @@ -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");

Expand Down
40 changes: 25 additions & 15 deletions crates/s3s-test/src/tcx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -15,26 +16,28 @@ 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>,
}

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>,
}

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>,
Expand All @@ -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 {
Expand All @@ -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(),
},
);
Expand All @@ -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(),
},
);
Expand Down Expand Up @@ -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(),
},
);
Expand Down
Loading

0 comments on commit e5db7a0

Please sign in to comment.