Skip to content

Instantly share code, notes, and snippets.

@colin-kiegel
Last active March 22, 2017 23:33
Show Gist options
  • Save colin-kiegel/0adc17e841088aa887d0536daeec9194 to your computer and use it in GitHub Desktop.
Save colin-kiegel/0adc17e841088aa887d0536daeec9194 to your computer and use it in GitHub Desktop.
thoughts about assert_cli ... :-)
/// Warning: Pseudo-Rust Code
/// Some rough ideas how `assert_cli` could accept arbitrary assertions and possibly
/// support interactive commands.
///
/// The current simple API could be implemented on top of this more general one.
Cmd::run("")
.with_timeout(Duration(...))
.assert(StdOut::ends_with("Are you sure?")) // ::starts_with, ::eq, ::ne, ::contains
.assert(StdIn::from("n\n")) // I don't know if asserting this makes sense ^^'
.assert(ExitCode::eq(1)) // ExitCode::in(1..100)
.unwrap(); // could panic with `assertion 3 of 3 failed ... <REASON> ...
// previous assertions: ...`
// all assertions above will be `and`-related (until we introduce the `or`-operator ^^)
struct Cmd {
// ...
}
impl Cmd {
pub fn assert<A: Assertion>(self, assertion: A) -> Self {
if let Err(e) = assertion.assert(&mut self) {
self.push_error(e);
}
self
}
fn push_error(&mut self, _) -> ();
fn unwrap() {
// iterate over all assertions
}
}
// We need some public methods on `Cmd` to be accessible by assertions,
// but it shouldn't clutter the public `Cmd` API -> use a trait. :-)
//
// Public interface used by Assertions!
trait CommandObserver {
fn stdout(&self) -> &str;
fn stderr(&self) -> &str;
fn stdin(&mut self, &str) -> Result<_, _>;
fn exit_code(&self) -> Some(ExitCode);
fn duration(&self) -> Duration;
fn flush(&mut self) -> (); // reset stdout / stderr
// fn kill(&mut self) -> (); // e.g. if it is waiting for input
}
trait Assertion<T> {
// assertions may want to manipulate `T` (as in 'cmd accepts `foo` via stdin')
fn assert(&self, x: &mut T) -> Result<_, _>;
}
type CommandAssertion = Assertion<Command>;
type StringAssertion = Assertion<str>;
struct StdOut<T: StringAssertion>(T);
impl<T> CommandAssertion for StdOut<T: StringAssertion> {
fn assert(&self, c: &Command) -> Result<(), _> {
// trait `CommandObserver` must be in scope for `std_out` - or use UFCS
self.0.assert(c.std_out()) // possibly enrich the error!
}
}
impl StdOut {
fn eq(x: &str) -> StdOut<Eq<&str>> {
StdOut(Eq(x))
}
// fn ne, contains, starts_with, ends_with etc.
// at least `contains` should be generic over std::str::pattern::Pattern
}
struct Eq<T: PartialEq>(T);
impl<T: PartialEq> StringAssertion for Eq<T> {
fn assert(&self, x: &str) -> Result<(), _> {
if self.0 == x {
Ok(())
} else {
Err(...)
}
}
}

sorry, this memo is in pretty bad shape ..

generalize prints over std::str::pattern::Pattern (like in String::contains) https://doc.rust-lang.org/std/str/pattern/trait.Pattern.html

Currently the Pattern is only implemented for String and str and is not yet stabilized (#27721) for 3rd party implementations. But once it is stabilized, you would have regex support and the like for free. :-)

I'm not sure if Rust would accept

fn prints<T, O>(self, output: O) -> Self where
    O: Into<T>,
    T: Pattern

That would be ambiguous in cases where a type X implements Into<A> and Into<B>, where A: Pattern, B: Pattern. You would then probably need to disambiguate prints(x as Into<A>) or prints::<A, _>(x). Even if Rust accepted it, it would introduce some forward compatibility hazards, where adding impls X: Into<B> and B: Pattern would make code in other places suddenly be ambiguous.

So my conclusion is, it should be either prints<O: Into<String>> or prints<O: Pattern>.

In the spirit of the above patterns, might be assertions like this:

  • print_starts_with
  • print_ends_with
@colin-kiegel
Copy link
Author

colin-kiegel commented Mar 22, 2017

@killercup: sorry about the memos being in bad shape - just tell me if you have any questions. And I'll try to make sense of them again myself. :-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment