347ZUOIROXFQD22J6JS3IPG7FUYADVFACJNP26Q77GSYW6UZHHTQC
use derive_builder::Builder;
use std::fmt::Debug;
use std::time::Duration;
use std::time::Instant;
#[cfg_attr(test, mockall::automock(type State = State;))]
trait WaitCondition {
type State: Copy;
fn is_in_state(&self, state: Self::State) -> bool;
}
#[derive(Clone, Copy)]
struct Port(u16);
#[derive(Clone, Copy)]
enum State {
Present,
Absent,
}
impl WaitCondition for Port {
type State = State;
fn is_in_state(&self, state: Self::State) -> bool {
match state {
State::Present => !port_scanner::local_port_available(self.0),
State::Absent => !self.is_in_state(State::Present),
}
}
}
#[derive(Debug, PartialEq, Eq)]
enum WaitError {
Timeout,
}
#[cfg_attr(test, mockall::automock)]
trait Time {
fn elapsed(&self) -> Duration;
}
impl Time for Instant {
fn elapsed(&self) -> Duration {
self.elapsed()
}
}
#[cfg_attr(test, mockall::automock)]
trait Clock {
fn sleep(&self, duration: Duration);
fn now(&self) -> Box<dyn Time>;
}
struct SystemClock;
impl Clock for SystemClock {
fn sleep(&self, duration: Duration) {
std::thread::sleep(duration)
}
fn now(&self) -> Box<dyn Time> {
Box::new(Instant::now())
}
}
struct Environment {
clock: Box<dyn Clock>,
}
trait Step {
type Error: Debug;
fn execute(self, environment: &Environment) -> Result<(), Self::Error>;
}
#[derive(Builder)]
#[builder(pattern = "immutable")]
struct Wait<Condition: WaitCondition> {
condition: Condition,
state: Condition::State,
#[builder(default = "Duration::from_secs(60)")]
timeout: Duration,
#[builder(default = "None")]
delay: Option<Duration>,
#[builder(default = "Duration::from_millis(100)")]
sleep: Duration,
}
impl<C: WaitCondition> Step for Wait<C> {
type Error = WaitError;
fn execute(self, environment: &Environment) -> Result<(), Self::Error> {
if let Some(delay) = self.delay {
environment.clock.sleep(delay);
}
let start_time = environment.clock.now();
while start_time.elapsed() <= self.timeout {
if self.condition.is_in_state(self.state) {
return Ok(());
}
environment.clock.sleep(self.sleep);
}
eprintln!("Timed out");
Err(WaitError::Timeout)
}
}
fn main() {
let env = Environment {
clock: Box::new(SystemClock),
};
let wait_builder = WaitBuilder::default();
let wait_builder = wait_builder
.condition(Port(8181))
.timeout(Duration::from_secs(3));
let wait = wait_builder.state(State::Present).build().unwrap();
eprintln!("Waiting for port 8181 to be listening");
wait.execute(&env).unwrap();
eprintln!("Port 8181 is listening");
eprintln!("Waiting for port 8181 to not be listening");
let wait = wait_builder.state(State::Absent).build().unwrap();
eprintln!("Success");
wait.execute(&env).unwrap();
}
#[cfg(test)]
mod tests {
use super::*;
fn waiter(condition: MockWaitCondition) -> Wait<MockWaitCondition> {
WaitBuilder::default()
.condition(condition)
.state(State::Present)
.timeout(Duration::from_secs(10))
.build()
.unwrap()
}
fn constant_condition(value: bool) -> MockWaitCondition {
let mut condition = MockWaitCondition::default();
condition.expect_is_in_state().return_const(value);
condition
}
fn one_second_poller() -> Box<dyn Time> {
let mut times = (1..).map(Duration::from_secs);
let mut time = MockTime::default();
time.expect_elapsed()
.returning(move || times.next().unwrap());
Box::new(time)
}
fn polling_clock() -> MockClock {
let mut clock = MockClock::default();
clock.expect_now().return_once(|| one_second_poller());
clock
}
#[test]
fn wait_does_not_sleep_if_condition_is_initially_satisfied() {
let wait = waiter(constant_condition(true));
let clock = polling_clock();
let result = wait.execute(&Environment {
clock: Box::new(clock),
});
assert!(result.is_ok());
}
#[test]
fn wait_times_out_if_condition_is_never_satisfied() {
let wait = waiter(constant_condition(false));
let mut clock = polling_clock();
clock.expect_sleep().return_const(());
let result = wait.execute(&Environment {
clock: Box::new(clock),
});
assert_eq!(result, Err(WaitError::Timeout))
}
#[test]
fn wait_succeeds_after_condition_becomes_true() {
let mut condition_values = [false, true].iter();
let mut condition = MockWaitCondition::default();
condition
.expect_is_in_state()
.returning(move |_| *condition_values.next().unwrap())
.times(2);
let wait = waiter(condition);
let mut clock = polling_clock();
clock.expect_sleep().return_const(());
let result = wait.execute(&Environment {
clock: Box::new(clock),
});
assert!(result.is_ok());
}
}
[package]
authors = ["James Rhodes <James.Rhodes@softwareag.com>"]
edition = "2018"
name = "ransible"
version = "0.1.0"
[dependencies]
derive_builder = "*"
port_scanner = "*"
[dev_dependencies]
mockall = "*"
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "aho-corasick"
version = "0.7.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
dependencies = [
"memchr",
]
[[package]]
name = "autocfg"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "darling"
version = "0.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f2c43f534ea4b0b049015d00269734195e6d3f0f6635cb692251aca6f9f8b3c"
dependencies = [
"darling_core",
"darling_macro",
]
[[package]]
name = "darling_core"
version = "0.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e91455b86830a1c21799d94524df0845183fa55bafd9aa137b01c7d1065fa36"
dependencies = [
"fnv",
"ident_case",
"proc-macro2",
"quote",
"strsim",
"syn",
]
[[package]]
name = "darling_macro"
version = "0.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29b5acf0dea37a7f66f7b25d2c5e93fd46f8f6968b1a5d7a3e02e97768afc95a"
dependencies = [
"darling_core",
"quote",
"syn",
]
[[package]]
name = "derive_builder"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d13202debe11181040ae9063d739fa32cfcaaebe2275fe387703460ae2365b30"
dependencies = [
"derive_builder_macro",
]
[[package]]
name = "derive_builder_core"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "66e616858f6187ed828df7c64a6d71720d83767a7f19740b2d1b6fe6327b36e5"
dependencies = [
"darling",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "derive_builder_macro"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58a94ace95092c5acb1e97a7e846b310cfbd499652f72297da7493f618a98d73"
dependencies = [
"derive_builder_core",
"syn",
]
[[package]]
name = "difference"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198"
[[package]]
name = "downcast"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4bb454f0228b18c7f4c3b0ebbee346ed9c52e7443b0999cd543ff3571205701d"
[[package]]
name = "float-cmp"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1267f4ac4f343772758f7b1bdcbe767c218bbab93bb432acbf5162bbf85a6c4"
dependencies = [
"num-traits",
]
[[package]]
name = "fnv"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "fragile"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69a039c3498dc930fe810151a34ba0c1c70b02b8625035592e74432f678591f2"
[[package]]
name = "ident_case"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "memchr"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc"
[[package]]
name = "mockall"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "18d614ad23f9bb59119b8b5670a85c7ba92c5e9adf4385c81ea00c51c8be33d5"
dependencies = [
"cfg-if",
"downcast",
"fragile",
"lazy_static",
"mockall_derive",
"predicates",
"predicates-tree",
]
[[package]]
name = "mockall_derive"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5dd4234635bca06fc96c7368d038061e0aae1b00a764dc817e900dc974e3deea"
dependencies = [
"cfg-if",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "normalize-line-endings"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be"
[[package]]
name = "num-traits"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
dependencies = [
"autocfg",
]
[[package]]
name = "port_scanner"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "325a6d2ac5dee293c3b2612d4993b98aec1dff096b0a2dae70ed7d95784a05da"
[[package]]
name = "predicates"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f49cfaf7fdaa3bfacc6fa3e7054e65148878354a5cfddcf661df4c851f8021df"
dependencies = [
"difference",
"float-cmp",
"normalize-line-endings",
"predicates-core",
"regex",
]
[[package]]
name = "predicates-core"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57e35a3326b75e49aa85f5dc6ec15b41108cf5aee58eabb1f274dd18b73c2451"
[[package]]
name = "predicates-tree"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15f553275e5721409451eb85e15fd9a860a6e5ab4496eb215987502b5f5391f2"
dependencies = [
"predicates-core",
"treeline",
]
[[package]]
name = "proc-macro2"
version = "1.0.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0d8caf72986c1a598726adc988bb5984792ef84f5ee5aa50209145ee8077038"
dependencies = [
"unicode-xid",
]
[[package]]
name = "quote"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7"
dependencies = [
"proc-macro2",
]
[[package]]
name = "ransible"
version = "0.1.0"
dependencies = [
"derive_builder",
"mockall",
"port_scanner",
]
[[package]]
name = "regex"
version = "1.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.6.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
[[package]]
name = "strsim"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "syn"
version = "1.0.73"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f71489ff30030d2ae598524f61326b902466f72a0fb1a8564c001cc63425bcc7"
dependencies = [
"proc-macro2",
"quote",
"unicode-xid",
]
[[package]]
name = "treeline"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7f741b240f1a48843f9b8e0444fb55fb2a4ff67293b50a9179dfd5ea67f8d41"
[[package]]
name = "unicode-xid"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"