-
-
Notifications
You must be signed in to change notification settings - Fork 34
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(s3s-test): impl custom test suite
- Loading branch information
Showing
12 changed files
with
777 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,3 +3,4 @@ | |
.vscode | ||
|
||
/codegen/s3.json | ||
.env |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
[package] | ||
name = "s3s-test" | ||
version = "0.0.0" | ||
description = "s3s test suite" | ||
readme = "../../README.md" | ||
keywords = ["s3"] | ||
categories = ["web-programming", "web-programming::http-server"] | ||
edition.workspace = true | ||
repository.workspace = true | ||
license.workspace = true | ||
|
||
[[bin]] | ||
name = "s3s-e2e" | ||
path = "e2e/main.rs" | ||
|
||
[dependencies] | ||
serde = { version = "1.0.210", features = ["derive"] } | ||
tokio = { version = "1.40.0", features = ["full"] } | ||
tracing = "0.1.40" | ||
tracing-subscriber = { version = "0.3.18", features = ["env-filter", "time"] } | ||
aws-credential-types = "1.2.1" | ||
aws-sdk-s3 = "1.51.0" | ||
clap = { version = "4.5.17", features = ["derive"] } | ||
dotenvy = "0.15.7" | ||
serde_json = "1.0.128" | ||
indexmap = "2.6.0" | ||
colored = "2.1.0" | ||
|
||
[dependencies.aws-config] | ||
version = "1.5.6" | ||
default-features = false | ||
features = ["behavior-version-latest"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
use s3s_test::Result; | ||
use s3s_test::TestFixture; | ||
use s3s_test::TestSuite; | ||
|
||
use tracing::debug; | ||
|
||
struct Basic { | ||
client: aws_sdk_s3::Client, | ||
} | ||
|
||
impl TestSuite for Basic { | ||
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); | ||
Ok(Self { client }) | ||
} | ||
} | ||
|
||
struct BasicOps { | ||
client: aws_sdk_s3::Client, | ||
} | ||
|
||
impl TestFixture<Basic> for BasicOps { | ||
async fn setup(suite: &Basic) -> 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); | ||
Ok(()) | ||
} | ||
} | ||
|
||
fn main() { | ||
s3s_test::cli::main(|tcx| { | ||
macro_rules! case { | ||
($s:ident, $x:ident, $c:ident) => {{ | ||
let mut suite = tcx.suite::<$s>(stringify!($s)); | ||
let mut fixture = suite.fixture::<$x>(stringify!($x)); | ||
fixture.case(stringify!($c), $x::$c); | ||
}}; | ||
} | ||
|
||
case!(Basic, BasicOps, list_buckets); | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
use std::path::PathBuf; | ||
|
||
use crate::report::FnResult; | ||
use crate::tcx::TestContext; | ||
|
||
use clap::Parser; | ||
use colored::ColoredString; | ||
use colored::Colorize; | ||
|
||
type StdError = Box<dyn std::error::Error + Send + Sync + 'static>; | ||
|
||
fn setup_tracing() { | ||
use std::io::IsTerminal; | ||
use tracing_subscriber::EnvFilter; | ||
|
||
let env_filter = EnvFilter::from_default_env(); | ||
let enable_color = std::io::stdout().is_terminal(); | ||
|
||
tracing_subscriber::fmt() | ||
.pretty() | ||
.with_env_filter(env_filter) | ||
.with_ansi(enable_color) | ||
.init(); | ||
} | ||
|
||
#[derive(Debug, Parser)] | ||
struct Opt { | ||
#[clap(long)] | ||
json: Option<PathBuf>, | ||
} | ||
|
||
fn status(passed: bool) -> ColoredString { | ||
if passed { | ||
"PASSED".green() | ||
} else { | ||
"FAILED".red() | ||
} | ||
} | ||
|
||
#[tokio::main] | ||
async fn async_main(opt: &Opt, register: impl FnOnce(&mut TestContext)) -> Result<(), StdError> { | ||
let mut tcx = TestContext::new(); | ||
register(&mut tcx); | ||
|
||
let report = crate::runner::run(&mut tcx).await; | ||
|
||
if let Some(ref json_path) = opt.json { | ||
let report_json = serde_json::to_string_pretty(&report)?; | ||
std::fs::write(json_path, report_json)?; | ||
} | ||
|
||
let w = format!("{:.3}", report.duration_ms).len(); | ||
|
||
for suite in &report.suites { | ||
let suite_name = suite.name.as_str().magenta(); | ||
for fixture in &suite.fixtures { | ||
let fixture_name = fixture.name.as_str().blue(); | ||
for case in &fixture.cases { | ||
let case_name = case.name.as_str().cyan(); | ||
let status = status(case.passed); | ||
let duration = case.duration_ms; | ||
println!("{status} {duration:>w$.3}ms [{suite_name}/{fixture_name}/{case_name}]"); | ||
if !case.passed { | ||
if let Some(ref run) = case.run { | ||
let hint = match run.result { | ||
FnResult::Ok => "".normal(), | ||
FnResult::Err(_) => "ERROR".red(), | ||
FnResult::Panicked => "PANICKED".red().bold(), | ||
}; | ||
let msg = if let FnResult::Err(ref e) = run.result { | ||
e.as_str() | ||
} else { | ||
"" | ||
}; | ||
println!(" {hint} {msg}"); | ||
} | ||
} | ||
} | ||
let status = status(fixture.case_count.all_passed()); | ||
let duration = fixture.duration_ms; | ||
println!("{status} {duration:>w$.3}ms [{suite_name}/{fixture_name}]"); | ||
} | ||
let status = status(suite.fixture_count.all_passed()); | ||
let duration = suite.duration_ms; | ||
println!("{status} {duration:>w$.3}ms [{suite_name}]"); | ||
} | ||
let status = status(report.suite_count.all_passed()); | ||
let duration = report.duration_ms; | ||
println!("{status} {duration:>w$.3}ms"); | ||
|
||
Ok(()) | ||
} | ||
|
||
pub fn main(register: impl FnOnce(&mut TestContext)) { | ||
dotenvy::dotenv().ok(); | ||
setup_tracing(); | ||
let opt = Opt::parse(); | ||
if let Err(err) = async_main(&opt, register) { | ||
eprintln!("{err}"); | ||
std::process::exit(1); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
use std::fmt; | ||
|
||
pub type Result<T = (), E = Failed> = std::result::Result<T, E>; | ||
|
||
#[derive(Debug)] | ||
pub struct Failed { | ||
source: Option<Box<dyn std::error::Error + Send + Sync + 'static>>, | ||
} | ||
|
||
impl<E> From<E> for Failed | ||
where | ||
E: std::error::Error + Send + Sync + 'static, | ||
{ | ||
fn from(source: E) -> Self { | ||
Self { | ||
source: Some(Box::new(source)), | ||
} | ||
} | ||
} | ||
|
||
impl fmt::Display for Failed { | ||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
if let Some(source) = &self.source { | ||
write!(f, "Failed: {source}") | ||
} else { | ||
write!(f, "Failed") | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
#![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 | ||
)] | ||
|
||
mod error; | ||
mod runner; | ||
mod traits; | ||
|
||
pub mod cli; | ||
pub mod report; | ||
pub mod tcx; | ||
|
||
pub use self::error::{Failed, Result}; | ||
pub use self::traits::*; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
use serde::{Deserialize, Serialize}; | ||
|
||
#[derive(Serialize, Deserialize)] | ||
pub struct Report { | ||
pub suite_count: CountSummary, | ||
pub duration_ns: u64, | ||
pub duration_ms: f64, | ||
|
||
pub suites: Vec<SuiteReport>, | ||
} | ||
|
||
#[derive(Serialize, Deserialize)] | ||
pub struct SuiteReport { | ||
pub name: String, | ||
|
||
pub fixture_count: CountSummary, | ||
pub duration_ns: u64, | ||
pub duration_ms: f64, | ||
|
||
pub setup: Option<FnSummary>, | ||
pub teardown: Option<FnSummary>, | ||
pub fixtures: Vec<FixtureReport>, | ||
} | ||
|
||
#[derive(Serialize, Deserialize)] | ||
pub struct FixtureReport { | ||
pub name: String, | ||
|
||
pub case_count: CountSummary, | ||
pub duration_ns: u64, | ||
pub duration_ms: f64, | ||
|
||
pub setup: Option<FnSummary>, | ||
pub teardown: Option<FnSummary>, | ||
pub cases: Vec<CaseReport>, | ||
} | ||
|
||
#[derive(Serialize, Deserialize)] | ||
pub struct CaseReport { | ||
pub name: String, | ||
|
||
pub passed: bool, | ||
pub duration_ns: u64, | ||
pub duration_ms: f64, | ||
|
||
pub run: Option<FnSummary>, | ||
} | ||
|
||
#[derive(Debug, Serialize, Deserialize)] | ||
pub struct FnSummary { | ||
pub result: FnResult, | ||
pub duration_ns: u64, | ||
pub duration_ms: f64, | ||
} | ||
|
||
#[derive(Debug, Serialize, Deserialize)] | ||
pub struct CountSummary { | ||
pub total: u64, | ||
pub passed: u64, | ||
pub failed: u64, | ||
} | ||
|
||
impl CountSummary { | ||
#[must_use] | ||
pub fn all_passed(&self) -> bool { | ||
self.passed == self.total | ||
} | ||
} | ||
|
||
#[derive(Debug, Serialize, Deserialize)] | ||
pub enum FnResult { | ||
Ok, | ||
Err(String), | ||
Panicked, | ||
} | ||
|
||
impl FnResult { | ||
#[must_use] | ||
pub fn is_ok(&self) -> bool { | ||
matches!(self, FnResult::Ok) | ||
} | ||
} |
Oops, something went wrong.