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"