Unverified Commit a68651d6 authored by Nugine's avatar Nugine Committed by GitHub
Browse files

feat(s3s-test): impl custom test suite (#191)

parent 9fb8e905
Loading
Loading
Loading
Loading
+19 −0
Original line number Diff line number Diff line
@@ -94,3 +94,22 @@ jobs:
        with:
          name: e2e-mint-logs
          path: ./target/s3s-proxy.log

  e2e-fs:
    needs: skip-check
    if: needs.skip-check.outputs.should_skip != 'true'
    name: e2e (s3s-fs, s3s-e2e)
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: dtolnay/rust-toolchain@master
        with:
          toolchain: stable
      - uses: Swatinem/rust-cache@v2
      - run: ./scripts/e2e-fs.sh
      - uses: actions/upload-artifact@v4
        with:
          name: e2e-fs-logs
          path: |
            ./target/s3s-fs.log
            ./target/s3s-e2e.log
+1 −0
Original line number Diff line number Diff line
@@ -3,3 +3,4 @@
.vscode

/codegen/s3.json
.env
+32 −0
Original line number Diff line number Diff line
[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"]
+54 −0
Original line number Diff line number Diff line
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);
    })
}
+102 −0
Original line number Diff line number Diff line
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);
    }
}
Loading