Skip to content

Commit

Permalink
Merge pull request #280 from redbadger/update-tests
Browse files Browse the repository at this point in the history
`Clone` bound on `Operation` (breaking) and test helper methods
  • Loading branch information
StuartHarris authored Oct 23, 2024
2 parents 0b2d1f2 + d8d80e2 commit 2b35e69
Show file tree
Hide file tree
Showing 29 changed files with 456 additions and 428 deletions.
12 changes: 6 additions & 6 deletions crux_core/src/capability/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ use channel::Sender;
/// type Output = HttpResponse;
/// }
/// ```
pub trait Operation: serde::Serialize + PartialEq + Send + 'static {
pub trait Operation: serde::Serialize + Clone + PartialEq + Send + 'static {
/// `Output` assigns the type this request results in.
type Output: serde::de::DeserializeOwned + Send + 'static;
}
Expand All @@ -238,7 +238,7 @@ pub trait Operation: serde::Serialize + PartialEq + Send + 'static {
/// # }
///
/// ```
#[derive(Debug, PartialEq, serde::Serialize, serde::Deserialize)]
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub enum Never {}

/// Implement `Operation` for `Never` to allow using it as a capability operation.
Expand All @@ -258,7 +258,7 @@ impl Operation for Never {
/// # pub struct Http<Ev> {
/// # context: CapabilityContext<HttpRequest, Ev>,
/// # }
/// # #[derive(serde::Serialize, serde::Deserialize, PartialEq, Eq)] pub struct HttpRequest;
/// # #[derive(Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)] pub struct HttpRequest;
/// # impl Operation for HttpRequest {
/// # type Output = ();
/// # }
Expand Down Expand Up @@ -372,8 +372,8 @@ pub trait WithContext<Ev, Ef> {
/// For example (from `crux_time`)
///
/// ```rust
/// # #[derive(PartialEq,serde::Serialize)]pub struct TimeRequest;
/// # #[derive(serde::Deserialize)]pub struct TimeResponse(pub String);
/// # #[derive(Clone, PartialEq, serde::Serialize)] pub struct TimeRequest;
/// # #[derive(Clone, serde::Deserialize)] pub struct TimeResponse(pub String);
/// # impl crux_core::capability::Operation for TimeRequest {
/// # type Output = TimeResponse;
/// # }
Expand Down Expand Up @@ -625,7 +625,7 @@ mod tests {
#[allow(dead_code)]
enum Event {}

#[derive(PartialEq, Serialize)]
#[derive(PartialEq, Clone, Serialize)]
struct Op {}

impl Operation for Op {
Expand Down
2 changes: 1 addition & 1 deletion crux_core/src/capability/shell_request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ mod tests {

use crate::capability::{channel, executor_and_spawner, CapabilityContext, Operation};

#[derive(serde::Serialize, PartialEq, Eq, Debug)]
#[derive(serde::Serialize, Clone, PartialEq, Eq, Debug)]
struct TestOperation;

impl Operation for TestOperation {
Expand Down
2 changes: 1 addition & 1 deletion crux_core/src/capability/shell_stream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ mod tests {

use crate::capability::{channel, executor_and_spawner, CapabilityContext, Operation};

#[derive(serde::Serialize, PartialEq, Eq, Debug)]
#[derive(serde::Serialize, Clone, PartialEq, Eq, Debug)]
struct TestOperation;

impl Operation for TestOperation {
Expand Down
26 changes: 25 additions & 1 deletion crux_core/src/testing.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Testing support for unit testing Crux apps.
use anyhow::Result;
use std::sync::Arc;
use std::{collections::VecDeque, sync::Arc};

use crate::{
capability::{
Expand Down Expand Up @@ -203,6 +203,30 @@ impl<Ef, Ev> Update<Ef, Ev> {
self.events.len()
);
}

/// Take effects matching the `predicate` out of the [`Update`]
/// and return them, mutating the `Update`
pub fn take_effects<P>(&mut self, predicate: P) -> VecDeque<Ef>
where
P: FnMut(&Ef) -> bool,
{
let (matching_effects, other_effects) = self.take_effects_partitioned_by(predicate);

self.effects = other_effects.into_iter().collect();

matching_effects
}

/// Take all of the effects out of the [`Update`]
/// and split them into those matching `predicate` and the rest
pub fn take_effects_partitioned_by<P>(&mut self, predicate: P) -> (VecDeque<Ef>, VecDeque<Ef>)
where
P: FnMut(&Ef) -> bool,
{
std::mem::take(&mut self.effects)
.into_iter()
.partition(predicate)
}
}

/// Panics if the pattern doesn't match an `Effect` from the specified `Update`
Expand Down
4 changes: 2 additions & 2 deletions crux_core/tests/capability_orchestration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ pub mod capabilities {
use crux_core::macros::Capability;
use serde::{Deserialize, Serialize};

#[derive(PartialEq, Serialize, Deserialize, Debug)]
#[derive(PartialEq, Clone, Serialize, Deserialize, Debug)]
pub struct OpOne {
number: usize,
}
Expand Down Expand Up @@ -123,7 +123,7 @@ pub mod capabilities {
use crux_core::macros::Capability;
use serde::{Deserialize, Serialize};

#[derive(PartialEq, Serialize, Deserialize, Debug)]
#[derive(PartialEq, Clone, Serialize, Deserialize, Debug)]
pub struct OpTwo {
number: usize,
}
Expand Down
121 changes: 57 additions & 64 deletions crux_http/tests/with_tester.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,19 +143,19 @@ mod tests {
let app = AppTester::<App, _>::default();
let mut model = Model::default();

let mut update = app.update(Event::Get, &mut model);

let Effect::Http(request) = update.effects_mut().next().unwrap();
let http_request = &request.operation;
let request = &mut app
.update(Event::Get, &mut model)
.expect_one_effect()
.expect_http();

assert_eq!(
*http_request,
request.operation,
HttpRequest::get("http://example.com/")
.header("authorization", "secret-token")
.build()
);

let update = app
let actual = app
.resolve(
request,
HttpResult::Ok(
Expand All @@ -166,19 +166,17 @@ mod tests {
.build(),
),
)
.expect("Resolves successfully");
.expect("Resolves successfully")
.expect_one_event();

let actual = update.events.clone();
assert_matches!(&actual[..], [Event::Set(Ok(response))] => {
assert_eq!(*response.body().unwrap(), "\"hello\"".to_string());
assert_eq!(*response.header("my_header").unwrap().iter()
assert_matches!(actual.clone(), Event::Set(Ok(response)) => {
assert_eq!(response.body().unwrap(), "\"hello\"");
assert_eq!(response.header("my_header").unwrap().iter()
.map(|v| v.to_string())
.collect::<Vec<_>>(), vec!["my_value1", "my_value2"]);
});

for event in update.events {
app.update(event, &mut model).assert_empty();
}
app.update(actual, &mut model).assert_empty();
assert_eq!(model.body, "\"hello\"");
assert_eq!(model.values, vec!["my_value1", "my_value2"]);
}
Expand All @@ -188,9 +186,10 @@ mod tests {
let app = AppTester::<App, _>::default();
let mut model = Model::default();

let mut update = app.update(Event::Post, &mut model);

let Effect::Http(request) = update.effects_mut().next().unwrap();
let request = &mut app
.update(Event::Post, &mut model)
.expect_one_effect()
.expect_http();

assert_eq!(
request.operation,
Expand All @@ -200,16 +199,16 @@ mod tests {
.build()
);

let update = app
let actual = app
.resolve(
request,
HttpResult::Ok(HttpResponse::ok().json("The Body").build()),
)
.expect("Resolves successfully");
.expect("Resolves successfully")
.expect_one_event();

let actual = update.events;
assert_matches!(&actual[..], [Event::Set(Ok(response))] => {
assert_eq!(*response.body().unwrap(), "\"The Body\"".to_string());
assert_matches!(actual, Event::Set(Ok(response)) => {
assert_eq!(response.body().unwrap(), "\"The Body\"");
});
}

Expand All @@ -218,40 +217,37 @@ mod tests {
let app = AppTester::<App, _>::default();
let mut model = Model::default();

let mut update = app.update(Event::GetPostChain, &mut model);

let Effect::Http(request) = update.effects_mut().next().unwrap();
let http_request = &request.operation;
let request = &mut app
.update(Event::GetPostChain, &mut model)
.expect_one_effect()
.expect_http();

assert_eq!(
*http_request,
request.operation,
HttpRequest::get("http://example.com/").build()
);

let mut update = app
let request = &mut app
.resolve(
request,
HttpResult::Ok(HttpResponse::ok().body("secret_place").build()),
)
.expect("Resolves successfully");

assert!(update.events.is_empty());

let Effect::Http(request) = update.effects_mut().next().unwrap();
let http_request = &request.operation;
.expect("Resolves successfully")
.expect_one_effect()
.expect_http();

assert_eq!(
*http_request,
request.operation,
HttpRequest::post("http://example.com/secret_place").build()
);

let update = app
let actual = app
.resolve(request, HttpResult::Ok(HttpResponse::status(201).build()))
.expect("Resolves successfully");
.expect("Resolves successfully")
.expect_one_event();

let actual = update.events.clone();
assert_matches!(&actual[..], [Event::ComposeComplete(status)] => {
assert_eq!(*status, 201);
assert_matches!(actual, Event::ComposeComplete(status) => {
assert_eq!(status, 201);
});
}

Expand All @@ -260,22 +256,21 @@ mod tests {
let app = AppTester::<App, _>::default();
let mut model = Model::default();

let mut update = app.update(Event::ConcurrentGets, &mut model);
let mut effects = update.effects_mut();
let mut requests = app
.update(Event::ConcurrentGets, &mut model)
.take_effects(Effect::is_http);

let Effect::Http(request_one) = effects.next().unwrap();
let http_request = &request_one.operation;
let request_one = &mut requests.pop_front().unwrap().expect_http();

assert_eq!(
*http_request,
request_one.operation,
HttpRequest::get("http://example.com/one").build()
);

let Effect::Http(request_two) = effects.next().unwrap();
let http_request = &request_two.operation;
let request_two = &mut requests.pop_front().unwrap().expect_http();

assert_eq!(
*http_request,
request_two.operation,
HttpRequest::get("http://example.com/two").build()
);

Expand All @@ -291,16 +286,16 @@ mod tests {
assert!(update.effects.is_empty());
assert!(update.events.is_empty());

let update = app
let actual = app
.resolve(
request_one,
HttpResult::Ok(HttpResponse::ok().body("one").build()),
)
.expect("Resolves successfully");
.expect("Resolves successfully")
.expect_one_event();

let actual = update.events.clone();
assert_matches!(&actual[..], [Event::ComposeComplete(status)] => {
assert_eq!(*status, 200);
assert_matches!(actual, Event::ComposeComplete(status) => {
assert_eq!(status, 200);
});
}

Expand All @@ -309,31 +304,29 @@ mod tests {
let app = AppTester::<App, _>::default();
let mut model = Model::default();

let mut update = app.update(Event::Get, &mut model);

let mut effects = update.effects_mut();
let Effect::Http(request) = effects.next().unwrap();
let http_request = &request.operation;
let request = &mut app
.update(Event::Get, &mut model)
.expect_one_effect()
.expect_http();

assert_eq!(
*http_request,
request.operation,
HttpRequest::get("http://example.com/")
.header("authorization", "secret-token")
.build()
);

let update = app
let actual = app
.resolve(
request,
HttpResult::Err(crux_http::HttpError::Io(
"Socket shenanigans prevented the request".to_string(),
)),
)
.expect("Resolves successfully");

let actual = update.events.clone();
.expect("Resolves successfully")
.expect_one_event();

let Event::Set(Err(crux_http::HttpError::Io(error))) = &actual[0] else {
let Event::Set(Err(crux_http::HttpError::Io(error))) = actual else {
panic!("Expected original error back")
};

Expand Down
Loading

0 comments on commit 2b35e69

Please sign in to comment.