diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..1924ad3 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,44 @@ +name: CI Build + +on: [push, pull_request] + +jobs: + build: + name: Build and Test + runs-on: ${{ matrix.os }} + # We want to run on external PRs, but not on internal ones as push automatically builds + # H/T: https://github.com/Dart-Code/Dart-Code/commit/612732d5879730608baa9622bf7f5e5b7b51ae65 + if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != 'amzn/ion-cli' + strategy: + matrix: + # TODO: add windows after fixing cmake version error issues + # see for example Windows build workflow: https://github.com/amzn/ion-rust/blob/a4b154cc0a5b5b661a45ac14d3719a501573d8f2/.github/workflows/rust.yml#L13-L28 + os: [ubuntu-latest, macos-latest] + + steps: + - name: Git Checkout + uses: actions/checkout@v2 + with: + submodules: recursive + - name: Rust Toolchain + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + - name: Cargo Build + uses: actions-rs/cargo@v1 + with: + command: build + args: --verbose --workspace + - name: Cargo Test + uses: actions-rs/cargo@v1 + with: + command: test + args: --verbose --workspace -- --test-threads=1 + # TODO: run rustfmt and enable check + # - name: Rustfmt Check + # uses: actions-rs/cargo@v1 + # with: + # command: fmt + # args: --verbose -- --check diff --git a/Cargo.lock b/Cargo.lock index b92f146..6161174 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -35,6 +35,20 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" +[[package]] +name = "assert_cmd" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c98233c6673d8601ab23e77eb38f999c51100d46c5703b17288c57fddf3a1ffe" +dependencies = [ + "bstr", + "doc-comment", + "predicates", + "predicates-core", + "predicates-tree", + "wait-timeout", +] + [[package]] name = "atty" version = "0.2.14" @@ -117,6 +131,17 @@ dependencies = [ "wyz", ] +[[package]] +name = "bstr" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" +dependencies = [ + "lazy_static", + "memchr", + "regex-automata", +] + [[package]] name = "byteorder" version = "1.4.2" @@ -230,6 +255,24 @@ dependencies = [ "syn", ] +[[package]] +name = "difflib" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" + +[[package]] +name = "doc-comment" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" + +[[package]] +name = "either" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" + [[package]] name = "env_logger" version = "0.7.1" @@ -303,6 +346,7 @@ name = "ion-cli" version = "0.2.0" dependencies = [ "anyhow", + "assert_cmd", "clap", "cmake", "colored", @@ -310,6 +354,7 @@ dependencies = [ "ion-schema", "libc", "memmap", + "rstest", "tempfile", ] @@ -373,6 +418,15 @@ dependencies = [ "libc", ] +[[package]] +name = "itertools" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" +dependencies = [ + "either", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -510,12 +564,48 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" +[[package]] +name = "pest" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53" +dependencies = [ + "ucd-trie", +] + [[package]] name = "ppv-lite86" version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" +[[package]] +name = "predicates" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5aab5be6e4732b473071984b3164dbbfb7a3674d30ea5ff44410b6bcd960c3c" +dependencies = [ + "difflib", + "itertools", + "predicates-core", +] + +[[package]] +name = "predicates-core" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da1c2388b1513e1b605fcec39a95e0a9e8ef088f71443ef37099fa9ae6673fcb" + +[[package]] +name = "predicates-tree" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d86de6de25020a36c6d3643a86d9a6a9f552107c0559c60ea03551b5e16c032" +dependencies = [ + "predicates-core", + "termtree", +] + [[package]] name = "proc-macro2" version = "1.0.32" @@ -607,6 +697,12 @@ dependencies = [ "thread_local", ] +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" + [[package]] name = "regex-syntax" version = "0.6.22" @@ -622,18 +718,58 @@ dependencies = [ "winapi", ] +[[package]] +name = "rstest" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "041bb0202c14f6a158bbbf086afb03d0c6e975c2dec7d4912f8061ed44f290af" +dependencies = [ + "cfg-if 1.0.0", + "proc-macro2", + "quote", + "rustc_version", + "syn", +] + [[package]] name = "rustc-hash" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc_version" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee" +dependencies = [ + "semver", +] + [[package]] name = "ryu" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" +[[package]] +name = "semver" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver-parser" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7" +dependencies = [ + "pest", +] + [[package]] name = "shlex" version = "0.1.1" @@ -692,6 +828,12 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "termtree" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "507e9898683b6c43a9aa55b64259b721b52ba226e0f3779137e50ad114a4c90b" + [[package]] name = "textwrap" version = "0.9.0" @@ -740,6 +882,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "ucd-trie" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" + [[package]] name = "unicode-width" version = "0.1.8" @@ -764,6 +912,15 @@ version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" +[[package]] +name = "wait-timeout" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" +dependencies = [ + "libc", +] + [[package]] name = "wasi" version = "0.10.2+wasi-snapshot-preview1" diff --git a/Cargo.toml b/Cargo.toml index ce75b3f..9b8dbad 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,7 +24,12 @@ ion-schema = "0.1.0" [build-dependencies] cmake = "0.1.44" +[dev-dependencies] +rstest = "~0.10.0" +assert_cmd = "~1.0.5" +tempfile = "~3.2.0" + [[bin]] name = "ion" -test = false +test = true bench = false diff --git a/tests/cli.rs b/tests/cli.rs new file mode 100644 index 0000000..f15e3c1 --- /dev/null +++ b/tests/cli.rs @@ -0,0 +1,146 @@ +use anyhow::Result; +use assert_cmd::Command; +use ion_rs::value::owned::OwnedElement; +use ion_rs::value::reader::*; +use rstest::*; +use std::fs::File; +use std::io::{Read, Write}; +use std::time::Duration; +use tempfile::TempDir; + +enum FileMode { + /// Use `STDIN` or `STDOUT` + Default, + /// Use a named file + Named, +} + +struct TestCase> { + /// The text of the ion payload to test + ion_text: S, + /// The expected Ion + expected_ion: OwnedElement, +} + +impl From<(&'static str, &'static str)> for TestCase<&'static str> { + /// Simple conversion for static `str` slices into a test case + fn from((ion_text, expected_ion): (&'static str, &'static str)) -> Self { + let expected_ion = element_reader().read_one(expected_ion.as_bytes()).unwrap(); + Self { + ion_text, + expected_ion, + } + } +} + +#[rstest] +#[case::simple(( +r#" +{ + name: "Fido", + + age: years::4, + + birthday: 2012-03-01T, + + toys: [ + ball, + rope, + ], + + weight: pounds::41.2, + + buzz: {{VG8gaW5maW5pdHkuLi4gYW5kIGJleW9uZCE=}}, +} +"#, +r#" +{ + name: "Fido", + + age: years::4, + + birthday: 2012-03-01T, + + toys: [ + ball, + rope, + ], + + weight: pounds::41.2, + + buzz: {{VG8gaW5maW5pdHkuLi4gYW5kIGJleW9uZCE=}}, +} +"# +).into())] +/// Calls the ion CLI binary dump command with a set of arguments the ion-cli is expected to support. +/// This does not verify specific formatting, only basic CLI behavior. +fn run_it>( + #[case] test_case: TestCase, + #[values("", "binary", "text", "pretty")] format_flag: &str, + #[values(FileMode::Default, FileMode::Named)] input_mode: FileMode, + #[values(FileMode::Default, FileMode::Named)] output_mode: FileMode, +) -> Result<()> { + let TestCase { + ion_text, + expected_ion, + } = test_case; + + let temp_dir = TempDir::new()?; + let input_path = temp_dir.path().join("INPUT.ion"); + let output_path = temp_dir.path().join("OUTPUT.ion"); + + let mut cmd = Command::cargo_bin("ion")?; + cmd.arg("dump").timeout(Duration::new(5, 0)); + if format_flag != "" { + cmd.arg("-f"); + cmd.arg(format_flag); + } + match output_mode { + FileMode::Default => { + // do nothing + } + FileMode::Named => { + // tell driver to output to a file + cmd.arg("-o"); + cmd.arg(&output_path); + } + }; + + match input_mode { + FileMode::Default => { + // do nothing + cmd.write_stdin(ion_text.as_ref()); + } + FileMode::Named => { + // dump our test data to input file + let mut input_file = File::create(&input_path)?; + input_file.write(ion_text.as_ref().as_bytes())?; + input_file.flush()?; + + // TODO: test multiple input files + + // make this the input for our driver + cmd.arg(input_path.to_str().unwrap()); + } + }; + + let assert = cmd.assert(); + + let actual_ion = match output_mode { + FileMode::Default => { + let output = assert.get_output(); + element_reader().read_one(&output.stdout)? + } + FileMode::Named => { + let mut output_file = File::open(output_path)?; + let mut output_buffer = vec![]; + output_file.read_to_end(&mut output_buffer)?; + element_reader().read_one(&output_buffer)? + } + }; + + assert_eq!(expected_ion, actual_ion); + assert.success(); + + Ok(()) +}