aboutsummaryrefslogtreecommitdiff
path: root/dev_tools
diff options
context:
space:
mode:
Diffstat (limited to 'dev_tools')
-rw-r--r--dev_tools/Cargo.lock154
-rw-r--r--dev_tools/Cargo.toml2
-rw-r--r--dev_tools/src/bin/test-examples.rs139
3 files changed, 295 insertions, 0 deletions
diff --git a/dev_tools/Cargo.lock b/dev_tools/Cargo.lock
index 7bcc602..35d3a8f 100644
--- a/dev_tools/Cargo.lock
+++ b/dev_tools/Cargo.lock
@@ -12,6 +12,28 @@ dependencies = [
]
[[package]]
+name = "equivalent"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
+
+[[package]]
+name = "hashbrown"
+version = "0.17.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
+
+[[package]]
+name = "indexmap"
+version = "2.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
+dependencies = [
+ "equivalent",
+ "hashbrown",
+]
+
+[[package]]
name = "just_fmt"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -27,15 +49,138 @@ dependencies = [
]
[[package]]
+name = "memchr"
+version = "2.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.106"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.45"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "serde"
+version = "1.0.228"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
+dependencies = [
+ "serde_core",
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_core"
+version = "1.0.228"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.228"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde_spanned"
+version = "0.6.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "syn"
+version = "2.0.117"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "toml"
+version = "0.8.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362"
+dependencies = [
+ "serde",
+ "serde_spanned",
+ "toml_datetime",
+ "toml_edit",
+]
+
+[[package]]
+name = "toml_datetime"
+version = "0.6.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "toml_edit"
+version = "0.22.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
+dependencies = [
+ "indexmap",
+ "serde",
+ "serde_spanned",
+ "toml_datetime",
+ "toml_write",
+ "winnow",
+]
+
+[[package]]
+name = "toml_write"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
+
+[[package]]
name = "tools"
version = "0.1.0"
dependencies = [
"colored",
"just_fmt",
"just_template",
+ "serde",
+ "toml",
]
[[package]]
+name = "unicode-ident"
+version = "1.0.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
+
+[[package]]
name = "windows-link"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -49,3 +194,12 @@ checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
dependencies = [
"windows-link",
]
+
+[[package]]
+name = "winnow"
+version = "0.7.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945"
+dependencies = [
+ "memchr",
+]
diff --git a/dev_tools/Cargo.toml b/dev_tools/Cargo.toml
index 7abb157..56f89d3 100644
--- a/dev_tools/Cargo.toml
+++ b/dev_tools/Cargo.toml
@@ -7,3 +7,5 @@ edition = "2024"
just_template = "0.1.3"
just_fmt = "0.1.2"
colored = "3.1.1"
+toml = "0.8"
+serde = { version = "1", features = ["derive"] }
diff --git a/dev_tools/src/bin/test-examples.rs b/dev_tools/src/bin/test-examples.rs
new file mode 100644
index 0000000..fa3d8f3
--- /dev/null
+++ b/dev_tools/src/bin/test-examples.rs
@@ -0,0 +1,139 @@
+use std::collections::HashMap;
+
+use serde::Deserialize;
+use tools::{eprintln_cargo_style, println_cargo_style, run_cmd};
+
+#[derive(Deserialize)]
+struct TestConfig {
+ test: HashMap<String, Vec<TestCase>>,
+}
+
+#[derive(Deserialize)]
+struct TestCase {
+ command: String,
+ expect: Expect,
+}
+
+#[derive(Deserialize)]
+struct Expect {
+ #[serde(rename = "exit-code")]
+ exit_code: i32,
+ result: String,
+}
+
+fn main() {
+ #[cfg(windows)]
+ let _ = colored::control::set_virtual_terminal(true);
+
+ let config = load_config();
+ let (passed, total) = run_all_tests(&config);
+
+ println_cargo_style!("Result: {}/{} tests passed", passed, total);
+
+ if passed != total {
+ eprintln_cargo_style!("{} test(s) failed", total - passed);
+ std::process::exit(1);
+ }
+}
+
+/// Parse test config from TOML file
+fn load_config() -> TestConfig {
+ let content = std::fs::read_to_string("examples/test-example-async.toml").unwrap_or_else(|e| {
+ eprintln_cargo_style!("Failed to read TOML config file: {}", e);
+ std::process::exit(1);
+ });
+
+ toml::from_str(&content).unwrap_or_else(|e| {
+ eprintln_cargo_style!("Failed to parse TOML config: {}", e);
+ std::process::exit(1);
+ })
+}
+
+/// Run all example test groups, return (passed, total)
+fn run_all_tests(config: &TestConfig) -> (usize, usize) {
+ let mut total = 0;
+ let mut passed = 0;
+
+ for (example_name, test_cases) in &config.test {
+ println_cargo_style!("Test: {}", example_name);
+
+ if !build_example(example_name) {
+ total += test_cases.len();
+ continue;
+ }
+
+ for test_case in test_cases {
+ total += 1;
+ if run_single_test(example_name, test_case) {
+ passed += 1;
+ }
+ }
+ }
+
+ (passed, total)
+}
+
+/// Build the example binary, return true on success
+fn build_example(example_name: &str) -> bool {
+ let manifest = format!("examples/{}/Cargo.toml", example_name);
+ run_cmd!("cargo build --manifest-path {}", manifest).is_ok()
+}
+
+/// Run a single test case, return true on pass
+fn run_single_test(example_name: &str, test_case: &TestCase) -> bool {
+ let binary_path = format!(".temp/target/debug/{}", get_binary_name(example_name));
+ let args: Vec<&str> = test_case.command.split_whitespace().collect();
+
+ let output = match std::process::Command::new(&binary_path)
+ .args(&args)
+ .output()
+ {
+ Ok(o) => o,
+ Err(e) => {
+ eprintln_cargo_style!("'{}' - failed to run: {}", test_case.command, e);
+ return false;
+ }
+ };
+
+ let actual_exit_code = output.status.code().unwrap_or(-1);
+ let actual_stdout = String::from_utf8_lossy(&output.stdout).trim().to_string();
+ let actual_stderr = String::from_utf8_lossy(&output.stderr).trim().to_string();
+
+ let exit_ok = actual_exit_code == test_case.expect.exit_code;
+ let result_ok = actual_stdout == test_case.expect.result
+ || actual_stdout.contains(&test_case.expect.result);
+
+ if exit_ok && result_ok {
+ println_cargo_style!("Passed: '{}'", test_case.command);
+ true
+ } else {
+ eprintln_cargo_style!("'{}'", test_case.command);
+ if !exit_ok {
+ eprintln_cargo_style!(
+ "Expected exit code: {}, actual: {}",
+ test_case.expect.exit_code,
+ actual_exit_code
+ );
+ }
+ if !result_ok {
+ eprintln_cargo_style!("Expected output: {:?}", test_case.expect.result);
+ eprintln_cargo_style!("Actual stdout: {:?}", actual_stdout);
+ if !actual_stderr.is_empty() {
+ eprintln_cargo_style!("Actual stderr: {:?}", actual_stderr);
+ }
+ }
+ false
+ }
+}
+
+/// Resolve binary filename for the given example
+///
+/// The binary name matches the package name. On Windows, the `.exe` suffix is required.
+fn get_binary_name(example_name: &str) -> String {
+ let base = example_name;
+ if cfg!(target_os = "windows") {
+ format!("{}.exe", base)
+ } else {
+ base.to_string()
+ }
+}