Skip to content

Commit

Permalink
Feature/domain unit tests (#2)
Browse files Browse the repository at this point in the history
* add unit-tests for domain core

- extract domain core into own crate
- make `[no_std]` on that crate conditional (not for testing)
- add unit tests for all logic-bearing models
- restructure app service in keret-controller, so it's not in a module `domain` but `application_service` (including ports / dependencies)

* extract app service into own crate

- new crate for app service and "port" traits
  (things the app service depend on)
- remove free/Mutex handling from app service to make it testable
- rework main to have whole app service in one mutex instead of the 3 individual mutexes

* add unit-tests for app service
  • Loading branch information
kelko authored Oct 12, 2024
1 parent f960ac6 commit 326c002
Show file tree
Hide file tree
Showing 31 changed files with 1,264 additions and 457 deletions.
89 changes: 89 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 4 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
[workspace]
members = [
"src/keret-controller",
"src/keret-adapter",
"src/keret-controller",
"src/keret-controller-appservice",
"src/keret-controller-domain",
"src/keret-controller-transmit",
"src/keret-service-transmit",
"src/keret-service",
Expand All @@ -14,4 +16,4 @@ lto = true

[profile.release.package.keret-controller]
codegen-units = 1
debug = true
#debug = true
12 changes: 12 additions & 0 deletions src/keret-controller-appservice/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[package]
name = "keret-controller-appservice"
version = "0.1.0"
edition = "2021"

[dependencies]
snafu = { version = "0.8", default-features = false }
keret-controller-domain = { path = "../keret-controller-domain" }
keret-controller-transmit = { path = "../keret-controller-transmit", default-features = false }

[dev-dependencies]
mockall = "0.13.0"
97 changes: 97 additions & 0 deletions src/keret-controller-appservice/src/app_service/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
use crate::{
error::{DomainErrorOccurredSnafu, SendingMessageToOutsideFailedSnafu},
ports::{Display, OutsideMessaging, RunningTimeClock, UserInterface},
Error,
};
use keret_controller_domain::{AppMode, StateUpdateResult};
use snafu::ResultExt;

#[cfg(test)]
mod test;

/// application service to orchestrate the domain logic
pub struct ApplicationService<TClock, TDisplay, TUserInterface, TSerialBus, TReportFunc>
where
TClock: RunningTimeClock,
TDisplay: Display,
TUserInterface: UserInterface,
TSerialBus: OutsideMessaging,
TReportFunc: FnMut(&Error<TSerialBus::Error>) + Send + Sync,
{
pub running_timer: TClock,
pub display: TDisplay,
pub controls: TUserInterface,
serial_bus: TSerialBus,
report_error: TReportFunc,
}

impl<TClock, TDisplay, TUserInterface, TSerialBus, TReportFunc>
ApplicationService<TClock, TDisplay, TUserInterface, TSerialBus, TReportFunc>
where
TClock: RunningTimeClock,
TDisplay: Display,
TUserInterface: UserInterface,
TSerialBus: OutsideMessaging,
TReportFunc: FnMut(&Error<TSerialBus::Error>) + Send + Sync,
{
/// setup a new `ApplicationService` instance
#[inline]
pub fn new(
running_timer: TClock,
display: TDisplay,
controls: TUserInterface,
serial_bus: TSerialBus,
report_error: TReportFunc,
) -> Self {
Self {
running_timer,
display,
controls,
serial_bus,
report_error,
}
}

/// run the next cycle of the main logic loop, returning the new state
pub fn next_cycle(&mut self, mode: &AppMode) -> AppMode {
let next = self
.calculate_next_state(mode)
.unwrap_or_else(|e| self.handle_runtime_error(e));
self.display.show_mode(&next);

next
}

/// calculate the next state:
/// check what the user requested to do (by clicking on buttons) and
/// let domain layer calculate the next state based on this input
fn calculate_next_state(
&mut self,
mode: &AppMode,
) -> Result<AppMode, Error<TSerialBus::Error>> {
let request = self.controls.requested_interaction();
let time = self.running_timer.now();

let StateUpdateResult {
mode,
result: message,
} = mode
.handle_interaction_request(request, time)
.context(DomainErrorOccurredSnafu)?;

if let Some(message) = message {
self.serial_bus
.send_result(message)
.context(SendingMessageToOutsideFailedSnafu)?;
}

Ok(mode)
}

/// report an error that happened while executing the main loop
/// and switch the AppMode appropriately to indicate it's in a failure state
fn handle_runtime_error(&mut self, err: Error<TSerialBus::Error>) -> AppMode {
(self.report_error)(&err);
AppMode::Error
}
}
Loading

0 comments on commit 326c002

Please sign in to comment.