summaryrefslogtreecommitdiff
path: root/rust/src/web.rs
diff options
context:
space:
mode:
authorArnaud Bailly <arnaud.bailly@iohk.io>2025-01-25 10:45:41 +0100
committerArnaud Bailly <arnaud.bailly@iohk.io>2025-01-25 10:45:41 +0100
commit7752d73216578d5961751b5d0535088d384b4aa6 (patch)
tree786e46fe1276e93ade0a48398cd4c9ac13081707 /rust/src/web.rs
parentd6f68e919db51d366c8ca3c1509bea12aa81d692 (diff)
downloadlambda-nantes-7752d73216578d5961751b5d0535088d384b4aa6.tar.gz
Move λ-calcul workshop code to subdirectory
Diffstat (limited to 'rust/src/web.rs')
-rw-r--r--rust/src/web.rs899
1 files changed, 0 insertions, 899 deletions
diff --git a/rust/src/web.rs b/rust/src/web.rs
deleted file mode 100644
index 3f8f056..0000000
--- a/rust/src/web.rs
+++ /dev/null
@@ -1,899 +0,0 @@
-use actix_web::{get, middleware::Logger, post, web, App, HttpResponse, HttpServer, Responder};
-use chrono::{DateTime, Utc};
-use clap::Parser;
-use handlebars::{DirectorySourceOptions, Handlebars};
-use log::info;
-use proptest::test_runner::{Config, RngAlgorithm, TestRng, TestRunner};
-use rand::Rng;
-use serde::{Deserialize, Serialize};
-use std::sync::Mutex;
-use std::time::Duration;
-use std::{collections::HashMap, sync::Arc};
-use tokio::task::{self, JoinHandle};
-use uuid::Uuid;
-
-use lambda::lambda::{eval_all, eval_whnf, generate_expr, generate_exprs, gensym, Environment};
-use lambda::parser::{parse, parse_total};
-
-#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
-struct Registration {
- url: String,
- name: String,
-}
-
-#[derive(Debug, Serialize, Deserialize, Clone)]
-struct ClientData {
- name: String,
- grade: u8,
- last_query: DateTime<Utc>,
- success: bool,
-}
-
-impl ClientData {
- fn from(client: &Client) -> Self {
- ClientData {
- name: client.name.clone(),
- grade: client.grade,
- last_query: client
- .results
- .last()
- .map_or(chrono::offset::Utc::now(), |q| q.timestamp),
- success: client
- .results
- .last()
- .map_or(false, |q| matches!(q.result, TestResult::TestSucceeded)),
- }
- }
-}
-
-#[derive(Debug, Serialize, Deserialize, Clone)]
-struct Leaderboard {
- clients: Vec<ClientData>,
-}
-
-trait AppState: Send + Sync {
- fn register(&mut self, registration: &Registration) -> RegistrationResult;
- fn unregister(&mut self, url: &String);
-}
-
-#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
-enum RegistrationResult {
- RegistrationSuccess { id: String, url: String },
- UrlAlreadyRegistered { url: String },
-}
-
-#[derive(Debug, Clone)]
-struct Client {
- id: Uuid,
- name: String,
- url: String,
- grade: u8,
- runner: TestRunner,
- results: Vec<Test>,
- delay: std::time::Duration,
-}
-
-#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
-struct Test {
- timestamp: DateTime<Utc>,
- result: TestResult,
-}
-
-#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
-enum TestResult {
- TestFailed(String),
- ErrorSendingTest(String),
- TestSucceeded,
-}
-
-impl Client {
- fn new(url: String, name: String, delay: Duration) -> Self {
- let id = Uuid::new_v4();
- let runner = TestRunner::new_with_rng(
- Config::default(),
- TestRng::from_seed(RngAlgorithm::XorShift, &id.to_bytes_le()),
- );
- Self {
- id,
- url,
- name,
- grade: 1,
- runner,
- results: Vec::new(),
- delay,
- }
- }
-
- fn time_to_next_test(&self) -> Duration {
- self.delay
- }
-
- fn generate_expr(&mut self) -> (String, String) {
- if self.grade >= 10 {
- self.generate_exprs()
- } else {
- let input = generate_expr(self.grade.into(), &mut self.runner);
- let expected = eval_whnf(&input, &mut Environment::new());
- (input.to_string(), expected.to_string())
- }
- }
-
- fn generate_exprs(&mut self) -> (String, String) {
- let exprs = generate_exprs(self.grade.into(), &mut self.runner);
- let input = exprs
- .iter()
- .map(|v| format!("{}", v))
- .collect::<Vec<_>>()
- .join("\n");
- let expected = eval_all(&exprs);
- (
- input,
- expected
- .iter()
- .map(|v| format!("{}", v))
- .collect::<Vec<_>>()
- .join("\n"),
- )
- }
-
- /// Applies a `Test` to update client's state
- fn apply(&mut self, test: &Test) {
- match test.result {
- TestResult::TestSucceeded => {
- self.grade = self.grade.saturating_add(1);
- self.delay = Duration::from_secs_f64(self.delay.as_secs_f64() * 0.8);
- if self.delay.as_millis() < 500 {
- self.delay = Duration::from_millis(500);
- }
- }
- TestResult::TestFailed(_) => {
- self.delay = Duration::from_secs_f64(self.delay.as_secs_f64() * 1.2);
- if self.delay.as_secs() > 30 {
- self.delay = Duration::from_secs(30);
- }
- }
- _ => (),
- }
- self.results.push(test.clone());
- }
-
- fn check_result(&self, expected: &String, response: &Result<String, TestResult>) -> Test {
- let result = match response {
- Ok(expr) => {
- let vals = parse(expr);
- let actual = eval_all(&vals)
- .iter()
- .map(|v| format!("{}", v))
- .collect::<Vec<_>>()
- .join("\n");
- if actual == *expected {
- TestResult::TestSucceeded
- } else {
- TestResult::TestFailed(actual)
- }
- }
- Err(res) => res.clone(),
- };
- Test {
- result,
- timestamp: chrono::offset::Utc::now(),
- }
- }
-}
-
-#[derive(Debug)]
-struct State {
- base_duration: Duration,
- clients: HashMap<String, (Arc<Mutex<Client>>, JoinHandle<()>)>,
-}
-
-impl State {
- fn new() -> Self {
- State::with_duration(Duration::from_secs(10))
- }
-
- fn with_duration(base_duration: Duration) -> Self {
- Self {
- base_duration,
- clients: HashMap::new(),
- }
- }
-
- fn client_events(&self, url: &String) -> usize {
- let client = self.clients.get(url).unwrap().0.lock().unwrap();
- client.results.len()
- }
-}
-
-impl AppState for State {
- fn register(&mut self, registration: &Registration) -> RegistrationResult {
- if self.clients.contains_key(&registration.url) {
- RegistrationResult::UrlAlreadyRegistered {
- url: registration.url.clone(),
- }
- } else {
- let client = Client::new(
- registration.url.clone(),
- registration.name.clone(),
- self.base_duration,
- );
- let id = client.id.to_string();
- let client_ref = Arc::new(Mutex::new(client));
- // let it run in the background
- // FIXME: should find a way to handle graceful termination
- let client_handle = task::spawn(send_tests(client_ref.clone()));
-
- self.clients.insert(
- registration.url.clone(),
- (client_ref.clone(), client_handle),
- );
-
- RegistrationResult::RegistrationSuccess {
- id,
- url: registration.url.clone(),
- }
- }
- }
-
- fn unregister(&mut self, url: &String) {
- let (_, handle) = self.clients.get(url).unwrap();
- handle.abort()
- }
-}
-
-#[post("/register")]
-async fn register(
- app_state: web::Data<Arc<Mutex<State>>>,
- registration: web::Json<Registration>,
-) -> impl Responder {
- let result = app_state.lock().unwrap().register(&registration);
- match result {
- RegistrationResult::RegistrationSuccess { .. } => HttpResponse::Ok().json(result),
- RegistrationResult::UrlAlreadyRegistered { .. } => HttpResponse::BadRequest().json(result),
- }
-}
-
-#[post("/eval")]
-async fn eval(input: String) -> impl Responder {
- let exprs = parse_total(&input);
- match exprs {
- Ok(exprs) => {
- let mut rng = rand::thread_rng();
- if rng.gen_range(0..10) <= 2 {
- return HttpResponse::Ok().body(gensym());
- }
- let output = eval_all(&exprs)
- .iter()
- .map(|v| format!("{}", v))
- .collect::<Vec<_>>()
- .join("\n");
- HttpResponse::Ok().body(output.to_string())
- }
- Err(e) => HttpResponse::BadRequest().body(e.to_string()),
- }
-}
-
-#[get("/leaderboard")]
-async fn leaderboard(
- app_state: web::Data<Arc<Mutex<State>>>,
- hb: web::Data<Handlebars<'_>>,
-) -> impl Responder {
- let clients = &app_state.lock().unwrap().clients;
- let mut client_data = vec![];
- for client in clients.values() {
- let client = client.0.lock().unwrap();
- client_data.push(ClientData::from(&client));
- }
- client_data.sort_by(|a, b| b.grade.cmp(&a.grade));
-
- let body = hb
- .render(
- "leaderboard",
- &Leaderboard {
- clients: client_data,
- },
- )
- .unwrap();
-
- web::Html::new(body)
-}
-
-#[derive(Parser, Debug)]
-struct Options {
- /// The port to listen on
- /// Defaults to 8080
- #[arg(short, long, default_value_t = 8080)]
- port: u16,
- /// The host to bind the server to
- /// Defaults to 127.0.0.1
- #[arg(long, default_value = "127.0.0.1")]
- host: String,
-}
-
-#[tokio::main]
-async fn main() -> std::io::Result<()> {
- let options = Options::parse();
- let app_state = Arc::new(Mutex::new(State::new()));
-
- env_logger::init();
- // Handlebars uses a repository for the compiled templates. This object must be
- // shared between the application threads, and is therefore passed to the
- // Application Builder as an atomic reference-counted pointer.
- let mut handlebars = Handlebars::new();
- handlebars
- .register_templates_directory(
- "./templates",
- DirectorySourceOptions {
- tpl_extension: ".html".to_owned(),
- hidden: false,
- temporary: false,
- },
- )
- .unwrap();
-
- HttpServer::new(move || {
- App::new()
- .wrap(Logger::default())
- .app_data(web::Data::new(app_state.clone()))
- .app_data(web::Data::new(handlebars.clone()))
- .service(register)
- .service(eval)
- .service(leaderboard)
- })
- .bind((options.host, options.port))?
- .run()
- .await
-}
-
-fn get_test(client_m: &Mutex<Client>) -> (String, String, String) {
- let mut client = client_m.lock().unwrap();
- let (input, expected) = client.generate_expr();
- (input, client.url.clone(), expected)
-}
-
-async fn send_tests(client_m: Arc<Mutex<Client>>) {
- loop {
- let sleep = sleep_time(&client_m);
- tokio::time::sleep(sleep).await;
- {
- let (input, url, expected) = get_test(&client_m);
-
- let response = send_test(&input, &url, sleep).await;
-
- apply_result(&client_m, expected, response);
- }
- }
-}
-
-fn apply_result(client_m: &Mutex<Client>, expected: String, response: Result<String, TestResult>) {
- let mut client = client_m.lock().unwrap();
- let test = client.check_result(&expected, &response);
- client.apply(&test);
-}
-
-fn sleep_time(client_m: &Arc<Mutex<Client>>) -> Duration {
- client_m.lock().unwrap().time_to_next_test()
-}
-
-async fn send_test(input: &String, url: &String, timeout: Duration) -> Result<String, TestResult> {
- info!("Sending {} to {}", input, url);
- let body = input.clone();
- let response = reqwest::Client::new()
- .post(url)
- .timeout(timeout)
- .header("content-type", "text/plain")
- .body(body)
- .send()
- .await;
- match response {
- Ok(response) => {
- let body = response.text().await.unwrap();
- info!("Response from {}: {}", url, body);
- Ok(body)
- }
- Err(e) => {
- info!("Error sending test: {}", e);
- Err(TestResult::ErrorSendingTest(e.to_string()))
- }
- }
-}
-
-#[cfg(test)]
-mod app_tests {
- use std::str::from_utf8;
- use std::sync::Arc;
-
- use actix_web::http::header::TryIntoHeaderValue;
- use actix_web::{body, http::header::ContentType, middleware::Logger, test, App};
- use lambda::ast::Value;
-
- use super::*;
-
- #[actix_web::test]
- async fn post_registration_returns_success_with_unique_id() {
- let state = Arc::new(Mutex::new(State::new()));
- // FIXME should only be called once, move to setup
- env_logger::init();
-
- let app = test::init_service(
- App::new()
- .wrap(Logger::default())
- .app_data(web::Data::new(state))
- .service(register),
- )
- .await;
- let url = "http://192.168.1.1".to_string();
- let req = test::TestRequest::post()
- .uri("/register")
- .set_json(Registration {
- url: url.clone(),
- name: "foo".to_string(),
- })
- .insert_header(ContentType::json())
- .to_request();
-
- let resp = test::call_service(&app, req).await;
-
- assert!(resp.status().is_success());
-
- let body = resp.into_body();
- let bytes = body::to_bytes(body).await;
- match serde_json::from_slice::<RegistrationResult>(bytes.as_ref().unwrap()).unwrap() {
- RegistrationResult::RegistrationSuccess { id: _, url: url1 } => assert_eq!(url1, url),
- _ => panic!("Expected RegistrationSuccess, got {:?}", bytes.unwrap()),
- };
- }
-
- #[actix_web::test]
- async fn post_registration_returns_400_when_register_fails() {
- let state = Arc::new(Mutex::new(State::new()));
-
- let app = test::init_service(
- App::new()
- .wrap(Logger::default())
- .app_data(web::Data::new(state.clone()))
- .service(register),
- )
- .await;
- let url = "http://192.168.1.1".to_string();
- let registration = Registration {
- url: url.clone(),
- name: "foo".to_string(),
- };
-
- state.lock().unwrap().register(&registration);
-
- let req = test::TestRequest::post()
- .uri("/register")
- .set_json(registration)
- .insert_header(ContentType::json())
- .to_request();
-
- let resp = test::call_service(&app, req).await;
-
- assert!(resp.status().is_client_error());
- assert_eq!(
- ContentType::json().try_into_value().unwrap(),
- resp.headers().get("content-type").unwrap()
- );
- }
-
- #[actix_web::test]
- async fn post_expression_returns_evaluation() {
- let app = test::init_service(App::new().wrap(Logger::default()).service(eval)).await;
-
- let req = test::TestRequest::post()
- .uri("/eval")
- .set_payload("((lam (x y) x) 1 2)")
- .insert_header(ContentType::plaintext())
- .to_request();
-
- let resp = test::call_service(&app, req).await;
-
- assert!(resp.status().is_success());
-
- let body = resp.into_body();
- let bytes = body::to_bytes(body).await.unwrap();
- assert_eq!(bytes, "1".to_string().into_bytes());
- }
-
- #[actix_web::test]
- async fn post_expression_returns_multiple_evaluations() {
- let app = test::init_service(App::new().wrap(Logger::default()).service(eval)).await;
-
- let req = test::TestRequest::post()
- .uri("/eval")
- .set_payload("((lam (x y) x) 1 2)\n42")
- .insert_header(ContentType::plaintext())
- .to_request();
-
- let resp = test::call_service(&app, req).await;
-
- assert!(resp.status().is_success());
-
- let body = resp.into_body();
- let bytes = body::to_bytes(body).await.unwrap();
- assert_eq!("1\n42".to_string().into_bytes(), bytes);
- }
-
- #[actix_web::test]
- async fn get_leaderboard_returns_html_page_listing_clients_state() {
- let app_state = Arc::new(Mutex::new(State::new()));
- app_state.lock().unwrap().register(&Registration {
- url: "http://1.2.3.4".to_string(),
- name: "client1".to_string(),
- });
-
- let mut handlebars = Handlebars::new();
- handlebars
- .register_templates_directory(
- "./templates",
- DirectorySourceOptions {
- tpl_extension: ".html".to_owned(),
- hidden: false,
- temporary: false,
- },
- )
- .unwrap();
-
- let app = test::init_service(
- App::new()
- .wrap(Logger::default())
- .app_data(web::Data::new(app_state.clone()))
- .app_data(web::Data::new(handlebars.clone()))
- .service(leaderboard),
- )
- .await;
-
- let req = test::TestRequest::get().uri("/leaderboard").to_request();
-
- let resp = test::call_service(&app, req).await;
-
- assert!(resp.status().is_success());
-
- let bytes = body::to_bytes(resp.into_body()).await.unwrap();
- assert!(from_utf8(&bytes).unwrap().contains("client1"));
- }
-
- #[test]
- async fn app_does_not_register_same_url_twice() {
- let mut app_state = State::new();
- let registration = Registration {
- name: "foo".to_string(),
- url: "http://1.2.3.4".to_string(),
- };
-
- app_state.register(&registration);
- let result = app_state.register(&registration);
-
- assert_eq!(
- RegistrationResult::UrlAlreadyRegistered {
- url: "http://1.2.3.4".to_string()
- },
- result
- );
- }
-
- #[test]
- async fn unregistering_registered_client_stops_tester_thread_from_sending_tests() {
- let mut app_state = State::with_duration(Duration::from_millis(100));
- let registration = Registration {
- name: "foo".to_string(),
- url: "http://1.2.3.4".to_string(),
- };
-
- let reg = app_state.register(&registration);
- assert!(matches!(
- reg,
- RegistrationResult::RegistrationSuccess { .. }
- ));
-
- tokio::time::sleep(Duration::from_millis(500)).await;
-
- app_state.unregister(&registration.url);
-
- let grade_before = app_state.client_events(&registration.url);
- tokio::time::sleep(Duration::from_millis(500)).await;
- let grade_after = app_state.client_events(&registration.url);
-
- assert_eq!(grade_before, grade_after);
- }
-
- fn client() -> Client {
- Client::new(
- "http://1.2.3.4".to_string(),
- "foo".to_string(),
- Duration::from_secs(10),
- )
- }
-
- #[test]
- async fn client_generates_constant_at_level_1() {
- let mut client = client();
-
- let (input, _) = client.generate_expr();
-
- match parse(&input)[..] {
- [Value::Num(_)] => (),
- _ => panic!("Expected constant 3"),
- }
- }
-
- #[test]
- async fn client_generates_different_inputs_on_each_call() {
- let mut client = client();
-
- let (input1, _) = client.generate_expr();
- let (input2, _) = client.generate_expr();
-
- assert_ne!(input1, input2);
- }
-
- #[test]
- async fn client_generates_ascii_variables_at_level_2() {
- let mut client = client();
- client.grade = 2;
-
- let (input, _) = client.generate_expr();
-
- let parsed = parse(&input);
- match &parsed[..] {
- [Value::Sym(name)] => {
- assert!(name.chars().all(|c| c.is_ascii_alphanumeric()));
- }
- _ => panic!("Expected symbol, got {:?}", parsed),
- }
- }
-
- #[test]
- async fn client_generates_unicode_variables_at_level_3() {
- let mut client = client();
- client.grade = 3;
-
- let (input, _) = client.generate_expr();
-
- let parsed = parse(&input);
- match &parsed[..] {
- [Value::Sym(_)] => (),
- _ => panic!("Expected symbol, got {:?}", parsed),
- }
- }
-
- #[test]
- async fn client_generates_binary_application_at_level_4() {
- let mut client = client();
- client.grade = 4;
-
- let (input, _) = client.generate_expr();
-
- let parsed = parse(&input);
- match &parsed[..] {
- [Value::App(_, _)] => (),
- _ => panic!("Expected symbol, got {:?}", parsed),
- }
- }
-
- #[test]
- async fn client_generates_nested_applications_and_constants_at_level_5() {
- let mut client = client();
- client.grade = 5;
-
- let (input, _) = client.generate_expr();
-
- let parsed = parse(&input);
- match &parsed[..] {
- [Value::App(_, _)] => (),
- [Value::Sym(_)] => (),
- [Value::Num(_)] => (),
- _ => panic!("Expected symbol, got {:?}", parsed),
- }
- }
-
- #[test]
- async fn client_generates_lambda_terms_at_level_6() {
- let mut client = client();
- client.grade = 6;
-
- let (input, _) = client.generate_expr();
-
- let parsed = parse(&input);
- match &parsed[..] {
- [Value::Lam(_, _)] => (),
- _ => panic!("Expected symbol, got {:?}", parsed),
- }
- }
-
- #[test]
- async fn client_generates_application_with_lambda_terms_at_level_7() {
- let mut client = client();
- client.grade = 7;
-
- let (input, _) = client.generate_expr();
-
- let parsed = parse(&input);
- match &parsed[..] {
- [Value::App(t1, _)] if matches!(**t1, Value::Lam(_, _)) => (),
- _ => panic!("Expected symbol, got {:?}", parsed),
- }
- }
-
- #[test]
- async fn client_generates_applications_with_more_than_2_terms_at_level_8() {
- let mut client = client();
- client.grade = 8;
-
- let (input, _) = client.generate_expr();
-
- let parsed = parse(&input);
- if let [Value::App(_, _)] = &parsed[..] {
- assert!(input.split(' ').count() >= 2)
- }
- }
-
- #[test]
- async fn client_generates_more_complex_terms_at_level_9() {
- let mut client = client();
- client.grade = 9;
-
- let (input, _) = client.generate_expr();
-
- let parsed = parse(&input);
-
- assert!(!parsed.is_empty());
- }
-
- #[test]
- async fn client_generates_multiple_terms_at_level_10() {
- let mut client = client();
- client.grade = 10;
-
- let (input, _) = client.generate_expr();
-
- let parsed = parse(&input);
-
- assert!(!parsed.is_empty());
- }
-
- #[test]
- async fn client_increases_grade_on_successful_test() {
- let mut client = client();
- let test = Test {
- timestamp: chrono::offset::Utc::now(),
- result: TestResult::TestSucceeded,
- };
-
- client.apply(&test);
-
- assert_eq!(2, client.grade);
- }
-
- #[test]
- async fn client_stores_test_results() {
- let mut client = client();
- let test = Test {
- timestamp: chrono::offset::Utc::now(),
- result: TestResult::TestSucceeded,
- };
-
- client.apply(&test);
-
- assert_eq!(test, client.results.first().unwrap().clone());
- }
-
- #[test]
- async fn client_returns_test_successful_if_result_match() {
- let client = client();
- let expected = "1".to_string();
- let response = Ok("1".to_string());
-
- let test = client.check_result(&expected, &response);
-
- assert_eq!(TestResult::TestSucceeded, test.result);
- }
-
- #[test]
- async fn client_returns_test_failed_given_result_do_not_match() {
- let client = client();
- let expected = "1".to_string();
- let response = Ok("2".to_string());
-
- let test = client.check_result(&expected, &response);
-
- assert_eq!(TestResult::TestFailed("2".to_string()), test.result);
- }
-
- #[test]
- async fn client_does_not_increase_grade_on_failed_test() {
- let mut client = client();
- let test = Test {
- timestamp: chrono::offset::Utc::now(),
- result: TestResult::TestFailed("2".to_string()),
- };
-
- client.apply(&test);
-
- assert_eq!(1, client.grade);
- }
-
- #[test]
- async fn client_starts_delay_to_next_test_at_10s() {
- let client = client();
-
- let delay = client.time_to_next_test();
-
- assert_eq!(std::time::Duration::from_secs(10), delay);
- }
-
- #[test]
- async fn client_increases_delay_to_next_upon_failed_test() {
- let mut client = client();
- let test = Test {
- timestamp: chrono::offset::Utc::now(),
- result: TestResult::TestFailed("2".to_string()),
- };
- let delay_before = client.time_to_next_test();
-
- client.apply(&test);
-
- assert!(delay_before < client.time_to_next_test());
- }
-
- #[test]
- async fn client_increases_delay_to_maximum_of_30s() {
- let mut client = client();
- let test = Test {
- timestamp: chrono::offset::Utc::now(),
- result: TestResult::TestFailed("2".to_string()),
- };
-
- for _ in 0..100 {
- client.apply(&test);
- }
-
- assert_eq!(Duration::from_secs(30), client.time_to_next_test());
- }
-
- #[test]
- async fn client_score_cannot_go_beyond_255() {
- let mut client = client();
- let test = Test {
- timestamp: chrono::offset::Utc::now(),
- result: TestResult::TestSucceeded,
- };
-
- for _ in 0..256 {
- client.apply(&test);
- }
-
- assert_eq!(255, client.grade);
- }
-
- #[test]
- async fn client_decreases_delay_to_next_upon_successful_test() {
- let mut client = client();
- let test = Test {
- timestamp: chrono::offset::Utc::now(),
- result: TestResult::TestSucceeded,
- };
- let delay_before = client.time_to_next_test();
-
- client.apply(&test);
-
- assert!(delay_before > client.time_to_next_test());
- }
-
- #[test]
- async fn client_decreases_delay_to_minimum_of_500ms() {
- let mut client = client();
- let test = Test {
- timestamp: chrono::offset::Utc::now(),
- result: TestResult::TestSucceeded,
- };
-
- for _ in 0..100 {
- client.apply(&test);
- }
-
- assert_eq!(Duration::from_millis(500), client.time_to_next_test());
- }
-}