Snapshot testing for a herd of CLI tests

Related tags

Command-line trycmd
Overview

trycmd

Snapshot testing for a herd of CLI tests

Documentation License Crates Status

trycmd aims to simplify the process for running a large collection of end-to-end CLI test cases, taking inspiration from trybuild.

Example

Here's a trivial example:

#[test]
fn ui() {
    let t = trycmd::TestCases::new();
    t.pass("tests/cmd/*.toml");
}

See the docs for more.

Relevant crates

For testing command line programs.

  • assert_cmd for test cases that are individual pets, rather than herd of cattle
  • escargot for more control over configuring the crate's binary.
  • duct for orchestrating multiple processes.
  • rexpect for testing interactive programs.
  • assert_fs for filesystem fixtures and assertions.
    • or tempfile for scratchpad directories.
  • dir-diff for testing file side-effects.

For snapshot testing:

License

Licensed under either of

at your option.

Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.

Comments
  • Provide simple, concrete, contained example

    Provide simple, concrete, contained example

    See #145 for what that looks like. There are currently no concrete examples unless you count the "Users" section, but those are prod crates that are big and complicated.

    opened by SUPERCILEX 11
  • Writing more than 4096 bytes to stdout results in deadlock on Windows

    Writing more than 4096 bytes to stdout results in deadlock on Windows

    The company I work for is using trycmd and we noticed our CI timing out on Windows. After some investigation, it appears writing more than 4096 bytes to stdout will result in trycmd deadlocking on Windows. This is presumably related to #45572 in Rust (std::process::Command hangs if piped stdout buffer fills) and makes sense since Windows uses a much smaller buffer than Linux.

    A reproducible test case on Windows:

    // src/main.rs
    fn main() {
        // Change to 4096 and no deadlock appears
        for _ in 0..4097 {
            print!("a");
        }
    }
    

    A simple trycmd is all that it takes because we aren't looking for anything to actually pass or fail.

    // tests/bug.rs
    #[test]
    fn trycmd_bug() {
        let t = trycmd::TestCases::new();
        t.cases("tests/bug.md");
    }
    

    In bug.md:

    $ trycmd-bug
    a
    
    bug 
    opened by kbknapp 11
  • [EXE] not being removed

    [EXE] not being removed

    On windows, my tests are failing because [EXE] shows up: https://github.com/SUPERCILEX/ftzz/actions/runs/3161445260/jobs/5147037527. I thought it was supposed to be removed? In general, I don't understand variables. The docs are very unclear. Are they replacing input or output? Is the value a search string? Or is the var replaced with the value? Tried both and neither did anything. Examples are needed of what happens to the output or input when a variable is added.

    opened by SUPERCILEX 9
  • Snapbox 0.3.1 Breaking Change in `\` normalization

    Snapbox 0.3.1 Breaking Change in `\` normalization

    0.3.1 of Snapbox was a breaking change for me—as I'm using it to compare some generated TOML here. The \ in these cases are not path separators, so should not be normalized.

    If this is intentional, I can just replace my usage of assert_eq_path with a custom Assert that disables normalization—but I wanted to report the issue just in case it was unintentional.

    Thanks!

    bug breaking-change 
    opened by dbanty 6
  • Docs do a poor job of explain what this library does

    Docs do a poor job of explain what this library does

    It took me several hours to understand how this library worked... the docs need a lot of love.

    • In the readme and intro docs, a test is added with that looks through "tests/cmd/*.trycmd" but no example is shown of a trycmd so I had no idea these files actually needed to exist (I thought TRYCMD=dump would create them and was very confused for a while). An example of the trycmd file is needed.
    • It also needs to be mentioned that md files are interpreted the same way.
    • The example here makes no sense. It looks like that rust code will be compiled on the fly (it isn't) and where is my-cmd coming from? And how does the output make any sense? It says print hello world but then only hello is printed. This example needs to be completely reworked: it should clearly and explicitly be shown that you have a basic rust binary with a main function that prints something, an md/trycmd file that calls that binary (no need for input params), and a test that has the trycmd file as a case.
    • Eliding content needs an example because I still can't figure out what it does: https://github.com/assert-rs/trycmd/issues/140
    opened by SUPERCILEX 4
  • Trim whitespace in readme output

    Trim whitespace in readme output

    I'm not sure if it's ok to trim whitespace in all output (since it'd be nice to have a tailing newline in some stdout file), so I'm thinking only trailing lines in readmes should be trimmed.

    opened by SUPERCILEX 4
  • Take inspiration from cargo's substitutions?

    Take inspiration from cargo's substitutions?

    When debugging a test failure in cargo, I came across match_contains

    //! Routines for comparing and diffing output.
    //!
    //! # Patterns
    //!
    //! Many of these functions support special markup to assist with comparing
    //! text that may vary or is otherwise uninteresting for the test at hand. The
    //! supported patterns are:
    //!
    //! - `[..]` is a wildcard that matches 0 or more characters on the same line
    //!   (similar to `.*` in a regex). It is non-greedy.
    //! - `[EXE]` optionally adds `.exe` on Windows (empty string on other
    //!   platforms).
    //! - `[ROOT]` is the path to the test directory's root.
    //! - `[CWD]` is the working directory of the process that was run.
    //! - There is a wide range of substitutions (such as `[COMPILING]` or
    //!   `[WARNING]`) to match cargo's "status" output and allows you to ignore
    //!   the alignment. See the source of `substitute_macros` for a complete list
    //!   of substitutions.
    //!
    //! # Normalization
    //!
    //! In addition to the patterns described above, the strings are normalized
    //! in such a way to avoid unwanted differences. The normalizations are:
    //!
    //! - Raw tab characters are converted to the string `<tab>`. This is helpful
    //!   so that raw tabs do not need to be written in the expected string, and
    //!   to avoid confusion of tabs vs spaces.
    //! - Backslashes are converted to forward slashes to deal with Windows paths.
    //!   This helps so that all tests can be written assuming forward slashes.
    //!   Other heuristics are applied to try to ensure Windows-style paths aren't
    //!   a problem.
    //! - Carriage returns are removed, which can help when running on Windows.
    

    crates/cargo-test-support/src/compare.rs

    question 
    opened by epage 4
  • Unexpected Failure When Replacing Variable

    Unexpected Failure When Replacing Variable

    Summary

    While trying to use insert_var to replace data for cargo --help's output, in order to add UI testing to cross for our integrationg tests, I noticed that I was unable to use insert_var to actually replace data for very specific corner cases. Here's a sample project reproducing the failing test case. The minimum, failing example test string is:

    const DATA: &str = r#"'
    :"#;
    

    Detailed Information

    The test case uses:

    $ trycmd-test
    [REPLACE]
    
    

    And the test script is:

    const DATA: &str = r#"'
    :"#;
    
    #[test]
    fn cli_tests() {
        trycmd::TestCases::new()
            .case("tests/cmd/failed-replacement.md")
            .insert_var("[REPLACE]", DATA)
            .unwrap();
    }
    

    Where main.rs is:

    fn main() {
        println!("{DATA}");
    }
    
    const DATA: &str = r#"'
    :"#;
    
    

    It will fail with the following output:

    running 1 test
    Testing tests/cmd/failed-replacement.md:2 ... failed
    Exit: success
    
    ---- expected: stdout
    ++++ actual:   stdout
       1      - [REPLACE]
            1 + '
            2 + :
    stderr:
    
    Update snapshots with `TRYCMD=overwrite`
    Debug output with `TRYCMD=dump`
    test cli_tests ... FAILED
    
    

    Note that all of the following actually work:

    const EX1: &str = r#"':"#;
    const EX2: &str = r#"X's USAGE:"#;
    

    It seems to require the following regex pattern: '.*\n.*:. If the newline is not present, everything works as expected.

    Original Use

    The original failing output, and what we were originally trying to match, is as follows:

    Rust's package manager
    
    USAGE:
        cargo [+toolchain] [OPTIONS] [SUBCOMMAND]
    
    OPTIONS:
        -V, --version               Print version info and exit
            --list                  List installed commands
            --explain <CODE>        Run `rustc --explain CODE`
        -v, --verbose               Use verbose output (-vv very verbose/build.rs output)
        -q, --quiet                 Do not print cargo log messages
            --color <WHEN>          Coloring: auto, always, never
            --frozen                Require Cargo.lock and cache are up to date
            --locked                Require Cargo.lock is up to date
            --offline               Run without accessing the network
            --config <KEY=VALUE>    Override a configuration value (unstable)
        -Z <FLAG>                   Unstable (nightly-only) flags to Cargo, see 'cargo -Z help' for
                                    details
        -h, --help                  Print help information
    
    Some common cargo commands are (see all commands with --list):
        build, b    Compile the current package
        check, c    Analyze the current package and report errors, but don't build object files
        clean       Remove the target directory
        doc, d      Build this package's and its dependencies' documentation
        new         Create a new cargo package
        init        Create a new cargo package in an existing directory
        add         Add dependencies to a manifest file
        run, r      Run a binary or example of the local package
        test, t     Run the tests
        bench       Run the benchmarks
        update      Update dependencies listed in Cargo.lock
        search      Search registry for crates
        publish     Package and upload this package to the registry
        install     Install a Rust binary. Default location is $HOME/.cargo/bin
        uninstall   Uninstall a Rust binary
    
    See 'cargo help <command>' for more information on a specific command.
    
    
    
    A-trycmd A-snapbox 
    opened by Alexhuszagh 3
  • Revamp intro docs

    Revamp intro docs

    Closes #141. Content elision still needs work, but I'll leave that to #140.

    I think it's now clear how to use the library. I unfortunately couldn't figure out how to include the simple.md file into the README which means their contents are duplicated. Very annoying but oh well.

    opened by SUPERCILEX 3
  • Can't figure out how to get tests working

    Can't figure out how to get tests working

    I added the lib and this test:

    #[test]
    fn readme_test() {
        trycmd::TestCases::new().case("README.md");
    }
    

    Running the test gives Testing README.md ... ignored with no explanation of why. Here's the repo in question: https://github.com/SUPERCILEX/ftzz

    opened by SUPERCILEX 2
  • Full example of snapshot-testing a CLI binary?

    Full example of snapshot-testing a CLI binary?

    I'm brand new to trycmd, and would love to use it to snapshot test various combinations of args passed to a binary I'm building. However, despite the reputation of trycmd doing exactly this, I can't find a single example here of how to do that. Reading the example provided in examples/, there seems to be no binary involved. Even though I'll probably figure this out by looking at how various opensource projects use it on GitHub, I think providing an end-to-end working example of a binary that takes an argument, accepts input from stdin, and writes output to stdout, being snapshot tested end to end, would help a lot of others onboard to the project.

    A potential example program might be a simple CLI app that reads stdin, reverses each line, and spits out the reversed lines to stdout.

    opened by willbuckner 2
  • Piping from `echo` to provide standard input

    Piping from `echo` to provide standard input

    I'd like to be able to show examples in my documentation that use standard input. My usual way to document this is something like:

    $ echo "some simple input" | mycommand -
    the output
    

    ... so that users can copy-paste it and get the same result (and then modify as needed). It would be great to be able to test that these examples work using trycmd.

    Moved from https://github.com/assert-rs/trycmd/issues/168#issuecomment-1323763887

    enhancement A-trycmd 
    opened by jpmckinney 0
  • Support for test cases in documentation comments

    Support for test cases in documentation comments

    I use clap, and I put examples in the documentation comments for my commands/subcommands like:

    enum Commands {
        /// Counts from START to STOP.
        ///
        /// $ mycli counter 1 3
        /// 1
        /// 2
        /// 3
        Counter {
            start: usize,
            stop: usize,
        }
    }
    

    From discussion in https://github.com/clap-rs/clap/discussions/4500

    One alternative is to pull a markdown file into your doc comment

    e.g. #[doc = include_str!("../../README.md")]

    enhancement A-trycmd 
    opened by jpmckinney 0
  • Support using output of one command to match output of next

    Support using output of one command to match output of next

    I was wondering if there was any functionality that would allow using command output to bind a variable, such that it could be used to test a subsequent command? For example, given a command to generate some key and list generated keys:

    Generate two keys:

    $ gen-key
    yD0zydG611bl6RzwLutdnGd7zfgj6fdNDDreI786
    $ gen-key
    0zydG611bl6RzwLutdnGd7zfgj6fdNDDreI786UH
    

    Then list all your keys:

    $ list-keys
    yD0zydG611bl6RzwLutdnGd7zfgj6fdNDDreI786
    0zydG611bl6RzwLutdnGd7zfgj6fdNDDreI786UH
    

    One way I imagine this could work is if we could bind variables in the output, for example:

    $ gen-key
    [KEY1]
    $ gen-key
    [KEY2]
    $ list-keys
    [KEY1]
    [KEY2]
    

    The first [KEY1] and [KEY2] would bind new variables. The second [KEY1] and [KEY2] (in list-keys) would expect the value to equal the bound variables.

    Without any way to use command output in the test, it requires the tests to be implemented partly in rust and partly in .md files, which is cumbersome, whenever there is output that can't be known in advance.

    Thoughts? I'm happy to help implement something like this if it makes sense for this project.

    enhancement A-trycmd 
    opened by cloudhead 6
  • Running

    Running "standard" unix commands

    I'm testing a tool that requires for example creating a git repository and cd'ing into it, something like:

    mkdir foo
    cd foo
    git init .
    my-cmd
    ...
    

    It seems like it's possible to "register" some of these commands using register_bin, but some commands like cd are actual shell built-ins. What is the correct way to write this test in such a way that it can be read as realistic example as well?

    enhancement A-trycmd 
    opened by cloudhead 9
  • Handle extended path length syntax in automatic [CWD] detection

    Handle extended path length syntax in automatic [CWD] detection

    Windows has the extended path length syntax to work around legacy file path restrictions. These paths start with the string \\?\ followed by a normal path; more details can be found here. The problem is that Rust's std::fs::canonicalize creates these kinds of paths on Windows (which makes sense given that paths over 260 characters are otherwise disallowed), but trycmd can't handle them. If my binary under test's output, on Windows, includes something like:

    \\?\C:\Path\To\Working\Directory\Some\File.txt
    

    trycmd will transform this into:

    //?/[CWD]/Some/File.txt
    

    However, on Posix-like systems, my binary's output looks like this:

    /Path/To/Working/Directory/Some/File.txt
    

    as the behaviour of fs::canonicalize is different (on all systems, canonicalization is handled by the operating system, in the case of Posix, via realpath). Therefore, trycmd "compresses" that into

    [CWD]/Some/File.txt
    

    Depending on whether the test was blessed on Windows or a Posix-like system, the tests will therefore fail on the other family of systems.

    A workaround is of course to discard extended length path syntax in my binary before printing the path or file name, but as stated above that can lead to other problems. I'd like trycmd to be able to handle this; i.e. collapse the leading extended path syntax specifier into [CWD] if it is found before the normal working directory path.

    opened by kleinesfilmroellchen 5
  • Sorting output for tests

    Sorting output for tests

    I have a program that produces a number of lines of output that do not need to be in any particular order. I suppose I could add an option to my program to sort output and then use that in my tests, but in my case, that would require quite a bit of restructuring. It would be nice if there was a way to have trycmd sort lines of output before comparing.

    enhancement A-trycmd A-snapbox 
    opened by michaelmior 1
An educational Bochs-based snapshot fuzzer project

Lucid An educational Bochs-based snapshot fuzzer project Misc Bochs: https://bochs.sourceforge.io/ Blog: https://h0mbre.github.io/New_Fuzzer_Project/#

null 41 Nov 12, 2023
Black-box integration tests for your REST API using the Rust and its test framework

restest Black-box integration test for REST APIs in Rust. This crate provides the [assert_api] macro that allows to declaratively test, given a certai

IOmentum 10 Nov 23, 2022
Assure that your tests are there, and well written.

cargo-is-tested [ ???? ] El libro contiene instrucciones e información detallada en Español. cargo-is-tested is a way to check which of your items are

Alex ✨ Cosmic Princess ✨ 15 Jan 8, 2023
A simple, opinionated way to run containers for tests in your Rust project.

rustainers rustainers is a simple, opinionated way to run containers for tests. TLDR More information about this crate can be found in the crate docum

wefox 4 Nov 23, 2023
Server load testing CLI tool 🏋️

?? Rhea A Server Load Testing Tool Rhea is a powerful and easy-to-use command-line tool written in Rust for load testing servers. It allows you to sim

Melih Sivri 11 Aug 7, 2023
A dead simple functional testing tool for command line applications

Pharaoh : build that test pyramid! What it is Pharaoh is a dead simple, no permission needed, functional test runner for command line applications, wr

Kevin Sztern 17 Dec 13, 2021
A simple program for C program IO testing. Written in Rust

A simple program for C program IO testing. Written in Rust, using concurrency to speed up valgrind testing. Make sure to update settings at your first run of the program!

null 1 Feb 22, 2022
Cucumber testing framework for Rust. Fully native, no external test runners or dependencies.

Cucumber testing framework for Rust An implementation of the Cucumber testing framework for Rust. Fully native, no external test runners or dependenci

Cucumber Rust 393 Dec 31, 2022
Croc-look is a tool to make testing and debuging proc macros easier

croc-look croc-look is a tool to make testing and debuging proc macros easier by these two features Printing the implementation specific generated cod

weegee 7 Dec 2, 2022
Rust library that provides helpers for testing resilience of I/O operations.

partial-io Helpers for testing I/O behavior with partial, interrupted and blocking reads and writes. This library provides: PartialRead and PartialWri

null 31 Oct 26, 2022
A workflow tool for quickly running / testing something you are working on

runfast What is it? This is a program intended to be run in a project directory to set up a project run command, and remember it so we dont have to ty

anna 4 Dec 16, 2022
Testing out if Rust can be used for a normal Data Engineering Pipeline.

RustForDataPipelines Testing out if Rust can be used for a normal Data Engineering Pipeline. Check out the full blog post here. https://www.confession

Daniel B 7 Feb 17, 2023
A library and binary for testing unhooking ntdll by identifying hooks via in-memory disassembly

(First Public?) Sample of unhooking ntdll (All Exports & IAT imports) hooks in Rust using in-memory disassembly, avoiding direct syscalls and all hooked functions (incl. hooked NtProtectVirtualMemory)

Signal Labs 52 Apr 9, 2023
botwork is a single-binary, generic and open-source automation framework written in Rust for acceptance testing & RPA

botwork botwork is a single-binary, generic and open-source automation framework written in Rust for acceptance testing, acceptance test driven develo

Nitimis 8 Apr 17, 2023
Visual regression testing of H264 frames and images.

twenty-twenty The twenty-twenty library allows for visual regression testing of H.264 frames and images. It makes it easy to update the contents when

KittyCAD 4 Jul 16, 2023
Api testing tool made with rust to use for api developement (Kind of Tui)

Api testing tool made with rust to use for api developement (Kind of Tui) This Rust project provides a simple yet powerful tool for making HTTP reques

Kythonlk 3 Feb 14, 2024
A CLI tool to get help with CLI tools 🐙

A CLI tool to get help with CLI tools ?? halp aims to help find the correct arguments for command-line tools by checking the predefined list of common

Orhun Parmaksız 566 Apr 16, 2023
Quickly build cool CLI apps in Rust.

QuiCLI Quickly build cool CLI apps in Rust. Getting started Read the Getting Started guide! Thanks This is only possible because of all the awesome li

Pascal Hertleif 538 Dec 5, 2022
A minimal CLI framework written in Rust

seahorse A minimal CLI framework written in Rust Features Easy to use No dependencies Typed flags(Bool, String, Int, Float) Documentation Here Usage T

Keisuke Toyota 223 Dec 30, 2022