aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.run/src/bin/cov-test.rs61
-rw-r--r--.run/src/bin/test-examples.rs69
-rw-r--r--CONTRIBUTING.md47
-rw-r--r--docs/dev/pages/abouts/ci.md24
-rw-r--r--examples/example-argument-parse/test.toml29
-rw-r--r--examples/example-argument-picker/test.toml41
-rw-r--r--examples/example-async-support/test.toml5
-rw-r--r--examples/example-basic/test.toml11
-rw-r--r--examples/example-clap-binding/test.toml17
-rw-r--r--examples/example-combine-pathf-dispatch-tree/test.toml11
-rw-r--r--examples/example-combine-pathf-metadata/test.toml17
-rw-r--r--examples/example-command-macro/test.toml17
-rw-r--r--examples/example-completion/test.toml5
-rw-r--r--examples/example-custom-pickable/test.toml17
-rw-r--r--examples/example-dispatch-tree/test.toml5
-rw-r--r--examples/example-enum-tag/test.toml17
-rw-r--r--examples/example-error-handling/test.toml29
-rw-r--r--examples/example-exitcode/test.toml17
-rw-r--r--examples/example-help/test.toml11
-rw-r--r--examples/example-hook/test.toml11
-rw-r--r--examples/example-lazy-resources/test.toml11
-rw-r--r--examples/example-metadata/test.toml23
-rw-r--r--examples/example-outside-type/test.toml17
-rw-r--r--examples/example-pack-err/test.toml35
-rw-r--r--examples/example-panic-unwind/test.toml11
-rw-r--r--examples/example-pathfinder/test.toml11
-rw-r--r--examples/example-resources/test.toml5
-rw-r--r--examples/example-setup/test.toml29
-rw-r--r--examples/example-structural-renderer/test.toml11
-rw-r--r--examples/test-examples.toml364
30 files changed, 547 insertions, 431 deletions
diff --git a/.run/src/bin/cov-test.rs b/.run/src/bin/cov-test.rs
index 1b2342e..ff2c3fc 100644
--- a/.run/src/bin/cov-test.rs
+++ b/.run/src/bin/cov-test.rs
@@ -18,7 +18,6 @@
//! cargo install --git https://github.com/Weicao-CatilGrass/cargo-llvm-cov cargo-llvm-cov
//! ```
-use std::collections::HashMap;
use std::fs;
use std::path::{Path, PathBuf};
@@ -34,15 +33,16 @@ const OUTPUT_DIR: &str = "docs/cov-test";
/// in one place so the final `report` can merge everything.
const COV_TARGET_DIR: &str = ".temp/cov-llvm";
-/// Parsed `examples/test-examples.toml` (`test.<example> = [ { command, expect } ]`).
+/// An example's `test.toml` (`[[runs]]` entries).
#[derive(Deserialize)]
struct TestConfig {
- test: HashMap<String, Vec<TestCase>>,
+ runs: Vec<TestCase>,
}
+/// One `[[runs]]` entry of an example's `test.toml`.
#[derive(Deserialize)]
struct TestCase {
- command: String,
+ input: Vec<String>,
}
fn main() {
@@ -108,7 +108,7 @@ fn main() {
}
// 3. Examples: build each example with explicit RUSTFLAGS, then execute
- // every command from test-examples.toml directly.
+ // every command declared in the example's test.toml directly.
//
// NOTE: `cargo llvm-cov run` cannot be used here. Its rustc wrapper
// only instruments the crates of the *current* cargo project (with
@@ -127,7 +127,7 @@ fn main() {
}
let examples = load_example_commands(&repo_root);
let mut built = std::collections::HashSet::new();
- for (example, command) in &examples {
+ for (example, input) in &examples {
if built.insert(example.clone()) {
println_cargo_style!("Building: {}", example);
run_cmd!(format!(
@@ -150,7 +150,7 @@ fn main() {
example
);
match std::process::Command::new(&binary)
- .args(command.split_whitespace())
+ .args(input)
.env("LLVM_PROFILE_FILE", &profraw)
.status()
{
@@ -279,22 +279,43 @@ fn find_test_crate_manifests(repo_root: &Path) -> Vec<PathBuf> {
manifests
}
-/// Parse `examples/test-examples.toml` into `(example_name, command)` pairs.
-fn load_example_commands(repo_root: &Path) -> Vec<(String, String)> {
- let content =
- fs::read_to_string(repo_root.join("examples/test-examples.toml")).unwrap_or_else(|e| {
- eprintln_cargo_style!("Failed to read examples/test-examples.toml: {}", e);
+/// Parse every `examples/<name>/test.toml` into `(example_name, input)` pairs.
+fn load_example_commands(repo_root: &Path) -> Vec<(String, Vec<String>)> {
+ let examples_dir = repo_root.join("examples");
+ let mut entries: Vec<_> = std::fs::read_dir(&examples_dir)
+ .unwrap_or_else(|e| {
+ eprintln_cargo_style!("Failed to read {}: {}", examples_dir.display(), e);
std::process::exit(1);
- });
- let config: TestConfig = toml::from_str(&content).unwrap_or_else(|e| {
- eprintln_cargo_style!("Failed to parse examples/test-examples.toml: {}", e);
- std::process::exit(1);
- });
+ })
+ .flatten()
+ .collect();
+ entries.sort_by_key(|e| e.file_name());
let mut pairs = Vec::new();
- for (example, cases) in &config.test {
- for case in cases {
- pairs.push((example.clone(), case.command.clone()));
+ for entry in entries {
+ let path = entry.path();
+ if !path.is_dir() {
+ continue;
+ }
+ let test_toml = path.join("test.toml");
+ if !test_toml.is_file() {
+ continue;
+ }
+ let name = path
+ .file_name()
+ .and_then(|n| n.to_str())
+ .unwrap_or_default()
+ .to_string();
+ let content = fs::read_to_string(&test_toml).unwrap_or_else(|e| {
+ eprintln_cargo_style!("Failed to read {}: {}", test_toml.display(), e);
+ std::process::exit(1);
+ });
+ let config: TestConfig = toml::from_str(&content).unwrap_or_else(|e| {
+ eprintln_cargo_style!("Failed to parse {}: {}", test_toml.display(), e);
+ std::process::exit(1);
+ });
+ for case in config.runs {
+ pairs.push((name.clone(), case.input));
}
}
pairs
diff --git a/.run/src/bin/test-examples.rs b/.run/src/bin/test-examples.rs
index 539459e..55976ef 100644
--- a/.run/src/bin/test-examples.rs
+++ b/.run/src/bin/test-examples.rs
@@ -1,18 +1,20 @@
-use std::collections::HashMap;
+use std::path::Path;
use colored::Colorize;
use indicatif::ProgressBar;
use serde::Deserialize;
use tools::{eprintln_cargo_style, println_cargo_style};
+/// An example's `test.toml` (`[[runs]]` entries).
#[derive(Deserialize)]
struct TestConfig {
- test: HashMap<String, Vec<TestCase>>,
+ runs: Vec<TestCase>,
}
+/// A single `[[runs]]` entry of an example's `test.toml`.
#[derive(Deserialize)]
struct TestCase {
- command: String,
+ input: Vec<String>,
expect: Expect,
}
@@ -27,10 +29,10 @@ fn main() {
#[cfg(windows)]
let _ = colored::control::set_virtual_terminal(true);
- let config = load_config();
+ let configs = load_all_test_configs();
// Count total test cases upfront
- let total: usize = config.test.values().map(|cases| cases.len()).sum();
+ let total: usize = configs.iter().map(|(_, cases)| cases.len()).sum();
let bar = ProgressBar::new(total as u64);
bar.set_style(
indicatif::ProgressStyle::default_bar()
@@ -43,7 +45,7 @@ fn main() {
);
bar.set_message("examples");
- let passed = run_all_tests(&config, &bar);
+ let passed = run_all_tests(&configs, &bar);
bar.finish_and_clear();
@@ -55,24 +57,51 @@ fn main() {
}
}
-/// Parse test config from TOML file
-fn load_config() -> TestConfig {
- let content = std::fs::read_to_string("examples/test-examples.toml").unwrap_or_else(|e| {
- eprintln_cargo_style!("Failed to read TOML config file: {}", e);
+/// Load `examples/<name>/test.toml` for every example that has one, in
+/// alphabetical order of the example directory name.
+fn load_all_test_configs() -> Vec<(String, Vec<TestCase>)> {
+ let examples_dir = Path::new("examples");
+ let mut configs = Vec::new();
+
+ let entries = std::fs::read_dir(examples_dir).unwrap_or_else(|e| {
+ eprintln_cargo_style!("Failed to read examples dir: {}", 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);
- })
+ for entry in entries.flatten() {
+ let path = entry.path();
+ if !path.is_dir() {
+ continue;
+ }
+ let test_toml = path.join("test.toml");
+ if !test_toml.is_file() {
+ continue;
+ }
+ let name = path
+ .file_name()
+ .and_then(|n| n.to_str())
+ .unwrap_or_default()
+ .to_string();
+ let content = std::fs::read_to_string(&test_toml).unwrap_or_else(|e| {
+ eprintln_cargo_style!("Failed to read {}: {}", test_toml.display(), e);
+ std::process::exit(1);
+ });
+ let config: TestConfig = toml::from_str(&content).unwrap_or_else(|e| {
+ eprintln_cargo_style!("Failed to parse {}: {}", test_toml.display(), e);
+ std::process::exit(1);
+ });
+ configs.push((name, config.runs));
+ }
+
+ configs.sort_by(|a, b| a.0.cmp(&b.0));
+ configs
}
/// Run all example test groups, return number passed
-fn run_all_tests(config: &TestConfig, bar: &ProgressBar) -> usize {
+fn run_all_tests(configs: &[(String, Vec<TestCase>)], bar: &ProgressBar) -> usize {
let mut passed = 0;
- for (example_name, test_cases) in &config.test {
+ for (example_name, test_cases) in configs {
bar.set_message(example_name.clone());
if !build_example(example_name) {
@@ -103,15 +132,15 @@ fn build_example(example_name: &str) -> bool {
/// Run a single test case, return true on pass
fn run_single_test(example_name: &str, test_case: &TestCase, bar: &ProgressBar) -> bool {
let binary_path = format!(".temp/target/debug/{}", get_binary_name(example_name));
- let args: Vec<&str> = test_case.command.split_whitespace().collect();
+ let command = test_case.input.join(" ");
let output = match std::process::Command::new(&binary_path)
- .args(&args)
+ .args(&test_case.input)
.output()
{
Ok(o) => o,
Err(e) => {
- bar.println(format!("'{}' - failed to run: {}", test_case.command, e));
+ bar.println(format!("'{command}' - failed to run: {e}"));
return false;
}
};
@@ -127,7 +156,7 @@ fn run_single_test(example_name: &str, test_case: &TestCase, bar: &ProgressBar)
if exit_ok && result_ok {
true
} else {
- bar.println(format!("failed: '{}'", test_case.command));
+ bar.println(format!("failed: '{command}'"));
if !exit_ok {
bar.println(format!(
" Expected exit code: {}, actual: {}",
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index e729295..779bc3e 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -6,22 +6,22 @@ Before contributing, we recommend reading [README](README.md) to get an overview
## 1. Project Structure 📦
-| Category | Path/Name | Description |
-| --------------------------- | -------------------- | ------------------------------------------------------------------ |
-| **Entry crate** | `mingling/` | Project entry point |
-| **Core library** | `mingling_core/` | Imported as an external dependency |
-| **Macro library** | `mingling_macros/` | Imported as an external dependency |
-| **Mingling Pathfinder** | `mingling_pathf/` | Build-time module path resolution for types |
-| **Mingling Picker2** | `arg_picker/` | Mingling Arguments Parser |
-| **Mingling Picker2 Macros** | `arg_picker_macros/` | Mingling Arguments Parser Macros |
-| **Scaffolding tool** | `mling/` | Scaffolding tool `mingling-cli` |
-| **Examples** | `examples/` | To add expected output tests, modify `examples/test-examples.toml` |
-| **Documents** | `docs/` | All documents |
-| **Dev Documents** | `docs/dev/` | Internal documents |
-| **Resources** | `docs/res/` | All resources |
-| **Development tools** | `.run/src/bin` | Contains scripts and Rust tools |
-| **CI** | `.run/src/bin/ci.rs` | Can be invoked directly via `cargo ci` |
-| **Temporary files** | `.temp/` | Ignored by `.gitignore` |
+| Category | Path/Name | Description |
+| --------------------------- | -------------------- | -------------------------------------------------------------------- |
+| **Entry crate** | `mingling/` | Project entry point |
+| **Core library** | `mingling_core/` | Imported as an external dependency |
+| **Macro library** | `mingling_macros/` | Imported as an external dependency |
+| **Mingling Pathfinder** | `mingling_pathf/` | Build-time module path resolution for types |
+| **Mingling Picker2** | `arg_picker/` | Mingling Arguments Parser |
+| **Mingling Picker2 Macros** | `arg_picker_macros/` | Mingling Arguments Parser Macros |
+| **Scaffolding tool** | `mling/` | Scaffolding tool `mingling-cli` |
+| **Examples** | `examples/` | To add expected output tests, add a `test.toml` in the example's dir |
+| **Documents** | `docs/` | All documents |
+| **Dev Documents** | `docs/dev/` | Internal documents |
+| **Resources** | `docs/res/` | All resources |
+| **Development tools** | `.run/src/bin` | Contains scripts and Rust tools |
+| **CI** | `.run/src/bin/ci.rs` | Can be invoked directly via `cargo ci` |
+| **Temporary files** | `.temp/` | Ignored by `.gitignore` |
## 2. How to Contribute
@@ -51,7 +51,20 @@ tags = ["tag1", "tag2"] # Tags (optional)
files = ["Cargo.toml", "src/main.rs"]
```
-If you change expected behavior, update the test assertions in `examples/test-examples.toml`.
+Optionally, each example may contain a `test.toml` file declaring expected output tests, which are executed by CI (`./run.sh test-examples`):
+
+```toml
+[[runs]]
+input = ["greet", "Alice"]
+
+expect.exit-code = 0
+expect.result = "Hello, Alice!"
+```
+
+- `input` is the list of CLI arguments passed to the example binary
+- `expect.exit-code` / `expect.result` assert the expected process exit code and stdout output
+
+If you change expected behavior, update the assertions in the example's `test.toml`.
After editing examples, run these scripts to keep things in sync:
diff --git a/docs/dev/pages/abouts/ci.md b/docs/dev/pages/abouts/ci.md
index a9be500..659a5fc 100644
--- a/docs/dev/pages/abouts/ci.md
+++ b/docs/dev/pages/abouts/ci.md
@@ -26,16 +26,16 @@ cargo ci
Every CI step is an independent switch (`--check-*`). Running `cargo ci` with no options executes **all** steps in the order below; pass one or more `--check-*` flags to run only the selected steps.
-| Step | Flag | What it does |
-| --------------- | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
-| Build | `--check-build` | Recursively finds all `Cargo.toml` files and runs `cargo build` for each crate in parallel (workspace members build with all documented features). |
-| Clippy | `--check-clippy` | Runs `cargo clippy ... -- -D warnings` for every crate in parallel; any warning fails the check. |
-| Test | `--check-test` | Runs `cargo test` for every crate in parallel (workspace tests run with all documented features; `arg-picker` is excluded). |
-| Arg picker | `--check-arg-picker` | Runs `cargo test -p arg-picker` with its default features. |
-| Markdown code | `--check-markdown-code` | Runs the `test-all-markdown-code` tool to verify code blocks in all `*.md` files compile. See [ABOUT_CODE_VERIFY](docs/_ABOUT_CODE_VERIFY.md). |
-| Examples | `--check-examples` | Runs the `test-examples` tool to verify all examples behave as expected. |
-| Docs up to date | `--check-docs-refresh` | Runs the documentation refresh tools and `cargo fmt`, then fails if the working tree is no longer clean (i.e. the docs were stale). |
-| API docs | `--check-api-docs` | Builds API docs with the `[package.metadata.docs.rs]` features and fails if `docs/api-docs/` is out of date. |
+| Step | Flag | What it does |
+| --------------- | ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| Build | `--check-build` | Recursively finds all `Cargo.toml` files and runs `cargo build` for each crate in parallel (workspace members build with all documented features). |
+| Clippy | `--check-clippy` | Runs `cargo clippy ... -- -D warnings` for every crate in parallel; any warning fails the check. |
+| Test | `--check-test` | Runs `cargo test` for every crate in parallel (workspace tests run with all documented features; `arg-picker` is excluded). |
+| Arg picker | `--check-arg-picker` | Runs `cargo test -p arg-picker` with its default features. |
+| Markdown code | `--check-markdown-code` | Runs the `test-all-markdown-code` tool to verify code blocks in all `*.md` files compile. See [ABOUT_CODE_VERIFY](docs/_ABOUT_CODE_VERIFY.md). |
+| Examples | `--check-examples` | Runs the `test-examples` tool to verify all examples behave as expected. Each example declares its expected output tests in `examples/<example>/test.toml`. |
+| Docs up to date | `--check-docs-refresh` | Runs the documentation refresh tools and `cargo fmt`, then fails if the working tree is no longer clean (i.e. the docs were stale). |
+| API docs | `--check-api-docs` | Builds API docs with the `[package.metadata.docs.rs]` features and fails if `docs/api-docs/` is out of date. |
### Docs up to date in detail
@@ -49,6 +49,10 @@ Every CI step is an independent switch (`--check-*`). Running `cargo ci` with no
Finally, it runs `cargo fmt` to unify code formatting. Because the refresh tools regenerate derived files, running this check against stale documentation modifies the working tree — and `ci.rs` fails the run in that case. (Using `--dirty` skips the cleanliness check, which makes this flag behave like a plain "refresh docs" command.)
+### Examples in detail
+
+`--check-examples` runs the `test-examples` tool, which scans `examples/*/test.toml`. Each file contains `[[runs]]` entries declaring the CLI arguments (`input`) and the expected `exit-code` / `result`. The tool builds every example that has a `test.toml` and executes each run against the built binary, so an example that changes its behavior only needs its own `test.toml` updated.
+
### Combining steps
When several `--check-*` flags are combined, the steps run in the order listed above. In "run all" mode (no flags given), the documentation steps all execute even if one of them fails, so every problem is reported in a single run.
diff --git a/examples/example-argument-parse/test.toml b/examples/example-argument-parse/test.toml
new file mode 100644
index 0000000..a405bd9
--- /dev/null
+++ b/examples/example-argument-parse/test.toml
@@ -0,0 +1,29 @@
+[[runs]]
+input = [ "transfer" ]
+
+expect.exit-code = 0
+expect.result = "file: (1048576)"
+
+[[runs]]
+input = [ "transfer", "--dir", "src" ]
+
+expect.exit-code = 0
+expect.result = "dir: src (1048576)"
+
+[[runs]]
+input = [ "transfer", "--size", "500", "myfile.txt" ]
+
+expect.exit-code = 0
+expect.result = "file: myfile.txt (500)"
+
+[[runs]]
+input = [ "strict-transfer", "README.md" ]
+
+expect.exit-code = 0
+expect.result = "file: README.md (1048576)"
+
+[[runs]]
+input = [ "strict-transfer" ]
+
+expect.exit-code = 0
+expect.result = "Error: name is not provided"
diff --git a/examples/example-argument-picker/test.toml b/examples/example-argument-picker/test.toml
new file mode 100644
index 0000000..a67ffbe
--- /dev/null
+++ b/examples/example-argument-picker/test.toml
@@ -0,0 +1,41 @@
+[[runs]]
+input = [ "calc", "1", "+", "1" ]
+
+expect.exit-code = 0
+expect.result = "Result: 2"
+
+[[runs]]
+input = [ "calc", "7", "*", "7" ]
+
+expect.exit-code = 0
+expect.result = "Result: 49"
+
+[[runs]]
+input = [ "calc" ]
+
+expect.exit-code = 0
+expect.result = "Error: First number (number_a) was not provided."
+
+[[runs]]
+input = [ "calc", "1" ]
+
+expect.exit-code = 0
+expect.result = "Error: Operator was not provided."
+
+[[runs]]
+input = [ "calc", "1", "+" ]
+
+expect.exit-code = 0
+expect.result = "Error: Second number (number_b) was not provided."
+
+[[runs]]
+input = [ "calc", "4", "/", "3" ]
+
+expect.exit-code = 0
+expect.result = "Result: 1.3333334"
+
+[[runs]]
+input = [ "calc", "4", "/", "3", "--round" ]
+
+expect.exit-code = 0
+expect.result = "Result: 1"
diff --git a/examples/example-async-support/test.toml b/examples/example-async-support/test.toml
new file mode 100644
index 0000000..735e115
--- /dev/null
+++ b/examples/example-async-support/test.toml
@@ -0,0 +1,5 @@
+[[runs]]
+input = [ "download", "readme.txt" ]
+
+expect.exit-code = 0
+expect.result = "\"readme.txt\" downloaded."
diff --git a/examples/example-basic/test.toml b/examples/example-basic/test.toml
new file mode 100644
index 0000000..fb4f1de
--- /dev/null
+++ b/examples/example-basic/test.toml
@@ -0,0 +1,11 @@
+[[runs]]
+input = [ "greet" ]
+
+expect.exit-code = 0
+expect.result = "Hello, World!"
+
+[[runs]]
+input = [ "greet", "Alice" ]
+
+expect.exit-code = 0
+expect.result = "Hello, Alice!"
diff --git a/examples/example-clap-binding/test.toml b/examples/example-clap-binding/test.toml
new file mode 100644
index 0000000..a12daa0
--- /dev/null
+++ b/examples/example-clap-binding/test.toml
@@ -0,0 +1,17 @@
+[[runs]]
+input = [ "greet" ]
+
+expect.exit-code = 0
+expect.result = "Hello, World!"
+
+[[runs]]
+input = [ "greet", "Alice" ]
+
+expect.exit-code = 0
+expect.result = "Hello, Alice!"
+
+[[runs]]
+input = [ "greet", "Alice", "-r", "5" ]
+
+expect.exit-code = 0
+expect.result = "Hello, Alice, Alice, Alice, Alice, Alice!"
diff --git a/examples/example-combine-pathf-dispatch-tree/test.toml b/examples/example-combine-pathf-dispatch-tree/test.toml
new file mode 100644
index 0000000..1fd14d9
--- /dev/null
+++ b/examples/example-combine-pathf-dispatch-tree/test.toml
@@ -0,0 +1,11 @@
+[[runs]]
+input = [ "hello", "Alice" ]
+
+expect.exit-code = 0
+expect.result = "Hello, Alice!"
+
+[[runs]]
+input = [ "hello" ]
+
+expect.exit-code = 0
+expect.result = "Hello, World!"
diff --git a/examples/example-combine-pathf-metadata/test.toml b/examples/example-combine-pathf-metadata/test.toml
new file mode 100644
index 0000000..2803cee
--- /dev/null
+++ b/examples/example-combine-pathf-metadata/test.toml
@@ -0,0 +1,17 @@
+[[runs]]
+input = [ "hello" ]
+
+expect.exit-code = 0
+expect.result = "Hello, World!"
+
+[[runs]]
+input = [ "hello", "Alice" ]
+
+expect.exit-code = 0
+expect.result = "Hello, Alice!"
+
+[[runs]]
+input = [ "desc" ]
+
+expect.exit-code = 0
+expect.result = "EntryHello desc = okay"
diff --git a/examples/example-command-macro/test.toml b/examples/example-command-macro/test.toml
new file mode 100644
index 0000000..215cbb3
--- /dev/null
+++ b/examples/example-command-macro/test.toml
@@ -0,0 +1,17 @@
+[[runs]]
+input = [ "hello", "world" ]
+
+expect.exit-code = 0
+expect.result = "Hello, World"
+
+[[runs]]
+input = [ "greet-someone", "Alice" ]
+
+expect.exit-code = 0
+expect.result = "Hello, Alice"
+
+[[runs]]
+input = [ "goodbye" ]
+
+expect.exit-code = 0
+expect.result = "Goodbye!"
diff --git a/examples/example-completion/test.toml b/examples/example-completion/test.toml
new file mode 100644
index 0000000..ccb0e46
--- /dev/null
+++ b/examples/example-completion/test.toml
@@ -0,0 +1,5 @@
+[[runs]]
+input = [ "greet", "World", "--repeat", "1" ]
+
+expect.exit-code = 0
+expect.result = "Hello, World!"
diff --git a/examples/example-custom-pickable/test.toml b/examples/example-custom-pickable/test.toml
new file mode 100644
index 0000000..8fc20eb
--- /dev/null
+++ b/examples/example-custom-pickable/test.toml
@@ -0,0 +1,17 @@
+[[runs]]
+input = [ "connect", "192.168.1.1:8080" ]
+
+expect.exit-code = 0
+expect.result = "Connected to \"192.168.1.1:8080\""
+
+[[runs]]
+input = [ "connect" ]
+
+expect.exit-code = 0
+expect.result = "Failed to parse address"
+
+[[runs]]
+input = [ "connect", "invalid" ]
+
+expect.exit-code = 0
+expect.result = "Failed to parse address"
diff --git a/examples/example-dispatch-tree/test.toml b/examples/example-dispatch-tree/test.toml
new file mode 100644
index 0000000..d455c48
--- /dev/null
+++ b/examples/example-dispatch-tree/test.toml
@@ -0,0 +1,5 @@
+[[runs]]
+input = [ "cmd5" ]
+
+expect.exit-code = 0
+expect.result = "It's works!"
diff --git a/examples/example-enum-tag/test.toml b/examples/example-enum-tag/test.toml
new file mode 100644
index 0000000..ca83b7f
--- /dev/null
+++ b/examples/example-enum-tag/test.toml
@@ -0,0 +1,17 @@
+[[runs]]
+input = [ "lang-select" ]
+
+expect.exit-code = 0
+expect.result = "Selected: Rust"
+
+[[runs]]
+input = [ "lang-select", "Python" ]
+
+expect.exit-code = 0
+expect.result = "Selected: Python"
+
+[[runs]]
+input = [ "lang-select", "OCaml" ]
+
+expect.exit-code = 0
+expect.result = "Selected: OCaml"
diff --git a/examples/example-error-handling/test.toml b/examples/example-error-handling/test.toml
new file mode 100644
index 0000000..9df5f6f
--- /dev/null
+++ b/examples/example-error-handling/test.toml
@@ -0,0 +1,29 @@
+[[runs]]
+input = [ "hello" ]
+
+expect.exit-code = 0
+expect.result = "No name provided"
+
+[[runs]]
+input = [ "hello", "MyBestFriendAlice" ]
+
+expect.exit-code = 0
+expect.result = "Name too long: 17 > 10"
+
+[[runs]]
+input = [ "hello", "Alice" ]
+
+expect.exit-code = 0
+expect.result = "Name not available"
+
+[[runs]]
+input = [ "hello", "Peter" ]
+
+expect.exit-code = 0
+expect.result = "Hello, Peter"
+
+[[runs]]
+input = [ "hallo" ]
+
+expect.exit-code = 0
+expect.result = "Command not found: \"hallo\""
diff --git a/examples/example-exitcode/test.toml b/examples/example-exitcode/test.toml
new file mode 100644
index 0000000..4a4989d
--- /dev/null
+++ b/examples/example-exitcode/test.toml
@@ -0,0 +1,17 @@
+[[runs]]
+input = [ "hello", "Alice" ]
+
+expect.exit-code = 0
+expect.result = "Hello, Alice"
+
+[[runs]]
+input = [ "hello" ]
+
+expect.exit-code = 1
+expect.result = "No name provided (with exit code 1)"
+
+[[runs]]
+input = [ "hello", "--help" ]
+
+expect.exit-code = 2
+expect.result = "Usage: hello <NAME>"
diff --git a/examples/example-help/test.toml b/examples/example-help/test.toml
new file mode 100644
index 0000000..d1ec550
--- /dev/null
+++ b/examples/example-help/test.toml
@@ -0,0 +1,11 @@
+[[runs]]
+input = [ "greet" ]
+
+expect.exit-code = 0
+expect.result = ""
+
+[[runs]]
+input = [ "greet", "--help" ]
+
+expect.exit-code = 0
+expect.result = "Usage: greet <NAME>"
diff --git a/examples/example-hook/test.toml b/examples/example-hook/test.toml
new file mode 100644
index 0000000..fb4f1de
--- /dev/null
+++ b/examples/example-hook/test.toml
@@ -0,0 +1,11 @@
+[[runs]]
+input = [ "greet" ]
+
+expect.exit-code = 0
+expect.result = "Hello, World!"
+
+[[runs]]
+input = [ "greet", "Alice" ]
+
+expect.exit-code = 0
+expect.result = "Hello, Alice!"
diff --git a/examples/example-lazy-resources/test.toml b/examples/example-lazy-resources/test.toml
new file mode 100644
index 0000000..c8729a7
--- /dev/null
+++ b/examples/example-lazy-resources/test.toml
@@ -0,0 +1,11 @@
+[[runs]]
+input = [ "none" ]
+
+expect.exit-code = 0
+expect.result = "None"
+
+[[runs]]
+input = [ "show" ]
+
+expect.exit-code = 0
+expect.result = "Initialized\nbaz: qux\nfoo: bar\nhello: world\nkey: value\nrust: lang"
diff --git a/examples/example-metadata/test.toml b/examples/example-metadata/test.toml
new file mode 100644
index 0000000..0cd0ce1
--- /dev/null
+++ b/examples/example-metadata/test.toml
@@ -0,0 +1,23 @@
+[[runs]]
+input = [ "greet" ]
+
+expect.exit-code = 0
+expect.result = "Hello, World!"
+
+[[runs]]
+input = [ "greet", "Alice" ]
+
+expect.exit-code = 0
+expect.result = "Hello, Alice!"
+
+[[runs]]
+input = [ "desc" ]
+
+expect.exit-code = 0
+expect.result = "EntryGreet desc = ok"
+
+[[runs]]
+input = [ "nodoc" ]
+
+expect.exit-code = 0
+expect.result = "EntryDescription has no description"
diff --git a/examples/example-outside-type/test.toml b/examples/example-outside-type/test.toml
new file mode 100644
index 0000000..50eaa90
--- /dev/null
+++ b/examples/example-outside-type/test.toml
@@ -0,0 +1,17 @@
+[[runs]]
+input = [ "parse", "42" ]
+
+expect.exit-code = 0
+expect.result = "Parsed number: 42"
+
+[[runs]]
+input = [ "parse", "hello" ]
+
+expect.exit-code = 0
+expect.result = "Parse error: invalid digit found in string"
+
+[[runs]]
+input = [ "error" ]
+
+expect.exit-code = 0
+expect.result = "IO_ERROR: Error"
diff --git a/examples/example-pack-err/test.toml b/examples/example-pack-err/test.toml
new file mode 100644
index 0000000..c4509cb
--- /dev/null
+++ b/examples/example-pack-err/test.toml
@@ -0,0 +1,35 @@
+[[runs]]
+input = [ "find" ]
+
+expect.exit-code = 0
+expect.result = "Search path not provided"
+
+[[runs]]
+input = [ "find", "Cargo.toml" ]
+
+expect.exit-code = 0
+expect.result = "Not a directory: Cargo.toml"
+
+[[runs]]
+input = [ "find", "examples" ]
+
+expect.exit-code = 0
+expect.result = "Found directory: examples"
+
+[[runs]]
+input = [ "find-structural", "--json" ]
+
+expect.exit-code = 0
+expect.result = "{\"name\":\"error_not_found_structural\"}"
+
+[[runs]]
+input = [ "find-structural", "Cargo.toml", "--json" ]
+
+expect.exit-code = 0
+expect.result = "{\"name\":\"error_not_dir_structural\",\"info\":\"Cargo.toml\"}"
+
+[[runs]]
+input = [ "find-structural", "examples", "--json" ]
+
+expect.exit-code = 0
+expect.result = "{\"inner\":\"examples\"}"
diff --git a/examples/example-panic-unwind/test.toml b/examples/example-panic-unwind/test.toml
new file mode 100644
index 0000000..2a29071
--- /dev/null
+++ b/examples/example-panic-unwind/test.toml
@@ -0,0 +1,11 @@
+[[runs]]
+input = [ "panic" ]
+
+expect.exit-code = 0
+expect.result = "Program not panic"
+
+[[runs]]
+input = [ "panic", "something_went_wrong" ]
+
+expect.exit-code = 0
+expect.result = "Program panic: something_went_wrong"
diff --git a/examples/example-pathfinder/test.toml b/examples/example-pathfinder/test.toml
new file mode 100644
index 0000000..fb4f1de
--- /dev/null
+++ b/examples/example-pathfinder/test.toml
@@ -0,0 +1,11 @@
+[[runs]]
+input = [ "greet" ]
+
+expect.exit-code = 0
+expect.result = "Hello, World!"
+
+[[runs]]
+input = [ "greet", "Alice" ]
+
+expect.exit-code = 0
+expect.result = "Hello, Alice!"
diff --git a/examples/example-resources/test.toml b/examples/example-resources/test.toml
new file mode 100644
index 0000000..3dfa8fe
--- /dev/null
+++ b/examples/example-resources/test.toml
@@ -0,0 +1,5 @@
+[[runs]]
+input = [ "current" ]
+
+expect.exit-code = 0
+expect.result = "Current directory:"
diff --git a/examples/example-setup/test.toml b/examples/example-setup/test.toml
new file mode 100644
index 0000000..811a108
--- /dev/null
+++ b/examples/example-setup/test.toml
@@ -0,0 +1,29 @@
+[[runs]]
+input = [ "1" ]
+
+expect.exit-code = 0
+expect.result = ""
+
+[[runs]]
+input = [ "2" ]
+
+expect.exit-code = 0
+expect.result = ""
+
+[[runs]]
+input = [ "3" ]
+
+expect.exit-code = 0
+expect.result = ""
+
+[[runs]]
+input = [ "4" ]
+
+expect.exit-code = 0
+expect.result = ""
+
+[[runs]]
+input = [ "5" ]
+
+expect.exit-code = 0
+expect.result = ""
diff --git a/examples/example-structural-renderer/test.toml b/examples/example-structural-renderer/test.toml
new file mode 100644
index 0000000..2271a51
--- /dev/null
+++ b/examples/example-structural-renderer/test.toml
@@ -0,0 +1,11 @@
+[[runs]]
+input = [ "render", "Bob", "22" ]
+
+expect.exit-code = 0
+expect.result = "Bob is 22 years old"
+
+[[runs]]
+input = [ "render", "Bob", "22", "--json" ]
+
+expect.exit-code = 0
+expect.result = "{\"member_name\":\"Bob\",\"member_age\":22}"
diff --git a/examples/test-examples.toml b/examples/test-examples.toml
deleted file mode 100644
index 03df5e1..0000000
--- a/examples/test-examples.toml
+++ /dev/null
@@ -1,364 +0,0 @@
-[[test.example-outside-type]]
-command = "parse 42"
-expect.exit-code = 0
-expect.result = "Parsed number: 42"
-
-[[test.example-outside-type]]
-command = "parse hello"
-expect.exit-code = 0
-expect.result = "Parse error: invalid digit found in string"
-
-[[test.example-outside-type]]
-command = "error"
-expect.exit-code = 0
-expect.result = "IO_ERROR: Error"
-
-[[test.example-lazy-resources]]
-command = "none"
-expect.exit-code = 0
-expect.result = "None"
-
-[[test.example-lazy-resources]]
-command = "show"
-expect.exit-code = 0
-expect.result = "Initialized\nbaz: qux\nfoo: bar\nhello: world\nkey: value\nrust: lang"
-
-[[test.example-basic]]
-command = "greet"
-expect.exit-code = 0
-expect.result = "Hello, World!"
-
-[[test.example-basic]]
-command = "greet Alice"
-expect.exit-code = 0
-expect.result = "Hello, Alice!"
-
-[[test.example-exitcode]]
-command = "hello Alice"
-expect.exit-code = 0
-expect.result = "Hello, Alice"
-
-[[test.example-exitcode]]
-command = "hello"
-expect.exit-code = 1
-expect.result = "No name provided (with exit code 1)"
-
-[[test.example-exitcode]]
-command = "hello --help"
-expect.exit-code = 2
-expect.result = "Usage: hello <NAME>"
-
-[[test.example-error-handling]]
-command = "hello"
-expect.exit-code = 0
-expect.result = "No name provided"
-
-[[test.example-error-handling]]
-command = "hello MyBestFriendAlice"
-expect.exit-code = 0
-expect.result = "Name too long: 17 > 10"
-
-[[test.example-error-handling]]
-command = "hello Alice"
-expect.exit-code = 0
-expect.result = "Name not available"
-
-[[test.example-error-handling]]
-command = "hello Peter"
-expect.exit-code = 0
-expect.result = "Hello, Peter"
-
-[[test.example-error-handling]]
-command = "hallo"
-expect.exit-code = 0
-expect.result = "Command not found: \"hallo\""
-
-[[test.example-argument-parse]]
-command = "transfer"
-expect.exit-code = 0
-expect.result = "file: (1048576)"
-
-[[test.example-argument-parse]]
-command = "transfer --dir src"
-expect.exit-code = 0
-expect.result = "dir: src (1048576)"
-
-[[test.example-argument-parse]]
-command = "transfer --size 500 myfile.txt"
-expect.exit-code = 0
-expect.result = "file: myfile.txt (500)"
-
-[[test.example-argument-parse]]
-command = "strict-transfer README.md"
-expect.exit-code = 0
-expect.result = "file: README.md (1048576)"
-
-[[test.example-argument-parse]]
-command = "strict-transfer"
-expect.exit-code = 0
-expect.result = "Error: name is not provided"
-
-[[test.example-async-support]]
-command = "download readme.txt"
-expect.exit-code = 0
-expect.result = "\"readme.txt\" downloaded."
-
-[[test.example-custom-pickable]]
-command = "connect 192.168.1.1:8080"
-expect.exit-code = 0
-expect.result = "Connected to \"192.168.1.1:8080\""
-
-[[test.example-custom-pickable]]
-command = "connect"
-expect.exit-code = 0
-expect.result = "Failed to parse address"
-
-[[test.example-custom-pickable]]
-command = "connect invalid"
-expect.exit-code = 0
-expect.result = "Failed to parse address"
-
-[[test.example-dispatch-tree]]
-command = "cmd5"
-expect.exit-code = 0
-expect.result = "It's works!"
-
-[[test.example-enum-tag]]
-command = "lang-select"
-expect.exit-code = 0
-expect.result = "Selected: Rust"
-
-[[test.example-enum-tag]]
-command = "lang-select Python"
-expect.exit-code = 0
-expect.result = "Selected: Python"
-
-[[test.example-enum-tag]]
-command = "lang-select OCaml"
-expect.exit-code = 0
-expect.result = "Selected: OCaml"
-
-[[test.example-structural-renderer]]
-command = "render Bob 22"
-expect.exit-code = 0
-expect.result = "Bob is 22 years old"
-
-[[test.example-structural-renderer]]
-command = "render Bob 22 --json"
-expect.exit-code = 0
-expect.result = "{\"member_name\":\"Bob\",\"member_age\":22}"
-
-[[test.example-help]]
-command = "greet"
-expect.exit-code = 0
-expect.result = ""
-
-[[test.example-help]]
-command = "greet --help"
-expect.exit-code = 0
-expect.result = "Usage: greet <NAME>"
-
-[[test.example-hook]]
-command = "greet"
-expect.exit-code = 0
-expect.result = "Hello, World!"
-
-[[test.example-hook]]
-command = "greet Alice"
-expect.exit-code = 0
-expect.result = "Hello, Alice!"
-
-[[test.example-panic-unwind]]
-command = "panic"
-expect.exit-code = 0
-expect.result = "Program not panic"
-
-[[test.example-panic-unwind]]
-command = "panic something_went_wrong"
-expect.exit-code = 0
-expect.result = "Program panic: something_went_wrong"
-
-[[test.example-resources]]
-command = "current"
-expect.exit-code = 0
-expect.result = "Current directory:"
-
-[[test.example-setup]]
-command = "1"
-expect.exit-code = 0
-expect.result = ""
-
-[[test.example-setup]]
-command = "2"
-expect.exit-code = 0
-expect.result = ""
-
-[[test.example-setup]]
-command = "3"
-expect.exit-code = 0
-expect.result = ""
-
-[[test.example-setup]]
-command = "4"
-expect.exit-code = 0
-expect.result = ""
-
-[[test.example-setup]]
-command = "5"
-expect.exit-code = 0
-expect.result = ""
-
-[[test.example-completion]]
-command = "greet World --repeat 1"
-expect.exit-code = 0
-expect.result = "Hello, World!"
-
-[[test.example-clap-binding]]
-command = "greet"
-expect.exit-code = 0
-expect.result = "Hello, World!"
-
-[[test.example-clap-binding]]
-command = "greet Alice"
-expect.exit-code = 0
-expect.result = "Hello, Alice!"
-
-[[test.example-clap-binding]]
-command = "greet Alice -r 5"
-expect.exit-code = 0
-expect.result = "Hello, Alice, Alice, Alice, Alice, Alice!"
-
-[[test.example-pack-err]]
-command = "find"
-expect.exit-code = 0
-expect.result = "Search path not provided"
-
-[[test.example-pack-err]]
-command = "find Cargo.toml"
-expect.exit-code = 0
-expect.result = "Not a directory: Cargo.toml"
-
-[[test.example-pack-err]]
-command = "find examples"
-expect.exit-code = 0
-expect.result = "Found directory: examples"
-
-[[test.example-pack-err]]
-command = "find-structural --json"
-expect.exit-code = 0
-expect.result = '{"name":"error_not_found_structural"}'
-
-[[test.example-pack-err]]
-command = "find-structural Cargo.toml --json"
-expect.exit-code = 0
-expect.result = '{"name":"error_not_dir_structural","info":"Cargo.toml"}'
-
-[[test.example-pack-err]]
-command = "find-structural examples --json"
-expect.exit-code = 0
-expect.result = '{"inner":"examples"}'
-
-[[test.example-pathfinder]]
-command = "greet"
-expect.exit-code = 0
-expect.result = "Hello, World!"
-
-[[test.example-pathfinder]]
-command = "greet Alice"
-expect.exit-code = 0
-expect.result = "Hello, Alice!"
-
-[[test.example-combine-pathf-dispatch-tree]]
-command = "hello Alice"
-expect.exit-code = 0
-expect.result = "Hello, Alice!"
-
-[[test.example-combine-pathf-dispatch-tree]]
-command = "hello"
-expect.exit-code = 0
-expect.result = "Hello, World!"
-
-[[test.example-argument-picker]]
-command = "calc 1 + 1"
-expect.exit-code = 0
-expect.result = "Result: 2"
-
-[[test.example-argument-picker]]
-command = "calc 7 * 7"
-expect.exit-code = 0
-expect.result = "Result: 49"
-
-[[test.example-argument-picker]]
-command = "calc"
-expect.exit-code = 0
-expect.result = "Error: First number (number_a) was not provided."
-
-[[test.example-argument-picker]]
-command = "calc 1"
-expect.exit-code = 0
-expect.result = "Error: Operator was not provided."
-
-[[test.example-argument-picker]]
-command = "calc 1 +"
-expect.exit-code = 0
-expect.result = "Error: Second number (number_b) was not provided."
-
-[[test.example-argument-picker]]
-command = "calc 4 / 3"
-expect.exit-code = 0
-expect.result = "Result: 1.3333334"
-
-[[test.example-argument-picker]]
-command = "calc 4 / 3 --round"
-expect.exit-code = 0
-expect.result = "Result: 1"
-
-[[test.example-command-macro]]
-command = "hello world"
-expect.exit-code = 0
-expect.result = "Hello, World"
-
-[[test.example-command-macro]]
-command = "greet-someone Alice"
-expect.exit-code = 0
-expect.result = "Hello, Alice"
-
-[[test.example-command-macro]]
-command = "goodbye"
-expect.exit-code = 0
-expect.result = "Goodbye!"
-
-[[test.example-metadata]]
-command = "greet"
-expect.exit-code = 0
-expect.result = "Hello, World!"
-
-[[test.example-metadata]]
-command = "greet Alice"
-expect.exit-code = 0
-expect.result = "Hello, Alice!"
-
-[[test.example-metadata]]
-command = "desc"
-expect.exit-code = 0
-expect.result = "EntryGreet desc = ok"
-
-[[test.example-metadata]]
-command = "nodoc"
-expect.exit-code = 0
-expect.result = "EntryDescription has no description"
-
-[[test.example-combine-pathf-metadata]]
-command = "hello"
-expect.exit-code = 0
-expect.result = "Hello, World!"
-
-[[test.example-combine-pathf-metadata]]
-command = "hello Alice"
-expect.exit-code = 0
-expect.result = "Hello, Alice!"
-
-[[test.example-combine-pathf-metadata]]
-command = "desc"
-expect.exit-code = 0
-expect.result = "EntryHello desc = okay"