diff --git a/tests/examples.rs b/tests/examples.rs new file mode 100644 index 0000000..58f4375 --- /dev/null +++ b/tests/examples.rs @@ -0,0 +1,136 @@ +//! Tests the kdl files in the examples directory. + +use kdl::*; +use std::collections::HashMap; + +/// Helper for constructing nodes. +/// +/// This takes input that's similar to KDL itself, but each node must be terminated with either +/// a semicolon or a braced block. Nodes whose name contains characters not valid in Rust +/// identifiers must be written as a string literal instead. +macro_rules! nodes { + ([$v:ident]:name) => {}; + ([$v:ident]:name $name:ident $($tt:tt)*) => { + nodes!([$v]:values stringify!($name); {} {} $($tt)*) + }; + ([$v:ident]:name $name:literal $($tt:tt)*) => { + nodes!([$v]:values $name; {} {} $($tt)*) + }; + ([$v:ident]:values $name:expr; {$($value:literal,)*} $props:tt $new_value:literal $($tt:tt)*) => { + nodes!([$v]:values $name; {$($value,)* $new_value,} $props $($tt)*) + }; + ([$v:ident]:values $name:expr; $values:tt {$($key:ident=$prop:literal,)*} $new_key:ident=$new_prop:literal $($tt:tt)*) => { + nodes!([$v]:values $name; $values {$($key=$prop,)* $new_key=$new_prop,} $($tt)*) + }; + ([$v:ident]:values $name:expr; $values:tt $props:tt $(; $($tt:tt)*)?) => { + nodes!([$v]:values $name; $values $props {} $($($tt)*)?) + }; + ([$v:ident]:values $name:expr; {$($value:literal,)*} {$($key:ident=$prop:literal,)*} {$($child:tt)*} $($tail:tt)*) => { + $v.push(KdlNode { + name: $name.to_owned(), + values: vec![$( $value.to_owned().into() ),*], + properties: { + #[allow(unused_mut)] + let mut map = HashMap::new(); + $( + map.insert(stringify!($key).to_owned(), $prop.to_owned().into()); + )* + map + }, + children: nodes!($($child)*), + }); + nodes!([$v]:name $($tail)*); + }; + // Explicitly match literal and ident at the start instead of $($tt:tt)* + // so we get better errors than "recursion limit exceeded" if we fail to match. + (:start $($tt:tt)+) => {{ + let mut v = Vec::new(); + nodes!([v]:name $($tt)+); + v + }}; + ($name:literal $($tt:tt)*) => { + nodes!(:start $name $($tt)*) + }; + ($name:ident $($tt:tt)*) => { + nodes!(:start $name $($tt)*) + }; + () => { vec![] } +} + +#[test] +fn test_ci() { + let doc = parse_document(include_str!("../examples/ci.kdl")); + let nodes = nodes! { + name "CI"; + on "push" "pull_request"; + env { + RUSTFLAGS "-Dwarnings" + } + jobs { + fmt_and_docs "Check fmt & build docs" { + "runs-on" "ubuntu-latest"; + steps { + step uses="actions/checkout@v1"; + step "Install Rust" uses="actions-rs/toolchain@v1" { + profile "minimal"; + toolchain "stable"; + components "rustfmt"; + override true; + } + step "rustfmt" run="cargo fmt --all -- --check"; + step "docs" run="cargo doc --no-deps"; + } + } + build_and_test "Build & Test" { + "runs-on" "${{ matrix.os }}"; + strategy { + matrix { + rust "1.46.0" "stable"; + os "ubuntu-latest" "macOS-latest" "windows-latest"; + } + } + + steps { + step uses="actions/checkout@v1"; + step "Install Rust" uses="actions-rs/toolchain@v1" { + profile "minimal"; + toolchain "${{ matrix.rust }}"; + components "clippy"; + override true; + } + step "Clippy" run="cargo clippy --all -- -D warnings"; + step "Run tests" run="cargo test --all --verbose"; + } + } + } + }; + assert_eq!(doc, Ok(nodes)); +} + +#[test] +fn test_cargo() { + let doc = parse_document(include_str!("../examples/cargo.kdl")); + let nodes = nodes! { + package { + name "kdl"; + version "0.0.0"; + description "kat's document language"; + authors "Kat Marchán "; + "license-file" "LICENSE.md"; + edition "2018"; + } + dependencies { + nom "6.0.1"; + thiserror "1.0.22"; + } + }; + assert_eq!(doc, Ok(nodes)); +} + +#[test] +fn test_nuget() { + let doc = parse_document(include_str!("../examples/nuget.kdl")); + // This file is particularly large. It would be nice to validate it, but for now + // I'm just going to settle for making sure it parses. + doc.expect("Parsing failed"); +}