assert-rs / predicates-rs

Boolean-valued predicate functions in Rust
docs.rs/predicates
Apache License 2.0
173 stars 29 forks source link

Predicates for string line count #103

Closed Narann closed 3 years ago

Narann commented 3 years ago

Hello,

First, thanks for your hard work on this crate, I use it with assert_cmd and it's easy to both read and use. :)

I test every line, of my output, but I want to be sure I didn't miss anything.

Is there any hope to see a predicate for line count ?

Like a predicate::str::line_count(5) to check the output line count is 5 ?

Hope this feature make sense and will brings some interest.

Keep the good work!

epage commented 3 years ago

Something like this could be added but I'd like to better understand the use case. In what situation do you know the number of lines but only check a subset of the content?

Narann commented 3 years ago

Hi and thanks for your answer. Here is the use case:

I have a tool that list header information:

#[test]
fn cmd_header_file_nuke_16_v9_f16_uncomp_1part() -> Result<(), Box<dyn std::error::Error>> {
    let mut cmd = Command::cargo_bin(BIN_NAME)?;

    cmd.arg("header")
        .arg("tests/images/nuke_16/v9.0/f16_uncomp_1part.exr")
        .assert()
        .success()
        .stdout(
            predicate::str::contains("layer count: 1")
                .and(predicate::str::contains("layer #0"))
                .and(predicate::str::contains("compression: no compression"))
                .and(predicate::str::contains("block: scanline"))
                .and(predicate::str::contains("line_order: increasing"))
                .and(predicate::str::contains("layer_size (v2u): (16, 16)"))
                .and(predicate::str::contains(
                    "display_window (box2i): position: (0, 0), size: 16x16",
                ))
                .and(predicate::str::contains("pixel_aspect_ratio (float): 1"))
                .and(predicate::str::contains("layer_position (v2i): (0, 0)"))
                .and(predicate::str::contains(
                    "screen_window_center (v2f): (0, 0)",
                ))
                .and(predicate::str::contains("screen_window_width (float): 1"))
                .and(predicate::str::contains(
                    "nuke/node_hash (string): \"b8af9cf2c951eddb\"",
                ))
                .and(predicate::str::contains(
                    "nuke/foo/bar (string): \"my_value\"",
                ))
                .and(predicate::str::contains("A, f16, sampling (1, 1)"))
                .and(predicate::str::contains("B, f16, sampling (1, 1)"))
                .and(predicate::str::contains("G, f16, sampling (1, 1)"))
                .and(predicate::str::contains("R, f16, sampling (1, 1)"))
                .and(predicate::str::contains("foo.alpha, f16, sampling (1, 1)"))
                .and(predicate::str::contains("foo.blue, f16, sampling (1, 1)"))
                .and(predicate::str::contains("foo.green, f16, sampling (1, 1)"))
                .and(predicate::str::contains("foo.red, f16, sampling (1, 1)")),
        );

    Ok(())
}

As you can see, I add my checks line by line as it's easier to comment to find debug/find if there are regression and which information have a problem.

The problem is, if one or more lines appears in my output, test will pass. It's something I want to avoid and that's the reason why I try to find a kinda line_count() predicate.

So you are technically right: I could simply check the whole output. But doing so, I would spend some time to find which line have a regression.

What I like with predicate its how tests are both easy to write and manipulate so I thought it would make sense to have line_count().

Hope this makes sense.

Keep the good work! :)

PS: Any workaround is welcome.

epage commented 3 years ago

So you are technically right: I could simply check the whole output. But doing so, I would spend some time to find which line have a regression.

Would predicates::str::similar help? Unlike an eq predicate, this will diff the content, highlighting which parts are different. You can configure it for whether the atoms are characters, words, or lines. We are looking at tweaking the API for what you select as the atom / split (see #102).

If that doesn't work, we can look into the line count. Another idea I'm playing with is where we support splitting the lines and you compare against an array of predicates, so something like

    cmd.arg("header")
        .arg("tests/images/nuke_16/v9.0/f16_uncomp_1part.exr")
        .assert()
        .success()
        .stdout(
            eq([
                predicate::str::contains("layer count: 1"),
                predicate::str::contains("layer #0"),
                predicate::str::contains("compression: no compression"),
            ]).lines()
        )

The API isn't feeling as clean (unsure on how to properly express it so the intent is clear and the API is discoverable).

Narann commented 3 years ago

Thanks for predicates::str::similar, I will try it. :)

The second example lost looks less explicit than, for example:

predicate::str::lines_contains(["my line", "my other line"])

But I'm not experienced enough with rust to have strong opinion and maybe what you write make more sense. rust community seems to encourage highly nested functional style. Nothing wrong with that, I'm just not used to it.

I don' want to take you precious time anymore. I will try with:

predicate::str::is_match("\n").unwrap().count(3)

Seems to be what I want.

Keep the good work and have a nice day! :)

Narann commented 3 years ago

I actually have a situation where the output count (line count) is known, but the order is not, this means predicates::str::similar almost always fail.

epage commented 3 years ago

Thats too bad it doesn't work for your case but I like the is_match approach. I think using that more flexible tool might be better than directly baking this in but feel free to suggest otherwise!