From a3618c8df04c4b20af144099f4832f5d46957ac6 Mon Sep 17 00:00:00 2001 From: CasperStaahl Date: Wed, 7 Dec 2022 12:19:10 +0100 Subject: [PATCH] Simulation (#55) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update test comment * Format * Revert "xd" This reverts commit 556b8a806a5cb82c133f314ca1af89fcd4edc2f3. * Manually fix residue conflicts… thanks Nicolai * Manually fix residue conflicts… thanks Nicolai * Still fixing * Still fixing * Fix compiler errors * Fix compiler warnings, and format * Fix compiler error * Update some tests, now passig :) * fmt * Refactor * Refactor * Preperation for next sprint * Multiple: - Add id to Edge - Test and implement \`from :: TransitionDecisionPoint -> DecisionPoint\` - Remove common helper function for Simulation testing to file in test dir * started testing * Works now * Implemented take_simulation_steps and refactored some code into simulation_info_to_transition_system helper function * Working on tests for DecisionPoint -> ProtoDecisionPoint * Remove unused code * Fix tests that failed because id was added to edge * Add test from__good_DecisionPoint__returns_good_ProtoDecisionPoint * TransitionDecision_from__Decision__returns_correct_TransitionDecision() 🧪 * No idea why I had to track these files again. * Added another failingfailing test Decision_from__ProtoDecision__returns_correct_Decision() 🧪 * Getting ready to implement * formatted * Start implement from :: (&DecisionPoint, &TransitionSystemPtr) -> ProtoDecisionPoint * Delete decisionpoint_serializer.rs * Working on test for ProtoDecisionPoint * Implementing from ProtoDecision to Decision * Test for ProtoDecisionPoint fixed * Implementation begun on transition_decision_point.rs * Cleanup * Move TransitionDecision to own file * Move Decision to own file * fmt * Update test names * Test pass and working implementation (needs reviewing) * Fix requested changes * Added slight documentation * Fix compile error * Merged main * . * Create federation from proto_decision * . * Fix from :: &Decision, &TransitionSystemPtr -> TransitionDecision * Refactor * Update documentation * Update documentation * ProtoEdge can find specific component * Update proto * Update proto * Fix compiler warning and errors * added component_name field to edge * Refactor from :: (&Decision, &TransitionSystemPrt) -> TransitionDecision * Multithread start_simulation api * Revert "Merge branch 'main' into Simulation" This reverts commit bc9ed6e7eaa342c4da0a9637a044947d8342e473, reversing changes made to bedb80dc6c33afbd9d9762b3c888c032ffd9a4cd. * Change branch * Not building XD * implement find state (maybe?) * Added some implementation to Protoconstraint::from(constraint) * Revert "Revert "Merge branch 'main' into Simulation"" This reverts commit 62552d16c2b862c43e81e2c0b1adf81e4b38f974. * Fix compiler error * Change from :: &Transition -> [Edge] to return empty [] * Unignore test start_simulation__responds_as_expected * Refactor * Work on from :: (&Constraint, &TransitionSystemPtr) -> ProtoConstraint * Correct Some tests * Fix compiler error * Fix bug in from :: Decision -> TransitionDecision * Encapsulate all Simulation structs * Refactor confusing function * Remove allow dead code * Fix compiler errors * Remove incorrect comment * Update error message * Replace unwrap() with match * Finish from :: &TransitionDecisionPoin -> DecisionPoint * Fix clippy * Do something i dont remeber * Working on ProtoDecision -> Decision, Clocks not working * Implement component_names for trait TransitionSystem * Ignore failing tests * Remove unused test module * I don't even know * Write test for composition * Fix warnings * EOD, test fixed and working on logic * Write tests for composition components * Write tests for composition components * Fix bug, tests still failing it works tho * Move test `start_simulation_step__get_composit_component__should_return_component` - Also fix decision point to order, so same result every time * Write passing tests for conjunction components * Test/Logic working on Decision::from(ProtoDecision) * Working on more tests. my friends! * Refactor and maybe fix bug * Working on tests once again! * Format * Write failing tests for take_simulation_step * Fix warnings and format * Fix warnings and format * Fix compiler warnings * This does not build! Logic * . * Fix bug and ignore tests that hang * Fix test * Fix clippy * Implement non-deterministic choises internally * Update proto * Format * Move stuff that create proto structs to proto_writer * Move stuff that creates stuff from proto objects * Remove unused test mod * Refactor * Fixed take_simulation_step__get_composit_component__should_return_component test Conjunction test is still ignored for now * filter hidden edges * take_simulation_step__get_composit_component__should_return_component for conjunction Also fixed error in previous commit that made some tests fail * Refactor * Create propterbased test B) * Add test * Create test and found bug :( * Refactor * Refactor * Refactor * Fix clippy: * Fix bug where hidden edges would not be filtered * Remove .clone() * Rename variable * Create baller integration test * format * Remove print line from test * Moved Proto_location_tuple_to_location_tuple to ProtoReader * Combine tests under same case * Combine test cases * Move test data to test_data.rs * Move test data from take_simultion_step.rs to test_data.rs * Move test data from start_simulation.rs to test_data.rs * split grpc_helper.rs into test_data.rs and helper.rs * Fix compiler warnings * Format * Move helpers functions from simulation.rs to helper.rs * Refactor and update documentation * Add documentation * Add documentation to handle_take_simulation_step * Update documentation : * Replace match with unwrap * Revert "Replace match with unwrap" This reverts commit 61c4fb85ec7b27ebdaeed36507d2cf941046e987. * Add documentation * Document decision.rs * Document decision_point.rs * Update documentation * Update documentation * Change resolve to return None when use_transition is false * Unignore from_Determinism_NonDeterminismCom__returns_ok * ^ * Unignore and update test and make test module private * Refactor component.rs * Fix clippy * Fix clippy * Update documentation * Update documentation and naming * Update documentation * Refactor and update documentation * Remove unused use * Refactor proto_writer.rs * Document proto_writer.rs * Update uncomment test * Document and refactor proto_reader.rs * more documentation and refactoring * Fix clippy * Refactor * Fix clippy * Refactor and document Decision::resolve * Refactor and document DecisionPoint::initial * Add test for non-convex federation for state_to_proto_state_to_state_is_same_state * Remove commented code * ^ * Refactor proto_decision_to_decision__ProtoDecision_with_universal_ProtoFederation__returns_correct_Decision * proto_decision_to_decision__ProtoDecision_with_nonuniversal_ProtoFederation__returns_correct_Decision * Refactor proto_decision_to_decision__ProtoDecision_with_conjunction_of_components__returns_correct_Decision * Refactor decision_point_to_proto_decision_point__initial_DecisionPoint_EcdarUniversity_Administration_par_Machine_par_Researcher__returns_correct_ProtoDecisionPoint * Refactor decision_point_to_proto_decision_point__initial_DecisionPoint_EcdarUniversity_Machine__returns_correct_ProtoDecisionPoint * Refactor decision_point_to_proto_decision_point__initial_DecisionPoint_EcdarUniversity_Machine_after_tea__returns_correct_ProtoDecisionPoint * Move test * Refactor from__initial_EcdarUniversity_Machine__returns_correct_DecisionPoint * Refactor testing * Refactor test * Add TODO * Make test module private * Refactor * Document decision_point_to_proto_decision_point() * Remove unused dependencie * Add bench for Simulation * Fix clippy * Refactor * Fixed test name to be accurate * Move .sorted() to sort less elements * Refactor proto_location_tuple_to_location_tuple as mentioned in review * Declare constant as const * Create alternative use_transition Co-authored-by: Mightyhaha <74659365+Mightyhaha@users.noreply.github.com> Co-authored-by: Patrick Bertelsen Co-authored-by: Simon Andersen Co-authored-by: Mads Balslev Co-authored-by: pberte20 <71381584+pberte20@users.noreply.github.com> --- Cargo.toml | 7 + benches/simulation_bench.rs | 122 ++ .../EcdarUniversity/Components/Machine4.json | 176 +++ .../EcdarUniversity/SystemDeclarations.json | 2 +- .../Components/NonConvexFederation.json | 28 + .../Simulation/Components/SimMachine.json | 107 ++ src/DataReader/component_loader.rs | 58 + src/DataReader/mod.rs | 2 + src/DataReader/parse_queries.rs | 2 +- src/DataReader/proto_reader.rs | 273 ++++ src/DataReader/proto_writer.rs | 334 +++++ src/ModelObjects/component.rs | 15 +- src/ProtobufServer/ecdar_backend.rs | 10 +- src/ProtobufServer/ecdar_requests/mod.rs | 2 + .../ecdar_requests/start_simulation.rs | 34 + .../ecdar_requests/take_simulation_step.rs | 49 + src/Simulation/decision.rs | 38 + src/Simulation/decision_point.rs | 86 ++ src/Simulation/mod.rs | 4 + src/Simulation/transition_decision.rs | 203 +++ src/Simulation/transition_decision_point.rs | 124 ++ src/System/save_component.rs | 4 +- src/TransitionSystems/compiled_component.rs | 20 +- src/TransitionSystems/location_id.rs | 22 + src/TransitionSystems/location_tuple.rs | 1 + src/TransitionSystems/mod.rs | 2 +- src/TransitionSystems/transition_system.rs | 92 +- src/tests/Simulation/helper.rs | 251 ++++ src/tests/Simulation/mod.rs | 2 + src/tests/Simulation/test_data.rs | 1310 +++++++++++++++++ src/tests/grpc/mod.rs | 3 + src/tests/grpc/simulation.rs | 61 + src/tests/grpc/start_simulation.rs | 67 + src/tests/grpc/take_simulation_step.rs | 72 + src/tests/mod.rs | 1 + 35 files changed, 3570 insertions(+), 14 deletions(-) create mode 100644 benches/simulation_bench.rs create mode 100644 samples/json/EcdarUniversity/Components/Machine4.json create mode 100644 samples/json/Simulation/Components/NonConvexFederation.json create mode 100644 samples/json/Simulation/Components/SimMachine.json create mode 100644 src/DataReader/proto_reader.rs create mode 100644 src/DataReader/proto_writer.rs create mode 100644 src/ProtobufServer/ecdar_requests/start_simulation.rs create mode 100644 src/ProtobufServer/ecdar_requests/take_simulation_step.rs create mode 100644 src/Simulation/decision.rs create mode 100644 src/Simulation/decision_point.rs create mode 100644 src/Simulation/transition_decision.rs create mode 100644 src/Simulation/transition_decision_point.rs create mode 100644 src/tests/Simulation/helper.rs create mode 100644 src/tests/Simulation/mod.rs create mode 100644 src/tests/Simulation/test_data.rs create mode 100644 src/tests/grpc/simulation.rs create mode 100644 src/tests/grpc/start_simulation.rs create mode 100644 src/tests/grpc/take_simulation_step.rs diff --git a/Cargo.toml b/Cargo.toml index 097f4bb3..c88c2513 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,9 +39,12 @@ edbm = { git = "https://github.com/Ecdar/EDBM" } log = "0.4.17" env_logger = { version = "0.9.0", optional = true } chrono = { version = "0.4.22", optional = true } +test-case = "2.2.2" crossbeam-channel = "0.5.6" num_cpus = "1.13.1" lru = "0.8.1" +itertools = "0.10.5" +regex = "1" # Enable optimizations for EDBM in debug mode, but not for our code: [profile.dev.package.edbm] @@ -71,4 +74,8 @@ harness = false [[bench]] name = "clock_reduction_bench" +harness = false + +[[bench]] +name = "simulation_bench" harness = false \ No newline at end of file diff --git a/benches/simulation_bench.rs b/benches/simulation_bench.rs new file mode 100644 index 00000000..0c3bae49 --- /dev/null +++ b/benches/simulation_bench.rs @@ -0,0 +1,122 @@ +use criterion::{criterion_group, criterion_main, Criterion}; +pub mod flamegraph; +use flamegraph::flamegraph_profiler::FlamegraphProfiler; +use reveaal::{ + tests::Simulation::helper, + DataReader::component_loader::ModelCache, + ProtobufServer::{ + services::{SimulationStartRequest, SimulationStepRequest}, + ConcreteEcdarBackend, + }, +}; +use tonic::Response; + +static PATH: &str = "samples/json/EcdarUniversity"; + +fn create_step_request( + component_names: &[&str], + components_path: &str, + composition: &str, + last_response: &SimulationStartRequest, +) -> SimulationStepRequest { + let cache = ModelCache::default(); + helper::create_step_request( + component_names, + components_path, + composition, + ConcreteEcdarBackend::handle_start_simulation(last_response.clone(), cache) + .map(Response::new), + ) +} + +fn start_simulation(c: &mut Criterion, id: &str, request: SimulationStartRequest) { + let cache = ModelCache::default(); + c.bench_function(id, |b| { + b.iter(|| ConcreteEcdarBackend::handle_start_simulation(request.to_owned(), cache.clone())) + }); +} + +fn take_simulation_step(c: &mut Criterion, id: &str, request: SimulationStepRequest) { + let cache = ModelCache::default(); + c.bench_function(id, |b| { + b.iter(|| ConcreteEcdarBackend::handle_take_simulation_step(request.clone(), cache.clone())) + }); +} + +fn simulation(c: &mut Criterion) { + let start_request_1 = helper::create_start_request(&["Machine"], PATH, "(Machine)"); + let start_request_2 = + helper::create_start_request(&["HalfAdm1", "HalfAdm2"], PATH, "(HalfAdm1 && HalfAdm2)"); + let start_request_3 = helper::create_start_request( + &["Administration", "Machine", "Researcher"], + PATH, + "(Administration || Machine || Researcher)", + ); + let start_request_4 = helper::create_start_request( + &["HalfAdm1", "HalfAdm2", "Machine", "Researcher"], + "samples/json/EcdarUniversity", + "((HalfAdm1 && HalfAdm2) || Machine || Researcher)", + ); + + let step_request_1 = create_step_request(&["Machine"], PATH, "(Machine)", &start_request_1); + let step_request_2 = create_step_request( + &["HalfAdm1", "HalfAdm2"], + PATH, + "(HalfAdm1 && HalfAdm2)", + &start_request_2, + ); + let step_request_3 = create_step_request( + &["Administration", "Machine", "Researcher"], + PATH, + "(Administration || Machine || Researcher)", + &start_request_3, + ); + let step_request_4 = create_step_request( + &["HalfAdm1", "HalfAdm2", "Machine", "Researcher"], + "samples/json/EcdarUniversity", + "((HalfAdm1 && HalfAdm2) || Machine || Researcher)", + &start_request_4, + ); + + start_simulation(c, "start simulation for (Machine)", start_request_1); + start_simulation( + c, + "start simulation for (HalfAdm1 && HalfAdm2)", + start_request_2, + ); + start_simulation( + c, + "start simulation for (Administration || Machine || Researcher)", + start_request_3, + ); + start_simulation( + c, + "start simulation for ((HalfAdm1 && HalfAdm2) || Machine || Researcher)", + start_request_4, + ); + + take_simulation_step(c, "take simulation step for (Machine)", step_request_1); + take_simulation_step( + c, + "take simulation step for (HalfAdm1 && HalfAdm2)", + step_request_2, + ); + take_simulation_step( + c, + "take simulation step for (Administration || Machine || Researcher)", + step_request_3, + ); + take_simulation_step( + c, + "take simulation step for ((HalfAdm1 && HalfAdm2) || Machine || Researcher)", + step_request_4, + ); +} + +criterion_group! { + name = simulation_benches; + config = Criterion::default().with_profiler(FlamegraphProfiler::new(100)); + targets = simulation +} + +criterion_main!(simulation_benches); diff --git a/samples/json/EcdarUniversity/Components/Machine4.json b/samples/json/EcdarUniversity/Components/Machine4.json new file mode 100644 index 00000000..34461b0d --- /dev/null +++ b/samples/json/EcdarUniversity/Components/Machine4.json @@ -0,0 +1,176 @@ +{ + "name": "Machine4", + "declarations": "clock y;", + "locations": [ + { + "id": "L4", + "nickname": "", + "invariant": "y\u003c\u003d6", + "type": "NORMAL", + "urgency": "NORMAL", + "x": 140.0, + "y": 300.0, + "color": "7", + "nicknameX": 30.0, + "nicknameY": -10.0, + "invariantX": 30.0, + "invariantY": -10.0 + }, + { + "id": "L5", + "nickname": "", + "invariant": "", + "type": "INITIAL", + "urgency": "NORMAL", + "x": 140.0, + "y": 100.0, + "color": "7", + "nicknameX": 30.0, + "nicknameY": -10.0, + "invariantX": 30.0, + "invariantY": 10.0 + } + ], + "edges": [ + { + "id": "E1", + "group": "", + "sourceLocation": "L4", + "targetLocation": "L5", + "status": "OUTPUT", + "select": "", + "guard": "y\u003e\u003d4", + "update": "", + "sync": "cof", + "isLocked": false, + "nails": [ + { + "x": 100.0, + "y": 230.0, + "propertyType": "GUARD", + "propertyX": -70.0, + "propertyY": -10.0 + }, + { + "x": 100.0, + "y": 180.0, + "propertyType": "SYNCHRONIZATION", + "propertyX": -70.0, + "propertyY": -10.0 + } + ] + }, + { + "id": "E2", + "group": "", + "sourceLocation": "L4", + "targetLocation": "L5", + "status": "OUTPUT", + "select": "", + "guard": "", + "update": "", + "sync": "tea", + "isLocked": false, + "nails": [ + { + "x": 210.0, + "y": 200.0, + "propertyType": "SYNCHRONIZATION", + "propertyX": 20.0, + "propertyY": -10.0 + } + ] + }, + { + "id": "E3", + "group": "", + "sourceLocation": "L5", + "targetLocation": "L4", + "status": "INPUT", + "select": "", + "guard": "", + "update": "y\u003d0", + "sync": "coin", + "isLocked": false, + "nails": [ + { + "x": 140.0, + "y": 220.0, + "propertyType": "SYNCHRONIZATION", + "propertyX": 20.0, + "propertyY": -10.0 + }, + { + "x": 140.0, + "y": 190.0, + "propertyType": "UPDATE", + "propertyX": 10.0, + "propertyY": -10.0 + } + ] + }, + { + "id": "E4", + "group": "", + "sourceLocation": "L4", + "targetLocation": "L4", + "status": "INPUT", + "select": "", + "guard": "", + "update": "", + "sync": "coin", + "isLocked": false, + "nails": [ + { + "x": 130.0, + "y": 350.0, + "propertyType": "SYNCHRONIZATION", + "propertyX": -60.0, + "propertyY": -10.0 + }, + { + "x": 160.0, + "y": 350.0, + "propertyType": "NONE", + "propertyX": 0.0, + "propertyY": 0.0 + } + ] + }, + { + "id": "E5", + "group": "", + "sourceLocation": "L5", + "targetLocation": "L5", + "status": "OUTPUT", + "select": "", + "guard": "y\u003c0", + "update": "", + "sync": "tea", + "isLocked": false, + "nails": [ + { + "x": 170.0, + "y": 60.0, + "propertyType": "GUARD", + "propertyX": 10.0, + "propertyY": -20.0 + }, + { + "x": 140.0, + "y": 60.0, + "propertyType": "SYNCHRONIZATION", + "propertyX": -20.0, + "propertyY": -30.0 + } + ] + } + ], + "description": "", + "x": 5.0, + "y": 5.0, + "width": 300.0, + "height": 390.0, + "color": "7", + "includeInPeriodicCheck": false +} \ No newline at end of file diff --git a/samples/json/EcdarUniversity/SystemDeclarations.json b/samples/json/EcdarUniversity/SystemDeclarations.json index 1350fd86..cc638f6b 100644 --- a/samples/json/EcdarUniversity/SystemDeclarations.json +++ b/samples/json/EcdarUniversity/SystemDeclarations.json @@ -1,4 +1,4 @@ { "name": "System Declarations", - "declarations": "system Spec, Machine, Machine2, Machine3, Administration, HalfAdm1, HalfAdm2, Researcher, Adm2;\nIO Spec { grant?, patent! }\nIO Machine { coin?, tea!, cof! }\nIO Machine2 { coin?, tea!, cof! }\nIO Machine3 { coin?, tea!, cof! }\nIO Administration { grant?, pub?, coin!, patent! }\nIO HalfAdm1 { grant?, pub?, coin!, patent! }\nIO HalfAdm2 { grant?, pub?, coin!, patent! }\nIO Researcher { cof?, tea?, pub! }\nIO Adm2 {grant?, pub?, coin!, patent!}" + "declarations": "system Spec, Machine, Machine2, Machine3, Machine4, Administration, HalfAdm1, HalfAdm2, Researcher, Adm2;\nIO Spec { grant?, patent! }\nIO Machine { coin?, tea!, cof! }\nIO Machine2 { coin?, tea!, cof! }\nIO Machine3 { coin?, tea!, cof! }\nIO Administration { grant?, pub?, coin!, patent! }\nIO HalfAdm1 { grant?, pub?, coin!, patent! }\nIO HalfAdm2 { grant?, pub?, coin!, patent! }\nIO Researcher { cof?, tea?, pub! }\nIO Adm2 {grant?, pub?, coin!, patent!}" } \ No newline at end of file diff --git a/samples/json/Simulation/Components/NonConvexFederation.json b/samples/json/Simulation/Components/NonConvexFederation.json new file mode 100644 index 00000000..4371a992 --- /dev/null +++ b/samples/json/Simulation/Components/NonConvexFederation.json @@ -0,0 +1,28 @@ +{ + "name": "NonConvexFederation", + "declarations": "clock x;", + "locations": [ + { + "id": "L4", + "nickname": "", + "invariant": "x < 2 || x > 4", + "type": "INITIAL", + "urgency": "NORMAL", + "x": 0, + "y": 0, + "color": "0", + "nicknameX": 0, + "nicknameY": 0, + "invariantX": 0, + "invariantY": 0 + } + ], + "edges": [], + "description": "", + "x": 0, + "y": 0, + "width": 0, + "height": 0, + "color": "0", + "includeInPeriodicCheck": false +} \ No newline at end of file diff --git a/samples/json/Simulation/Components/SimMachine.json b/samples/json/Simulation/Components/SimMachine.json new file mode 100644 index 00000000..1f4d32f2 --- /dev/null +++ b/samples/json/Simulation/Components/SimMachine.json @@ -0,0 +1,107 @@ +{ + "name": "SimMachine", + "declarations": "clock x;", + "locations": [ + { + "id": "L1", + "nickname": "", + "invariant": "", + "type": "INITIAL", + "urgency": "NORMAL", + "x": 140.0, + "y": 300.0, + "color": "7", + "nicknameX": 30.0, + "nicknameY": -10.0, + "invariantX": 30.0, + "invariantY": -10.0 + }, + { + "id": "L2", + "nickname": "", + "invariant": "", + "type": "NORMAL", + "urgency": "NORMAL", + "x": 140.0, + "y": 100.0, + "color": "7", + "nicknameX": 30.0, + "nicknameY": -10.0, + "invariantX": 30.0, + "invariantY": 10.0 + }, + { + "id": "L3", + "nickname": "", + "invariant": "", + "type": "NORMAL", + "urgency": "NORMAL", + "x": 140.0, + "y": 100.0, + "color": "7", + "nicknameX": 30.0, + "nicknameY": -10.0, + "invariantX": 30.0, + "invariantY": 10.0 + } + + ], + "edges": [ + { + "id": "E1", + "group": "", + "sourceLocation": "L1", + "targetLocation": "L2", + "status": "OUTPUT", + "select": "", + "guard": "1\u003cx", + "update": "", + "sync": "a", + "isLocked": false, + "nails": [ + { + "x": 100.0, + "y": 230.0, + "propertyType": "GUARD", + "propertyX": -70.0, + "propertyY": -10.0 + }, + { + "x": 100.0, + "y": 180.0, + "propertyType": "SYNCHRONIZATION", + "propertyX": -70.0, + "propertyY": -10.0 + } + ] + }, + { + "id": "E2", + "group": "", + "sourceLocation": "L1", + "targetLocation": "L3", + "status": "OUTPUT", + "select": "", + "guard": "x\u003c\u003d1", + "update": "", + "sync": "a", + "isLocked": false, + "nails": [ + { + "x": 210.0, + "y": 200.0, + "propertyType": "SYNCHRONIZATION", + "propertyX": 20.0, + "propertyY": -10.0 + } + ] + } + ], + "description": "", + "x": 5.0, + "y": 5.0, + "width": 300.0, + "height": 390.0, + "color": "7", + "includeInPeriodicCheck": false + } \ No newline at end of file diff --git a/src/DataReader/component_loader.rs b/src/DataReader/component_loader.rs index f2e6d6b9..9aa3df30 100644 --- a/src/DataReader/component_loader.rs +++ b/src/DataReader/component_loader.rs @@ -1,17 +1,21 @@ use lru::LruCache; use crate::component::Component; +use crate::xml_parser; use crate::DataReader::json_reader; use crate::DataReader::json_writer::component_to_json_file; use crate::DataReader::xml_parser::parse_xml_from_file; use crate::ModelObjects::queries::Query; use crate::ModelObjects::system_declarations::SystemDeclarations; +use crate::ProtobufServer::services; use crate::ProtobufServer::services::query_request::Settings; use crate::System::input_enabler; use std::collections::HashMap; use std::num::NonZeroUsize; use std::sync::{Arc, Mutex}; +use super::proto_reader::components_info_to_components; + type ComponentsMap = HashMap; struct ComponentTuple { @@ -132,12 +136,66 @@ impl ComponentContainer { } } + /// Creates a [`ComponentContainer`] from a [`services::ComponentsInfo`]. + pub fn from_info( + components_info: &services::ComponentsInfo, + ) -> Result { + let components = components_info_to_components(components_info); + let component_container = Self::from_components(components); + Ok(component_container) + } + + /// Creates a [`ComponentContainer`] from a [`Vec`] of [`Component`]s + pub fn from_components(components: Vec) -> ComponentContainer { + let mut comp_hashmap = HashMap::::new(); + for mut component in components { + log::trace!("Adding comp {} to container", component.get_name()); + + component.create_edge_io_split(); + let inputs: Vec<_> = component + .get_input_actions() + .into_iter() + .map(|channel| channel.name) + .collect(); + input_enabler::make_input_enabled(&mut component, &inputs); + comp_hashmap.insert(component.get_name().to_string(), component); + } + ComponentContainer::new(Arc::new(comp_hashmap)) + } + /// Sets the settings pub(crate) fn set_settings(&mut self, settings: Settings) { self.settings = Some(settings); } } +pub fn parse_components_if_some( + proto_component: &services::Component, +) -> Result, tonic::Status> { + if let Some(rep) = &proto_component.rep { + match rep { + services::component::Rep::Json(json) => parse_json_component(json), + services::component::Rep::Xml(xml) => Ok(parse_xml_components(xml)), + } + } else { + Ok(vec![]) + } +} + +fn parse_json_component(json: &str) -> Result, tonic::Status> { + match json_reader::json_to_component(json) { + Ok(comp) => Ok(vec![comp]), + Err(_) => Err(tonic::Status::invalid_argument( + "Failed to parse json component", + )), + } +} + +fn parse_xml_components(xml: &str) -> Vec { + let (comps, _, _) = xml_parser::parse_xml_from_str(xml); + comps +} + pub trait ProjectLoader: ComponentLoader { fn get_declarations(&self) -> &SystemDeclarations; fn get_queries(&self) -> &Vec; diff --git a/src/DataReader/mod.rs b/src/DataReader/mod.rs index 79146a8d..ab2eff35 100644 --- a/src/DataReader/mod.rs +++ b/src/DataReader/mod.rs @@ -4,5 +4,7 @@ pub mod json_writer; pub mod parse_edge; pub mod parse_invariant; pub mod parse_queries; +pub mod proto_reader; +pub mod proto_writer; pub mod serialization; pub mod xml_parser; diff --git a/src/DataReader/parse_queries.rs b/src/DataReader/parse_queries.rs index 83c9fa65..3f5b4f73 100644 --- a/src/DataReader/parse_queries.rs +++ b/src/DataReader/parse_queries.rs @@ -109,7 +109,7 @@ pub fn build_query_from_pair(pair: pest::iterators::Pair) -> QueryExpressi } } -fn build_expression_from_pair(pair: pest::iterators::Pair) -> QueryExpression { +pub fn build_expression_from_pair(pair: pest::iterators::Pair) -> QueryExpression { match pair.as_rule() { Rule::term => build_term_from_pair(pair), Rule::parenthesizedExp => { diff --git a/src/DataReader/proto_reader.rs b/src/DataReader/proto_reader.rs new file mode 100644 index 00000000..05bf908f --- /dev/null +++ b/src/DataReader/proto_reader.rs @@ -0,0 +1,273 @@ +use edbm::util::constraints::{Conjunction, Constraint, Disjunction, Inequality, RawInequality}; +use edbm::zones::OwnedFederation; + +use crate::component::{Component, Edge, State}; +use crate::ProtobufServer::services::{ + ComponentClock as ProtoComponentClock, ComponentsInfo, Conjunction as ProtoConjunction, + Constraint as ProtoConstraint, Decision as ProtoDecision, Disjunction as ProtoDisjunction, + Edge as ProtoEdge, Federation as ProtoFederation, LocationTuple as ProtoLocationTuple, + SimulationInfo, State as ProtoState, +}; +use crate::Simulation::decision::Decision; +use crate::TransitionSystems::transition_system::component_loader_to_transition_system; +use crate::TransitionSystems::{LocationID, LocationTuple, TransitionSystemPtr}; + +use super::component_loader::{parse_components_if_some, ComponentContainer}; + +/// Borrows a [`SimulationInfo`] and returns the corresponding [`TransitionsSystemPtr`]. +/// +/// # Panics +/// If: +/// - `simulation_info.components_info` is `None`. +/// - building the [`ComponentContainer`] fails. +pub fn simulation_info_to_transition_system( + simulation_info: &SimulationInfo, +) -> TransitionSystemPtr { + let composition = simulation_info.component_composition.to_owned(); + let component_info = simulation_info.components_info.as_ref().unwrap(); + + let mut component_container = ComponentContainer::from_info(component_info).unwrap(); + + component_loader_to_transition_system(&mut component_container, &composition) +} + +/// Borrows a [`ComponentsInfo`] and returns the corresponding [`Vec`] of [`Component`]s. +pub fn components_info_to_components(components_info: &ComponentsInfo) -> Vec { + components_info + .components + .iter() + .flat_map(parse_components_if_some) + .flatten() + .collect() +} + +/// Consumes a [`ProtoDecision`] and the borrows the [`TransitionsSystemPtr`] it belongs to and returns the corresponding [`Decision`]. +/// +/// # Panics +/// If: +/// - `proto_decision.source` is `None`. +/// - `proto_decision.edge` is `None`. +pub fn proto_decision_to_decision( + proto_decision: ProtoDecision, + system: &TransitionSystemPtr, + components: Vec, +) -> Decision { + let proto_state: ProtoState = proto_decision.source.unwrap(); + let state = proto_state_to_state(proto_state, system); + + let proto_edge: ProtoEdge = proto_decision.edge.unwrap(); + let decided = proto_edge_to_edge(proto_edge, components); + + Decision::new(state, decided) +} + +/// Consumes a [`ProtoState`] and the borrows the [`TransitionsSystemPtr`] it belongs to and returns the corresponding [`State`]. +/// +/// # Panics +/// If: +/// - `state.federation` is `None`. +/// - `state.location_tuple` is `None`. +pub fn proto_state_to_state(state: ProtoState, system: &TransitionSystemPtr) -> State { + let proto_federation: ProtoFederation = state.federation.unwrap(); + let federation: OwnedFederation = + proto_federation_to_owned_federation(proto_federation, system); + + let proto_location_tuple: ProtoLocationTuple = state.location_tuple.unwrap(); + let location_tuple = + proto_location_tuple_to_location_tuple(&proto_location_tuple, system).unwrap(); + + State::create(location_tuple, federation) +} + +fn proto_location_tuple_to_location_tuple( + location_tuple: &ProtoLocationTuple, + system: &TransitionSystemPtr, +) -> Option { + let id_looking_for: Vec = location_tuple + .locations + .iter() + .map(|l| LocationID::Simple { + location_id: l.id.to_string(), + component_id: l + .specific_component + .as_ref() + .map(|c| c.component_name.to_string()), + }) + .collect(); + + system + .get_all_locations() + .into_iter() + .map(|tuple| (tuple.id.clone(), tuple)) + .map(|(id, tuple)| (id.inorder_vec_tranform(), tuple)) + .find(|(id, _)| id.iter().eq(id_looking_for.iter())) + .map(|(_, tuple)| tuple) +} + +fn proto_edge_to_edge(proto_edge: ProtoEdge, components: Vec) -> Edge { + components + .into_iter() + .map(|c| c.get_edges().to_owned()) + .reduce(|acc, es| acc.into_iter().chain(es.into_iter()).collect()) + .unwrap() + .into_iter() + .find(|e| e.id == proto_edge.id) + .unwrap() +} + +fn proto_constraint_to_constraint( + proto_constraint: ProtoConstraint, + system: &TransitionSystemPtr, +) -> Constraint { + fn determine_index(clock: ProtoComponentClock, system: &TransitionSystemPtr) -> usize { + if clock.clock_name == "0" && clock.specific_component.is_none() { + 0 + } else { + system + .clock_name_and_component_to_index( + &clock.clock_name, + &clock.specific_component.unwrap().component_name, + ) + .unwrap() + } + } + + let x_clock = proto_constraint.x.unwrap(); + let i = determine_index(x_clock, system); + + let y_clock = proto_constraint.y.unwrap(); + let j = determine_index(y_clock, system); + + let inequality = match proto_constraint.strict { + true => Inequality::LS(proto_constraint.c), + false => Inequality::LE(proto_constraint.c), + }; + + let ineq: RawInequality = RawInequality::from_inequality(&inequality); + Constraint::new(i, j, ineq) +} + +fn proto_federation_to_owned_federation( + proto_federation: ProtoFederation, + system: &TransitionSystemPtr, +) -> OwnedFederation { + let proto_disjunction: ProtoDisjunction = proto_federation.disjunction.unwrap(); + + let proto_conjunctions: Vec = proto_disjunction.conjunctions; + let proto_constraints: Vec> = proto_conjunctions + .iter() + .map(|conjunction| conjunction.constraints.clone()) + .collect(); + + let mut constraints: Vec> = Vec::new(); + + for vec_proto_constraint in proto_constraints { + let mut constraint_vec: Vec = Vec::new(); + for proto_constraint in vec_proto_constraint { + let constraint = proto_constraint_to_constraint(proto_constraint, system); + constraint_vec.push(constraint); + } + constraints.push(constraint_vec); + } + + let mut conjunctions: Vec = Vec::new(); + + for constraint_vec in constraints { + let conjunction = Conjunction::new(constraint_vec); + conjunctions.push(conjunction); + } + + let disjunction: Disjunction = Disjunction::new(conjunctions); + OwnedFederation::from_disjunction(&disjunction, system.get_dim()) +} + +#[cfg(test)] +mod tests { + use crate::{ + tests::Simulation::test_data::{ + create_EcdarUniversity_Machine3and1_with_nonempty_Federation_Decision, + create_EcdarUniversity_Machine_Decision, create_EcdarUniversity_Machine_component, + create_EcdarUniversity_Machine_system, + create_EcdarUniversity_Machine_with_nonempty_Federation_Decision, + }, + DataReader::{json_reader::read_json_component, proto_reader::proto_decision_to_decision}, + Simulation::decision::Decision, + TransitionSystems::transition_system::components_to_transition_system, + }; + + #[test] + fn proto_decision_to_decision__ProtoDecision_with_universal_ProtoFederation__returns_correct_Decision( + ) { + // Arrange + let proto_decision = create_EcdarUniversity_Machine_Decision(); + let system = create_EcdarUniversity_Machine_system(); + + let component = create_EcdarUniversity_Machine_component(); + let expected_edge = component.find_edge_from_id("E29").unwrap(); + let expected_source = system.get_initial_state().unwrap(); + let expected_decision = Decision::new(expected_source, expected_edge.to_owned()); + + // Act + let actual_decision = proto_decision_to_decision(proto_decision, &system, vec![component]); + + // Assert + assert_eq!( + format!("{:?}", actual_decision), + format!("{:?}", expected_decision) + ); + } + + #[test] + fn proto_decision_to_decision__ProtoDecision_with_nonuniversal_ProtoFederation__returns_correct_Decision( + ) { + // Arrange + let proto_decision = create_EcdarUniversity_Machine_with_nonempty_Federation_Decision(); + let system = create_EcdarUniversity_Machine_system(); + + let component = create_EcdarUniversity_Machine_component(); + let expected_edge = component.find_edge_from_id("E29").unwrap(); + let action = "tea"; + let mut expected_source = system.get_initial_state().unwrap(); + let transition = + system.next_transitions_if_available(expected_source.get_location(), action); + transition + .first() + .unwrap() + .use_transition(&mut expected_source); + let expected_decision = Decision::new(expected_source, expected_edge.to_owned()); + + // Act + let actual_decision = proto_decision_to_decision(proto_decision, &system, vec![component]); + + // Assert + assert_eq!( + format!("{:?}", actual_decision), + format!("{:?}", expected_decision) + ); + } + + #[test] + fn proto_decision_to_decision__ProtoDecision_with_conjunction_of_components__returns_correct_Decision( + ) { + // Arrange + let machine3 = read_json_component("samples/json/EcdarUniversity", "Machine3"); + let machine = read_json_component("samples/json/EcdarUniversity", "Machine"); + let components = vec![machine3, machine.clone()]; + let system = components_to_transition_system(components.clone(), "( Machine3 && Machine )"); + let proto_decision = + create_EcdarUniversity_Machine3and1_with_nonempty_Federation_Decision(); + + let expected_edge = machine.find_edge_from_id("E29").unwrap(); + let expected_source = system.get_initial_state().unwrap(); + let expected_decision = Decision::new(expected_source, expected_edge.to_owned()); + + // Act + let actual_decision = proto_decision_to_decision(proto_decision, &system, components); + + // Assert + assert_eq!( + format!("{:?}", actual_decision), + format!("{:?}", expected_decision) + ); + } +} diff --git a/src/DataReader/proto_writer.rs b/src/DataReader/proto_writer.rs new file mode 100644 index 00000000..3482a57c --- /dev/null +++ b/src/DataReader/proto_writer.rs @@ -0,0 +1,334 @@ +use edbm::{ + util::constraints::{Conjunction, Constraint, Disjunction}, + zones::OwnedFederation, +}; + +use crate::{ + component::State, + ProtobufServer::services::{ + ComponentClock, Conjunction as ProtoConjunction, Constraint as ProtoConstraint, + DecisionPoint as ProtoDecisionPoint, Disjunction as ProtoDisjunction, Edge as ProtoEdge, + Federation as ProtoFederation, Location as ProtoLocation, + LocationTuple as ProtoLocationTuple, SpecificComponent, State as ProtoState, + }, + Simulation::decision_point::DecisionPoint, + TransitionSystems::{LocationID, LocationTuple, TransitionSystemPtr}, +}; + +/// Returns the [`ProtoDecisionPoint`] equivalent to the given [`DecisionPoint`] in the context of the given [`TransitionsSystemPtr`]. +pub fn decision_point_to_proto_decision_point( + decision_point: &DecisionPoint, + system: &TransitionSystemPtr, +) -> ProtoDecisionPoint { + let source = state_to_proto_state(decision_point.source(), system); + + let edges = decision_point + .possible_decisions() + .iter() + .map(edge_id_to_proto_edge) + .collect(); + + ProtoDecisionPoint { + source: Some(source), + edges, + } +} + +fn state_to_proto_state(s: &State, system: &TransitionSystemPtr) -> ProtoState { + let location_tuple = location_tuple_to_proto_location_tuple(s.get_location()); + let federation = federation_to_proto_federation(s.zone_ref(), system); + + ProtoState { + location_tuple: Some(location_tuple), + federation: Some(federation), + } +} + +fn location_tuple_to_proto_location_tuple(l: &LocationTuple) -> ProtoLocationTuple { + ProtoLocationTuple { + locations: location_id_to_proto_location_vec(&l.id), + } +} + +fn location_id_to_proto_location_vec(id: &LocationID) -> Vec { + match id { + LocationID::Simple { + location_id, + component_id, + } => vec![ProtoLocation { + id: location_id.to_string(), + specific_component: Some(SpecificComponent { + component_name: component_id.as_ref().unwrap_or(&"".to_string()).to_string(), // TODO this looks disgusting + component_index: 0, + }), + }], + LocationID::Conjunction(l, r) + | LocationID::Composition(l, r) + | LocationID::Quotient(l, r) => location_id_to_proto_location_vec(l) + .into_iter() + .chain(location_id_to_proto_location_vec(r).into_iter()) + .collect(), + LocationID::AnyLocation() => vec![], + } +} + +fn federation_to_proto_federation( + federation: &OwnedFederation, + system: &TransitionSystemPtr, +) -> ProtoFederation { + ProtoFederation { + disjunction: Some(disjunction_to_proto_disjunction( + &federation.minimal_constraints(), + system, + )), + } +} + +fn disjunction_to_proto_disjunction( + disjunction: &Disjunction, + system: &TransitionSystemPtr, +) -> ProtoDisjunction { + ProtoDisjunction { + conjunctions: disjunction + .conjunctions + .iter() + .map(|conjunction| conjunction_to_proto_conjunction(conjunction, system)) + .collect(), + } +} + +fn conjunction_to_proto_conjunction( + conjunction: &Conjunction, + system: &TransitionSystemPtr, +) -> ProtoConjunction { + ProtoConjunction { + constraints: conjunction + .constraints + .iter() + .map(|constraint| constraint_to_proto_constraint(constraint, system)) + .collect(), + } +} + +fn constraint_to_proto_constraint( + constraint: &Constraint, + system: &TransitionSystemPtr, +) -> ProtoConstraint { + fn clock_name(clock_name_and_component: Option<&(String, String)>) -> String { + const ZERO_CLOCK_NAME: &str = "0"; + match clock_name_and_component { + Some((clock_name, _)) => clock_name.to_string(), + // If an index does not correspond to an index we assume it's the zero clock + None => ZERO_CLOCK_NAME.to_string(), + } + } + + fn clock_component( + clock_name_and_component: Option<&(String, String)>, + ) -> Option { + clock_name_and_component.map(|x| SpecificComponent { + component_name: x.1.to_string(), + component_index: 0, + }) + } + + let x = system.index_to_clock_name_and_component(&constraint.i); + let y = system.index_to_clock_name_and_component(&constraint.j); + + ProtoConstraint { + x: Some(ComponentClock { + specific_component: clock_component(x.as_ref()), + clock_name: clock_name(x.as_ref()), + }), + y: Some(ComponentClock { + specific_component: clock_component(y.as_ref()), + clock_name: clock_name(y.as_ref()), + }), + strict: constraint.ineq().is_strict(), + c: constraint.ineq().bound(), + } +} + +fn edge_id_to_proto_edge(edge: &String) -> ProtoEdge { + ProtoEdge { + id: edge.to_string(), + specific_component: None, // Edge id's are unique thus this is not needed + } +} + +#[cfg(test)] +mod tests { + use super::{decision_point_to_proto_decision_point, state_to_proto_state}; + use crate::component::Component; + use crate::tests::Simulation::test_data::{ + create_EcdarUniversity_Machine_system, create_decision_point_after_taking_E5, + create_initial_decision_point, get_composition_response_Administration_Machine_Researcher, + initial_transition_decision_point_EcdarUniversity_Machine, + }; + use crate::DataReader::proto_reader::proto_state_to_state; + use crate::TransitionSystems::transition_system::components_to_transition_system; + use crate::{ + DataReader::json_reader::read_json_component, Simulation::decision_point::DecisionPoint, + }; + use test_case::test_case; + + #[test_case( + vec![ + read_json_component("samples/json/EcdarUniversity", "Machine"), + ], + "(Machine)"; + "(Machine)" + )] + #[test_case( + vec![ + read_json_component("samples/json/EcdarUniversity", "Administration"), + read_json_component("samples/json/EcdarUniversity", "Machine"), + ], + "(Administration || Machine)"; + "(Administration || Machine)" + )] + #[test_case( + vec![ + read_json_component("samples/json/EcdarUniversity", "HalfAdm1"), + read_json_component("samples/json/EcdarUniversity", "HalfAdm2"), + ], + "(HalfAdm1 && HalfAdm2)"; + "(HalfAdm1 && HalfAdm2)" + )] + #[test_case( + vec![ + read_json_component("samples/json/Simulation", "NonConvexFederation"), + ], + "(NonConvexFederation)"; + "(NonConvexFederation)" + )] + fn state_to_proto_state_to_state_is_same_state(components: Vec, composition: &str) { + let system = components_to_transition_system(components, composition); + let initial = system.get_initial_state().unwrap(); + + // exploit the fact that: + // x == convert (convert x) + assert_eq!( + format!("{:?}", initial), + format!( + "{:?}", + proto_state_to_state(state_to_proto_state(&initial, &system), &system) + ) + ); + } + + // TODO: this specific case fails because: + // TransitionSystem::clock_name_and_component_to_index_map can only map component and clock to one clock... + #[ignore = "won't fix, see comment"] + #[test_case( + vec![ + read_json_component("samples/json/EcdarUniversity", "Machine"), + ], + "(Machine && Machine)"; + "(Machine && Machine)" + )] + fn state_to_proto_state_to_state_is_same_state_____dup_for_ignore( + components: Vec, + composition: &str, + ) { + let system = components_to_transition_system(components, composition); + let initial = system.get_initial_state().unwrap(); + + // exploit the fact that: + // x == convert (convert x) + assert_eq!( + format!("{:?}", initial), + format!( + "{:?}", + proto_state_to_state(state_to_proto_state(&initial, &system), &system) + ) + ); + } + + #[test] + fn decision_point_to_proto_decision_point__initial_DecisionPoint_EcdarUniversity_Administration_par_Machine_par_Researcher__returns_correct_ProtoDecisionPoint( + ) { + // Arrange + let project_path = "samples/json/EcdarUniversity"; + + let administration = read_json_component(project_path, "Administration"); + let machine = read_json_component(project_path, "Machine"); + let researcher = read_json_component(project_path, "Researcher"); + + let combined = vec![administration, machine, researcher]; + let composition = "(Administration || Machine || Researcher)"; + + let system = components_to_transition_system(combined, composition); + + let decision_point = DecisionPoint::new( + system.get_initial_state().unwrap(), + vec![ + "E11".to_string(), + "E16".to_string(), + "E29".to_string(), + "E44".to_string(), + ], + ); + + let binding = get_composition_response_Administration_Machine_Researcher() + .unwrap() + .into_inner(); + let expected = binding.new_decision_points.first().unwrap(); + + // Act + let actual = decision_point_to_proto_decision_point(&decision_point, &system); + + // Assert + assert_eq!(format!("{:?}", actual), format!("{:?}", expected)) + } + + #[test] + fn decision_point_to_proto_decision_point__initial_DecisionPoint_EcdarUniversity_Machine__returns_correct_ProtoDecisionPoint( + ) { + // Arrange + let transitionDecisionPoint = initial_transition_decision_point_EcdarUniversity_Machine(); + let system = create_EcdarUniversity_Machine_system(); + + let decisionPoint = DecisionPoint::new( + transitionDecisionPoint.source().to_owned(), + vec!["E27".to_string(), "E29".to_string()], + ); + + let expected = create_initial_decision_point(); + + // Act + let actual = decision_point_to_proto_decision_point(&decisionPoint, &system); + + // Assert + assert_eq!(actual.source, expected.source); + assert_eq!(actual.edges.len(), 2); + assert!(actual.edges.contains(&expected.edges[0])); + assert!(actual.edges.contains(&expected.edges[1])); + } + + #[test] + fn decision_point_to_proto_decision_point__initial_DecisionPoint_EcdarUniversity_Machine_after_tea__returns_correct_ProtoDecisionPoint( + ) { + // Arrange + let system = create_EcdarUniversity_Machine_system(); + let mut after_tea = system.get_initial_state().unwrap(); + let action = "tea"; + let binding = system.next_transitions_if_available(after_tea.get_location(), action); + let tea_transition = binding.first().unwrap(); + tea_transition.use_transition(&mut after_tea); + + let decisionPoint = + DecisionPoint::new(after_tea, vec!["E27".to_string(), "E29".to_string()]); + + let expected = create_decision_point_after_taking_E5(); + + // Act + let actual = decision_point_to_proto_decision_point(&decisionPoint, &system); + + // Assert + assert_eq!(actual.source, expected.source); + assert_eq!(actual.edges.len(), 2); + assert!(actual.edges.contains(&expected.edges[0])); + assert!(actual.edges.contains(&expected.edges[1])); + } +} diff --git a/src/ModelObjects/component.rs b/src/ModelObjects/component.rs index 3afc6b5a..b285ac4e 100644 --- a/src/ModelObjects/component.rs +++ b/src/ModelObjects/component.rs @@ -18,7 +18,6 @@ use log::info; use serde::{Deserialize, Serialize}; use std::collections::{HashMap, HashSet}; use std::fmt; - /// The basic struct used to represent components read from either Json or xml #[derive(Debug, Deserialize, Serialize, Clone, Eq, PartialEq)] #[serde(into = "DummyComponent")] @@ -241,6 +240,11 @@ impl Component { max_bounds } + /// Find [`Edge`] in the component given the edges `id`. + pub fn find_edge_from_id(&self, id: &str) -> Option<&Edge> { + self.get_edges().iter().find(|e| e.id.contains(id)) + } + /// Used in initial setup to split edges based on their sync type pub fn create_edge_io_split(&mut self) { let mut o_edges = vec![]; @@ -520,6 +524,15 @@ impl Transition { false } + /// Returns the resulting [`State`] when using a transition in the given [`State`] + pub fn use_transition_alt(&self, state: &State) -> Option { + let mut state = state.to_owned(); + match self.use_transition(&mut state) { + true => Some(state), + false => None, + } + } + pub fn combinations( left: &Vec, right: &Vec, diff --git a/src/ProtobufServer/ecdar_backend.rs b/src/ProtobufServer/ecdar_backend.rs index 310bddda..4db2772f 100644 --- a/src/ProtobufServer/ecdar_backend.rs +++ b/src/ProtobufServer/ecdar_backend.rs @@ -93,15 +93,17 @@ impl EcdarBackend for ConcreteEcdarBackend { async fn start_simulation( &self, - _request: Request, + request: Request, ) -> Result, Status> { - unimplemented!(); + self.handle_request(request, Self::handle_start_simulation) + .await } async fn take_simulation_step( &self, - _request: Request, + request: Request, ) -> Result, Status> { - unimplemented!(); + self.handle_request(request, Self::handle_take_simulation_step) + .await } } diff --git a/src/ProtobufServer/ecdar_requests/mod.rs b/src/ProtobufServer/ecdar_requests/mod.rs index e0b85158..d652cd2e 100644 --- a/src/ProtobufServer/ecdar_requests/mod.rs +++ b/src/ProtobufServer/ecdar_requests/mod.rs @@ -1 +1,3 @@ mod send_query; +mod start_simulation; +mod take_simulation_step; diff --git a/src/ProtobufServer/ecdar_requests/start_simulation.rs b/src/ProtobufServer/ecdar_requests/start_simulation.rs new file mode 100644 index 00000000..c13c8cb0 --- /dev/null +++ b/src/ProtobufServer/ecdar_requests/start_simulation.rs @@ -0,0 +1,34 @@ +use crate::DataReader::component_loader::ModelCache; +use crate::DataReader::proto_reader::simulation_info_to_transition_system; +use crate::DataReader::proto_writer::decision_point_to_proto_decision_point; +use crate::ProtobufServer::services::{SimulationStartRequest, SimulationStepResponse}; +use crate::ProtobufServer::ConcreteEcdarBackend; +use crate::Simulation::decision_point::DecisionPoint; + +use tonic::Status; + +impl ConcreteEcdarBackend { + /// Handles a start simulation request: Responding with the initial decision point in the transition system given in the `request`. + pub fn handle_start_simulation( + request: SimulationStartRequest, + _cache: ModelCache, // TODO should be used... + ) -> Result { + fn option_to_vec(option: Option) -> Vec { + match option { + Some(item) => vec![item], + None => vec![], + } + } + + let simulation_info = request.simulation_info.unwrap(); + + let transition_system = simulation_info_to_transition_system(&simulation_info); + + let initial = DecisionPoint::initial(&transition_system) + .map(|i| decision_point_to_proto_decision_point(&i, &transition_system)); + + Ok(SimulationStepResponse { + new_decision_points: option_to_vec(initial), + }) + } +} diff --git a/src/ProtobufServer/ecdar_requests/take_simulation_step.rs b/src/ProtobufServer/ecdar_requests/take_simulation_step.rs new file mode 100644 index 00000000..bb3d8f62 --- /dev/null +++ b/src/ProtobufServer/ecdar_requests/take_simulation_step.rs @@ -0,0 +1,49 @@ +use tonic::Status; + +use crate::{ + DataReader::{ + component_loader::ModelCache, + proto_reader::{ + components_info_to_components, proto_decision_to_decision, + simulation_info_to_transition_system, + }, + proto_writer::decision_point_to_proto_decision_point, + }, + ProtobufServer::{ + services::{SimulationStepRequest, SimulationStepResponse}, + ConcreteEcdarBackend, + }, +}; + +impl ConcreteEcdarBackend { + /// Handles a take simulation step request: + /// Given a `decision` and transition system in the `request`, walk along the decided edge and respond with the resulting decision points. + pub fn handle_take_simulation_step( + request: SimulationStepRequest, + _cache: ModelCache, // TODO should be used... + ) -> Result { + let request_message = request; + let simulation_info = request_message.simulation_info.unwrap(); + + let components = + components_info_to_components(simulation_info.components_info.as_ref().unwrap()); + + let system = simulation_info_to_transition_system(&simulation_info); + + let chosen_decision = request_message.chosen_decision.unwrap(); + let chosen_decision = proto_decision_to_decision(chosen_decision, &system, components); + + let decision_points = chosen_decision.resolve(&system); + + let decision_points = decision_points + .into_iter() + .map(|dp| decision_point_to_proto_decision_point(&dp, &system)) + .collect(); + + let simulation_step_response = SimulationStepResponse { + new_decision_points: decision_points, + }; + + Ok(simulation_step_response) + } +} diff --git a/src/Simulation/decision.rs b/src/Simulation/decision.rs new file mode 100644 index 00000000..4085ab48 --- /dev/null +++ b/src/Simulation/decision.rs @@ -0,0 +1,38 @@ +use crate::{ + component::{Edge, State}, + TransitionSystems::TransitionSystemPtr, +}; + +use super::{decision_point::DecisionPoint, transition_decision::TransitionDecision}; + +/// Represent a decision in a any composition of components, that has been taken: In the current `source` state I have `decided` to use this [`Edge`]. +#[derive(Debug)] +pub struct Decision { + source: State, + decided: Edge, +} + +impl Decision { + pub fn new(source: State, decided: Edge) -> Self { + Self { source, decided } + } + + pub fn source(&self) -> &State { + &self.source + } + + pub fn decided(&self) -> &Edge { + &self.decided + } + + /// Resolves a [`Decision`]: use the `decided` [`Edge`] and returns a [`Vec`] of the [`DecisionPoint`]s of the destination [`State`]s. + /// + /// Some `decided` [`Edge`]s lead to ambiguity ie. they correspond to multiple [`Transition`]s. Thus one [`Edge`] can lead to multiple [`State`]s. + pub fn resolve(&self, system: &TransitionSystemPtr) -> Vec { + TransitionDecision::from(self, system) + .into_iter() + .filter_map(|transition_decision| transition_decision.resolve(system)) + .map(|transition_decision_point| DecisionPoint::from(&transition_decision_point)) + .collect() + } +} diff --git a/src/Simulation/decision_point.rs b/src/Simulation/decision_point.rs new file mode 100644 index 00000000..8801cb5b --- /dev/null +++ b/src/Simulation/decision_point.rs @@ -0,0 +1,86 @@ +use itertools::Itertools; +use regex::Regex; + +use crate::{ + component::State, + TransitionSystems::{TransitionID, TransitionSystemPtr}, +}; + +use super::transition_decision_point::TransitionDecisionPoint; + +/// Represents a decision in any composition of components: In the current `source` state there is a decision of using one of the `possible_decisions`. +#[derive(Clone, Debug)] +pub struct DecisionPoint { + source: State, + possible_decisions: Vec, +} + +impl DecisionPoint { + pub fn new(source: State, possible_decisions: Vec) -> Self { + Self { + source, + possible_decisions, + } + } + + pub fn source(&self) -> &State { + &self.source + } + + pub fn possible_decisions(&self) -> &[String] { + self.possible_decisions.as_ref() + } + + /// Returns the initial [`DecisionPoint`] in the given [`TransitionSystemPrt`]. + pub fn initial(system: &TransitionSystemPtr) -> Option { + TransitionDecisionPoint::initial(system).map(|initial| DecisionPoint::from(&initial)) + } +} + +impl From<&TransitionDecisionPoint> for DecisionPoint { + fn from(transition_decision_point: &TransitionDecisionPoint) -> Self { + fn is_edge(x: &str) -> bool { + let is_not_edge_regex = Regex::new("(input_).*").unwrap(); // `.unwrap()` always return `Some(...)` here + !is_not_edge_regex.is_match(x) + } + let possible_decisions = transition_decision_point + .possible_decisions() + .iter() + .flat_map(|transition| transition.id.get_leaves().concat()) + .filter_map(|transition_id| match transition_id { + TransitionID::Simple(v) => Some(v), + TransitionID::None => None, + _ => panic!("transition_id should not be other than Simple(_) and None"), + }) + .unique() + .filter(|x| is_edge(x)) + .sorted() + .collect(); + + DecisionPoint { + source: transition_decision_point.source().clone(), + possible_decisions, + } + } +} + +#[cfg(test)] +mod test { + use crate::tests::Simulation::test_data::initial_transition_decision_point_EcdarUniversity_Machine; + + use super::DecisionPoint; + + #[test] + fn from__initial_EcdarUniversity_Machine__returns_correct_DecisionPoint() { + // Arrange + let transition_decision_point = initial_transition_decision_point_EcdarUniversity_Machine(); + + // Act + let actual = DecisionPoint::from(&transition_decision_point); + + // Assert + assert_eq!(actual.possible_decisions.len(), 2); + assert!(actual.possible_decisions().contains(&"E27".to_string())); + assert!(actual.possible_decisions().contains(&"E29".to_string())); + } +} diff --git a/src/Simulation/mod.rs b/src/Simulation/mod.rs index a6f56767..5058751e 100644 --- a/src/Simulation/mod.rs +++ b/src/Simulation/mod.rs @@ -1 +1,5 @@ +pub mod decision; +pub mod decision_point; pub mod graph_layout; +pub mod transition_decision; +pub mod transition_decision_point; diff --git a/src/Simulation/transition_decision.rs b/src/Simulation/transition_decision.rs new file mode 100644 index 00000000..4445cba1 --- /dev/null +++ b/src/Simulation/transition_decision.rs @@ -0,0 +1,203 @@ +use crate::{ + component::{State, Transition}, + TransitionSystems::{TransitionID, TransitionSystemPtr}, +}; + +use super::{decision::Decision, transition_decision_point::TransitionDecisionPoint}; + +/// Represent a decision in a transition system, that has been taken: In the current `source` [`State`] I have `decided` to use this [`Transition`]. +#[derive(Debug)] +pub struct TransitionDecision { + source: State, + decided: Transition, +} + +impl TransitionDecision { + /// Returns all [`TransitionDecision`]s equivalent to the given [`Decision`] in relation to the given [`TransitionSystemPtr`]. + pub fn from(decision: &Decision, system: &TransitionSystemPtr) -> Vec { + fn contains(transition: &Transition, edge_id: &String) -> bool { + transition + .id + .get_leaves() + .concat() + .iter() + .filter_map(|x| match x { + TransitionID::Simple(x) => Some(x), + _ => None, + }) + .any(|x| x == edge_id) + } + let source = decision.source().to_owned(); + let action = decision.decided().get_sync(); + let edge_id = &decision.decided().id; + + // Choose transitions that correspond to a given edge. + system + .next_transitions_if_available(source.get_location(), action) + .into_iter() + .filter(|t| contains(t, edge_id)) + .map(|t| TransitionDecision { + source: source.to_owned(), + decided: t, + }) + .collect::>() + } + + /// Resolves a [`TransitionDecision`]: use the `decided` [`Transition`] and return the [`TransitionDecisionPoint`] of the destination [`State`]. + pub fn resolve(&self, system: &TransitionSystemPtr) -> Option { + let mut source = self.source.to_owned(); + match self.decided.use_transition(&mut source) { + true => Some(TransitionDecisionPoint::from(system, &source)), + false => None, + } + } +} + +#[cfg(test)] +mod tests { + use crate::{ + tests::Simulation::{ + helper::create_system_from_path, + test_data::{create_EcdarUniversity_Machine_system, create_Simulation_Machine_system}, + }, + DataReader::json_reader::read_json_component, + Simulation::{ + decision::Decision, transition_decision::TransitionDecision, + transition_decision_point::TransitionDecisionPoint, + }, + TransitionSystems::TransitionSystemPtr, + }; + + #[test] + fn from__Determinism_NonDeterminismCom__returns_non_deterministic_answer() { + // Arrange + let path = "samples/json/Determinism"; + let component = "NonDeterminismCom"; + let system = create_system_from_path(path, component); + let component = read_json_component(path, component); + + let decision = Decision::new( + system.get_initial_state().unwrap(), + component.get_edges().first().unwrap().to_owned(), + ); + + let expected_len = 1; + + // Act + let actual = TransitionDecision::from(&decision, &system); + + // Assert + assert_eq!(actual.len(), expected_len); + } + + #[test] + fn from__edge_with_action_that_maps_to_single_transition__returns_correct_TransitionDecision() { + // Arrange + let system = create_EcdarUniversity_Machine_system(); + let component = read_json_component("samples/json/EcdarUniversity", "Machine"); + let initial = system.get_initial_state().unwrap(); + let edge = component.get_edges()[4].clone(); + + let decision = Decision::new(initial.clone(), edge); + + let expected = TransitionDecision { + source: initial.clone(), + decided: system + .next_transitions(initial.get_location(), "tea") + .first() + .unwrap() + .to_owned(), + }; + + // Act n Assert + act_and_assert__from__good_Decision__returns_correct_TransitionDecision( + system, decision, expected, + ); + } + + #[test] + fn from__edge_with_action_that_maps_to_multiple_transitions__returns_correct_TransitionDecision( + ) { + // Arrange + let system = create_Simulation_Machine_system(); + let component = read_json_component("samples/json/Simulation", "SimMachine"); + let initial = system.get_initial_state().unwrap(); + let edges = component.get_edges().clone(); + + let decision = Decision::new(initial.clone(), edges[0].clone()); + + let edge_action = edges[0].get_sync(); + + let expected = TransitionDecision { + source: initial.clone(), + decided: system.next_transitions(initial.get_location(), edge_action)[0].clone(), + }; + + // Act n Assert + act_and_assert__from__good_Decision__returns_correct_TransitionDecision( + system, decision, expected, + ); + } + + fn act_and_assert__from__good_Decision__returns_correct_TransitionDecision( + system: TransitionSystemPtr, + decision: Decision, + expected: TransitionDecision, + ) { + // Act + let binding = TransitionDecision::from(&decision, &system); + let actual = binding.first().unwrap(); + + // Assert + assert_eq!(format!("{:?}", actual), format!("{:?}", expected)) + } + + // Yes this test is stupid and bad, no you will not remove it >:( + #[allow(unused_must_use)] + #[test] + fn resolve__EcdarUniversity_Machine__correct_TransitionDecisionPoint() { + // Arrange + let system = create_EcdarUniversity_Machine_system(); + + let initial = system.get_initial_state().unwrap(); + + let transition = system + .next_transitions_if_available(initial.get_location(), "coin") + .first() + .unwrap() + .to_owned(); + + let decision = TransitionDecision { + source: initial.clone(), + decided: transition.clone(), + }; + + // Act + let actual = decision.resolve(&system).unwrap(); + + // Assert + let actual_source = format!("{:?}", actual.source()); + let actual_possible_decisions: Vec = actual + .possible_decisions() + .iter() + .map(|x| format!("{:?}", x)) + .collect(); + + let mut source = initial; + transition.use_transition(&mut source); + let expected = TransitionDecisionPoint::from(&system, &source); + let expected_source = format!("{:?}", expected.source()); + let expected_possible_decisions = expected + .possible_decisions() + .iter() + .map(|x| format!("{:?}", x)); + + assert_eq!(actual_source, expected_source); + assert_eq!( + actual_possible_decisions.len(), + expected_possible_decisions.len() + ); + + expected_possible_decisions.map(|x| assert!(actual_possible_decisions.contains(&x))); + } +} diff --git a/src/Simulation/transition_decision_point.rs b/src/Simulation/transition_decision_point.rs new file mode 100644 index 00000000..ac1204b6 --- /dev/null +++ b/src/Simulation/transition_decision_point.rs @@ -0,0 +1,124 @@ +use crate::{ + component::{State, Transition}, + TransitionSystems::TransitionSystemPtr, +}; + +/// Represents a decision in a transition system: In the current `source` state there is a decision of using one of the `possible_decisions`. +#[derive(Debug, Clone)] +pub struct TransitionDecisionPoint { + source: State, + possible_decisions: Vec, +} + +impl TransitionDecisionPoint { + /// Constructs the initial [`TransitionDecisionPoint`] for a given [`TransitionSystemPtr`]. + pub fn initial(system: &TransitionSystemPtr) -> Option { + system + .get_initial_state() + .map(|source| Self::from(system, &source)) + } + + /// Constructs the [`TransitionDecisionPoint`] from a `source` [`State`] and a given [`TransitionSystemPtr`]. + pub fn from(system: &TransitionSystemPtr, source: &State) -> TransitionDecisionPoint { + let actions = system.get_actions(); + let transitions: Vec = actions + .into_iter() + // Map actions to transitions. An action can map to multiple actions thus flatten + .flat_map(|action| system.next_transitions_if_available(source.get_location(), &action)) + // Filter transitions that can be used + .filter(|transition| transition.use_transition_alt(source).is_some()) + .collect(); + + TransitionDecisionPoint { + source: source.to_owned(), + possible_decisions: transitions, + } + } + + pub fn source(&self) -> &State { + &self.source + } + + pub fn possible_decisions(&self) -> &[Transition] { + self.possible_decisions.as_ref() + } +} + +#[cfg(test)] +mod tests { + use super::TransitionDecisionPoint; + use crate::tests::Simulation::test_data::{ + create_EcdarUniversity_Machine4_system, create_EcdarUniversity_Machine_system, + }; + + #[test] + fn initial__EcdarUniversity_Machine__return_correct_state() { + // Arrange + let system = create_EcdarUniversity_Machine_system(); + let expected = system.get_initial_state().unwrap(); + + // Act + let actual = TransitionDecisionPoint::initial(&system).unwrap().source; + + // Assert + assert_eq!(format!("{:?}", actual), format!("{:?}", expected)) + } + + // TODO this test is confusing + #[test] + fn initial__EcdarUniversity_Machine__correct_transitions() { + // Arrange + let system = create_EcdarUniversity_Machine_system(); + + // Act + let actual: Vec = TransitionDecisionPoint::initial(&system) + .unwrap() + .possible_decisions + .into_iter() + .map(|x| format!("{:?}", x)) // shhhhhh, close your eyes, this is not logic + .collect(); + + // Assert + let expected_len = 2; + assert_eq!(actual.len(), expected_len); + + let expected_tea_transition = &format!( + "{:?}", + system.next_transitions_if_available(&system.get_initial_location().unwrap(), "tea")[0] + ); + assert!(actual.contains(expected_tea_transition)); + + let expected_coin_transition = &format!( + "{:?}", + system.next_transitions_if_available(&system.get_initial_location().unwrap(), "coin") + [0] + ); + assert!(actual.contains(expected_coin_transition)); + } + + // TODO this test is confusing + #[test] + fn initial__EcdarUniversity_Machine4__correct_transitions() { + // Arrange + let system = create_EcdarUniversity_Machine4_system(); + + // Act + let actual: Vec = TransitionDecisionPoint::initial(&system) + .unwrap() + .possible_decisions + .into_iter() + .map(|x| format!("{:?}", x)) // still no logic to be found here + .collect(); + + // Assert + let expected_len = 1; + assert_eq!(actual.len(), expected_len); + + let expected_coin_transition = &format!( + "{:?}", + system.next_transitions_if_available(&system.get_initial_location().unwrap(), "coin") + [0] + ); + assert!(actual.contains(expected_coin_transition)); + } +} diff --git a/src/System/save_component.rs b/src/System/save_component.rs index f2efd8d5..80de016b 100644 --- a/src/System/save_component.rs +++ b/src/System/save_component.rs @@ -43,7 +43,7 @@ pub fn combine_components( comp } -fn get_locations_from_tuples( +pub fn get_locations_from_tuples( location_tuples: &[LocationTuple], clock_map: &HashMap, ) -> Vec { @@ -65,7 +65,7 @@ fn get_locations_from_tuples( .collect() } -fn get_clock_map(sysrep: &TransitionSystemPtr) -> HashMap { +pub fn get_clock_map(sysrep: &TransitionSystemPtr) -> HashMap { let mut clocks = HashMap::new(); let decls = sysrep.get_decls(); diff --git a/src/TransitionSystems/compiled_component.rs b/src/TransitionSystems/compiled_component.rs index 2e26d336..ecc22a32 100644 --- a/src/TransitionSystems/compiled_component.rs +++ b/src/TransitionSystems/compiled_component.rs @@ -16,11 +16,17 @@ type Action = String; #[derive(Clone)] struct ComponentInfo { - //name: String, + name: String, declarations: Declarations, max_bounds: Bounds, } +impl ComponentInfo { + pub fn _name(&self) -> &str { + self.name.as_ref() + } +} + #[derive(Clone)] pub struct CompiledComponent { inputs: HashSet, @@ -87,7 +93,7 @@ impl CompiledComponent { initial_location, dim, comp_info: ComponentInfo { - //name: component.name, + name: component.name, declarations: component.declarations, max_bounds, }, @@ -111,6 +117,10 @@ impl CompiledComponent { Self::compile_with_actions(component, inputs, outputs, dim) } + + fn _comp_info(&self) -> &ComponentInfo { + &self.comp_info + } } impl TransitionSystem for CompiledComponent { @@ -193,7 +203,7 @@ impl TransitionSystem for CompiledComponent { } fn get_composition_type(&self) -> CompositionType { - panic!("Components do not have a composition type") + CompositionType::Simple } fn get_combined_decls(&self) -> Declarations { @@ -203,4 +213,8 @@ impl TransitionSystem for CompiledComponent { fn get_location(&self, id: &LocationID) -> Option { self.locations.get(id).cloned() } + + fn component_names(&self) -> Vec<&str> { + vec![&self.comp_info.name] + } } diff --git a/src/TransitionSystems/location_id.rs b/src/TransitionSystems/location_id.rs index 8a6dd3f6..a3bfaea2 100644 --- a/src/TransitionSystems/location_id.rs +++ b/src/TransitionSystems/location_id.rs @@ -36,6 +36,28 @@ impl LocationID { } } + /// Does an inorder walk of the [`LocationID`] tree mapping it to a list of [`LocationID::Simple`]. + pub fn inorder_vec_tranform(&self) -> Vec { + match self { + LocationID::Composition(left, right) + | LocationID::Quotient(left, right) + | LocationID::Conjunction(left, right) => { + let mut left = left.inorder_vec_tranform(); + let mut right = right.inorder_vec_tranform(); + left.append(&mut right); + left + } + LocationID::Simple { + location_id, + component_id, + } => vec![LocationID::Simple { + location_id: location_id.to_string(), + component_id: component_id.as_ref().map(|x| x.to_string()), + }], + LocationID::AnyLocation() => vec![LocationID::AnyLocation()], + } + } + /// It check whether the [`LocationID`] is a partial location by search through [`LocationID`] structure and see if there is any [`LocationID::AnyLocation`] pub fn is_partial_location(&self) -> bool { match self { diff --git a/src/TransitionSystems/location_tuple.rs b/src/TransitionSystems/location_tuple.rs index 9f1c1ed6..d41e09e3 100644 --- a/src/TransitionSystems/location_tuple.rs +++ b/src/TransitionSystems/location_tuple.rs @@ -12,6 +12,7 @@ pub enum CompositionType { Conjunction, Composition, Quotient, + Simple, } #[derive(Clone, Debug)] diff --git a/src/TransitionSystems/mod.rs b/src/TransitionSystems/mod.rs index 307dfe12..df8d0cc4 100644 --- a/src/TransitionSystems/mod.rs +++ b/src/TransitionSystems/mod.rs @@ -3,7 +3,7 @@ pub(crate) mod common; mod compiled_component; mod composition; mod conjunction; -mod location_id; +pub mod location_id; mod location_tuple; mod quotient; mod transition_id; diff --git a/src/TransitionSystems/transition_system.rs b/src/TransitionSystems/transition_system.rs index bdde3074..78601c5a 100644 --- a/src/TransitionSystems/transition_system.rs +++ b/src/TransitionSystems/transition_system.rs @@ -1,7 +1,13 @@ use super::{CompositionType, LocationID, LocationTuple}; +use crate::DataReader::parse_queries::Rule; use crate::EdgeEval::updater::CompiledUpdate; use crate::System::local_consistency::DeterminismFailure; use crate::{ + component::Component, + extract_system_rep::get_system_recipe, + parse_queries::{build_expression_from_pair, QueryParser}, + ComponentLoader, + DataReader::component_loader::ComponentContainer, ModelObjects::component::{Declarations, State, Transition}, System::local_consistency::DeterminismResult, System::local_consistency::{ConsistencyFailure, ConsistencyResult}, @@ -9,9 +15,14 @@ use crate::{ use dyn_clone::{clone_trait_object, DynClone}; use edbm::util::{bounds::Bounds, constraints::ClockIndex}; use log::warn; +use pest::Parser; use std::collections::hash_map::Entry; -use std::collections::{hash_set::HashSet, HashMap}; use std::hash::Hash; +use std::{ + collections::{hash_set::HashSet, HashMap}, + iter::zip, +}; + pub type TransitionSystemPtr = Box; pub type Action = String; pub type EdgeTuple = (Action, Transition); @@ -121,6 +132,60 @@ pub trait TransitionSystem: DynClone { fn get_composition_type(&self) -> CompositionType; + /// Returns a [`Vec`] of all component names in a given [`TransitionSystem`]. + fn component_names(&self) -> Vec<&str> { + let children = self.get_children(); + let left_child = children.0; + let right_child = children.1; + left_child + .component_names() + .into_iter() + .chain(right_child.component_names().into_iter()) + .collect() + } + + /// Maps a clock- and component name to a clock index for a given [`TransitionSystem`]. + fn clock_name_and_component_to_index(&self, name: &str, component: &str) -> Option { + let index_to_clock_name_and_component = self.clock_name_and_component_to_index_map(); + index_to_clock_name_and_component + .get(&(name.to_string(), component.to_string())) + .copied() + } + + /// Maps a clock index to a clock- and component name for a given [`TransitionSystem`]. + fn index_to_clock_name_and_component(&self, index: &usize) -> Option<(String, String)> { + fn invert(hash_map: HashMap) -> HashMap + where + T2: Hash + Eq, + { + hash_map.into_iter().map(|x| (x.1, x.0)).collect() + } + + let index_to_clock_name_and_component = self.clock_name_and_component_to_index_map(); + let index_to_clock_name_and_component = invert(index_to_clock_name_and_component); + index_to_clock_name_and_component + .get(index) + .map(|x| x.to_owned()) + } + + /// Returns a [`HashMap`] from clock- and component names to clock indices. + fn clock_name_and_component_to_index_map(&self) -> HashMap<(String, String), usize> { + let binding = self.component_names(); + let component_names = binding.into_iter(); + let binding = self.get_decls(); + let clock_to_index = binding.into_iter().map(|decl| decl.clocks.to_owned()); + + zip(component_names, clock_to_index) + .map(|x| { + x.1.iter() + .map(|y| ((y.0.to_owned(), x.0.to_string()), y.1.to_owned())) + .collect::>() + }) + .fold(HashMap::new(), |accumulator, head| { + accumulator.into_iter().chain(head).collect() + }) + } + ///Constructs a [CLockAnalysisGraph], ///where nodes represents locations and Edges represent transitions fn get_analysis_graph(&self) -> ClockAnalysisGraph { @@ -201,6 +266,31 @@ pub trait TransitionSystem: DynClone { } } +/// Returns a [`TransitionSystemPtr`] equivalent to a `composition` of some `components`. +pub fn components_to_transition_system( + components: Vec, + composition: &str, +) -> TransitionSystemPtr { + let mut component_container = ComponentContainer::from_components(components); + component_loader_to_transition_system(&mut component_container, composition) +} + +/// Returns a [`TransitionSystemPtr`] equivalent to a `composition` of some components in a [`ComponentLoader`]. +pub fn component_loader_to_transition_system( + loader: &mut dyn ComponentLoader, + composition: &str, +) -> TransitionSystemPtr { + let mut dimension = 0; + let composition = QueryParser::parse(Rule::expr, composition) + .unwrap() + .next() + .unwrap(); + let composition = build_expression_from_pair(composition); + get_system_recipe(&composition, loader, &mut dimension, &mut None) + .compile(dimension) + .unwrap() +} + #[derive(Debug, Clone, Eq, PartialEq)] pub enum ClockReductionInstruction { RemoveClock { diff --git a/src/tests/Simulation/helper.rs b/src/tests/Simulation/helper.rs new file mode 100644 index 00000000..af27bef1 --- /dev/null +++ b/src/tests/Simulation/helper.rs @@ -0,0 +1,251 @@ +use std::{fs, vec}; + +use tonic::{Request, Response, Status}; + +use crate::ProtobufServer::services::{ + self, Component as ProtoComponent, ComponentsInfo as ProtoComponentsInfo, + Decision as ProtoDecision, Edge as ProtoEdge, SimulationInfo as ProtoSimulationInfo, + SimulationStartRequest, SimulationStepRequest, SimulationStepResponse, State as ProtoState, +}; +use crate::{ + DataReader::json_reader::read_json_component, + ProtobufServer::services::component::Rep, + TransitionSystems::{ + transition_system::components_to_transition_system, CompositionType, TransitionSystemPtr, + }, +}; + +pub fn create_system_from_path(path: &str, name: &str) -> TransitionSystemPtr { + let component = read_json_component(path, name); + components_to_transition_system(vec![component], name) +} + +pub fn create_simulation_info( + composition: String, + components: Vec, +) -> ProtoSimulationInfo { + ProtoSimulationInfo { + component_composition: composition, + components_info: Some(ProtoComponentsInfo { + components, + components_hash: 0, + }), + user_id: 0, + } +} + +pub fn create_composition_string(comp_names: &Vec<&str>, comp_type: CompositionType) -> String { + let mut composition = String::new(); + for (i, name) in comp_names.iter().enumerate() { + composition.push_str(name); + if i < comp_names.len() - 1 { + match comp_type { + CompositionType::Conjunction => composition.push_str(" && "), + CompositionType::Composition => composition.push_str(" || "), + CompositionType::Quotient => { + unimplemented!("Quotient composition not implemented") + } + CompositionType::Simple => unimplemented!("Simple composition not implemented"), + } + } + } + composition +} + +pub fn create_components(comp_names: &[&str], sample_name: String) -> Vec { + let components: Vec = comp_names + .iter() + .map(|name| { + create_json_component_as_string(format!( + "samples/json/{}/Components/{}.json", + sample_name, name + )) + }) + .collect(); + + let components: Vec = components + .iter() + .map(|string| ProtoComponent { + rep: Some(Rep::Json(string.clone())), + }) + .collect(); + + components +} + +pub fn create_1tuple_state_with_single_constraint( + id: &str, + component_name: &str, + component_index: u32, + clock_x_name: &str, + clock_y_name: &str, + clock_constraint: i32, + is_constrain_strict: bool, +) -> services::State { + services::State { + location_tuple: Some(services::LocationTuple { + locations: vec![services::Location { + id: String::from(id), + specific_component: Some(services::SpecificComponent { + component_name: String::from(component_name), + component_index, + }), + }], + }), + federation: Some(services::Federation { + disjunction: Some(services::Disjunction { + conjunctions: vec![services::Conjunction { + constraints: vec![ + // constraint (x - y <= c) + services::Constraint { + x: Some(services::ComponentClock { + specific_component: Some(services::SpecificComponent { + component_name: String::from(component_name), + component_index, + }), + clock_name: String::from(clock_x_name), + }), + y: Some(services::ComponentClock { + specific_component: Some(services::SpecificComponent { + component_name: String::from(component_name), + component_index, + }), + clock_name: String::from(clock_y_name), + }), + strict: is_constrain_strict, + c: clock_constraint, + }, + ], + }], + }), + }), + } +} + +pub fn create_json_component_as_string(path: String) -> String { + fs::read_to_string(path).unwrap() +} + +pub fn create_simulation_step_request( + simulation_info: ProtoSimulationInfo, + source: services::State, + edge: services::Edge, +) -> SimulationStepRequest { + SimulationStepRequest { + simulation_info: Some(simulation_info), + chosen_decision: Some(services::Decision { + source: Some(source), + edge: Some(edge), + }), + } +} + +pub fn create_simulation_start_request( + composition: String, + component_json: String, +) -> Request { + Request::new(SimulationStartRequest { + simulation_info: Some(create_simulation_info_from(composition, component_json)), + }) +} + +pub fn create_empty_state() -> ProtoState { + ProtoState { + location_tuple: None, + federation: None, + } +} + +pub fn create_empty_edge() -> ProtoEdge { + ProtoEdge { + id: String::from(""), + specific_component: None, + } +} + +pub fn create_simulation_info_from( + composition: String, + component_json: String, +) -> ProtoSimulationInfo { + ProtoSimulationInfo { + user_id: 0, + component_composition: composition, + components_info: Some(ProtoComponentsInfo { + components: vec![ProtoComponent { + rep: Some(services::component::Rep::Json(component_json)), + }], + components_hash: 0, + }), + } +} + +pub fn create_start_request( + component_names: &[&str], + components_path: &str, + composition: &str, +) -> SimulationStartRequest { + let simulation_info = create_simulation_info_1(component_names, components_path, composition); + SimulationStartRequest { + simulation_info: Some(simulation_info), + } +} + +pub fn create_step_request( + component_names: &[&str], + components_path: &str, + composition: &str, + last_response: Result, Status>, +) -> SimulationStepRequest { + let simulation_info = create_simulation_info_1(component_names, components_path, composition); + let last_response = last_response.unwrap().into_inner(); + let source = last_response + .new_decision_points + .first() + .unwrap() + .source + .to_owned(); + let decision = last_response + .new_decision_points + .first() + .unwrap() + .edges + .first() + .unwrap() + .to_owned(); + + SimulationStepRequest { + simulation_info: Some(simulation_info), + chosen_decision: Some(ProtoDecision { + source, + edge: Some(decision), + }), + } +} + +fn create_simulation_info_1( + component_names: &[&str], + components_path: &str, + composition: &str, +) -> ProtoSimulationInfo { + let json_components: Vec<_> = component_names + .iter() + .map(|component_name| ProtoComponent { + rep: Some(Rep::Json( + fs::read_to_string(format!( + "{}/Components/{}.json", + components_path, component_name + )) + .unwrap(), + )), + }) + .collect(); + + ProtoSimulationInfo { + user_id: 0, + component_composition: composition.to_string(), + components_info: Some(ProtoComponentsInfo { + components: json_components, + components_hash: 0, + }), + } +} diff --git a/src/tests/Simulation/mod.rs b/src/tests/Simulation/mod.rs new file mode 100644 index 00000000..b6dc7e9c --- /dev/null +++ b/src/tests/Simulation/mod.rs @@ -0,0 +1,2 @@ +pub mod helper; +pub mod test_data; diff --git a/src/tests/Simulation/test_data.rs b/src/tests/Simulation/test_data.rs new file mode 100644 index 00000000..ebe740d2 --- /dev/null +++ b/src/tests/Simulation/test_data.rs @@ -0,0 +1,1310 @@ +use std::fs; + +use tonic::{Request, Response, Status}; + +use crate::ProtobufServer::services::{ + Component as ProtoComponent, ComponentClock as ProtoComponentClock, + Conjunction as ProtoConjunction, Constraint as ProtoConstraint, Decision as ProtoDecision, + DecisionPoint as ProtoDecisionPoint, Disjunction as ProtoDisjunction, Edge as ProtoEdge, + Federation as ProtoFederation, Location as ProtoLocation, LocationTuple as ProtoLocationTuple, + SimulationStartRequest, SimulationStepRequest, SimulationStepResponse, + SpecificComponent as ProtoSpecificComponent, State as ProtoState, +}; +use crate::Simulation::transition_decision_point::TransitionDecisionPoint; +use crate::TransitionSystems::CompositionType; +use crate::{ + component::Component, DataReader::json_reader::read_json_component, + TransitionSystems::TransitionSystemPtr, +}; + +use super::helper::{ + create_1tuple_state_with_single_constraint, create_components, create_composition_string, + create_empty_edge, create_empty_state, create_simulation_info, create_simulation_info_from, + create_simulation_start_request, create_simulation_step_request, create_system_from_path, +}; + +static ECDAR_UNI: &str = "samples/json/EcdarUniversity"; + +pub fn create_EcdarUniversity_Machine_component() -> Component { + let project_path = "samples/json/EcdarUniversity"; + read_json_component(project_path, "Machine") +} + +pub fn create_EcdarUniversity_Machine_system() -> TransitionSystemPtr { + create_system_from_path("samples/json/EcdarUniversity", "Machine") +} + +pub fn create_EcdarUniversity_HalfAdm1_system() -> TransitionSystemPtr { + create_system_from_path("samples/json/EcdarUniversity", "HalfAdm1") +} + +pub fn create_EcdarUniversity_HalfAdm2_system() -> TransitionSystemPtr { + create_system_from_path("samples/json/EcdarUniversity", "HalfAdm2") +} + +pub fn create_EcdarUniversity_Administration_system() -> TransitionSystemPtr { + create_system_from_path("samples/json/EcdarUniversity", "Administration") +} + +pub fn create_EcdarUniversity_Researcher_system() -> TransitionSystemPtr { + create_system_from_path("samples/json/EcdarUniversity", "Researcher") +} + +pub fn create_Simulation_Machine_system() -> TransitionSystemPtr { + create_system_from_path("samples/json/Simulation", "SimMachine") +} + +pub fn create_EcdarUniversity_Machine4_system() -> TransitionSystemPtr { + create_system_from_path("samples/json/EcdarUniversity", "Machine4") +} + +pub fn create_EcdarUniversity_Machine_Decision() -> ProtoDecision { + // kopieret fra create_EcdarUnversity_Machine_Initial_Decision_Point men ved ikke hvordan det kunne gøres til en funktion smart + let specific_comp_dp = ProtoSpecificComponent { + component_name: "Machine".to_string(), + component_index: 1, + }; + + let conjunction_dp = ProtoConjunction { + constraints: vec![], + }; + + let disjunction_dp = ProtoDisjunction { + conjunctions: vec![conjunction_dp], + }; + + let federation_dp = ProtoFederation { + disjunction: Some(disjunction_dp), + }; + + let location_dp1 = ProtoLocation { + id: "L5".to_string(), + specific_component: Some(specific_comp_dp.clone()), + }; + + let loc_tuple_dp = ProtoLocationTuple { + locations: vec![location_dp1], + }; + + let source_dp = ProtoState { + location_tuple: Some(loc_tuple_dp), + federation: Some(federation_dp), + }; + + let edge29 = ProtoEdge { + id: "E29".to_string(), + specific_component: Some(specific_comp_dp), + }; + + ProtoDecision { + source: Some(source_dp), + edge: Some(edge29), + } +} + +pub fn create_EcdarUniversity_Machine_with_nonempty_Federation_Decision() -> ProtoDecision { + // kopieret fra create_EcdarUnversity_Machine_Initial_Decision_Point men ved ikke hvordan det kunne gøres til en funktion smart + let specific_comp_dp = ProtoSpecificComponent { + component_name: "Machine".to_string(), + component_index: 1, + }; + + let componentclock_dp1 = ProtoComponentClock { + specific_component: None, + clock_name: "0".to_string(), + }; + let componentclock_dp2 = ProtoComponentClock { + specific_component: Some(specific_comp_dp.clone()), + clock_name: "y".to_string(), + }; + + let constraint29_dp = ProtoConstraint { + x: Some(componentclock_dp1), + y: Some(componentclock_dp2), + strict: false, + c: -2, + }; + + let conjunction_dp = ProtoConjunction { + constraints: vec![constraint29_dp], + }; + + let disjunction_dp = ProtoDisjunction { + conjunctions: vec![conjunction_dp], + }; + + let federation_dp = ProtoFederation { + disjunction: Some(disjunction_dp), + }; + + let location_dp1 = ProtoLocation { + id: "L5".to_string(), + specific_component: Some(specific_comp_dp.clone()), + }; + + let loc_tuple_dp = ProtoLocationTuple { + locations: vec![location_dp1], + }; + + let source_dp = ProtoState { + location_tuple: Some(loc_tuple_dp), + federation: Some(federation_dp), + }; + + let edge29 = ProtoEdge { + id: "E29".to_string(), + specific_component: Some(specific_comp_dp), + }; + + ProtoDecision { + source: Some(source_dp), + edge: Some(edge29), + } +} + +pub fn create_EcdarUniversity_Machine3and1_with_nonempty_Federation_Decision() -> ProtoDecision { + // kopieret fra create_EcdarUnversity_Machine_Initial_Decision_Point men ved ikke hvordan det kunne gøres til en funktion smart + let specific_comp_dp1 = ProtoSpecificComponent { + component_name: "Machine".to_string(), + component_index: 1, + }; + + let source_dp = ProtoState { + location_tuple: Some(ProtoLocationTuple { + locations: vec![ + ProtoLocation { + id: "L8".to_string(), + specific_component: Some(ProtoSpecificComponent { + component_name: "Machine3".to_string(), + component_index: 0, + }), + }, + ProtoLocation { + id: "L5".to_string(), + specific_component: Some(ProtoSpecificComponent { + component_name: "Machine".to_string(), + component_index: 0, + }), + }, + ], + }), + federation: Some(ProtoFederation { + disjunction: Some(ProtoDisjunction { + conjunctions: vec![ProtoConjunction { + constraints: vec![ + ProtoConstraint { + x: Some(ProtoComponentClock { + specific_component: Some(ProtoSpecificComponent { + component_name: "Machine3".to_string(), + component_index: 0, + }), + clock_name: "y".to_string(), + }), + y: Some(ProtoComponentClock { + specific_component: Some(ProtoSpecificComponent { + component_name: "Machine".to_string(), + component_index: 0, + }), + clock_name: "y".to_string(), + }), + strict: false, + c: 0, + }, + ProtoConstraint { + x: Some(ProtoComponentClock { + specific_component: Some(ProtoSpecificComponent { + component_name: "Machine".to_string(), + component_index: 0, + }), + clock_name: "y".to_string(), + }), + y: Some(ProtoComponentClock { + specific_component: Some(ProtoSpecificComponent { + component_name: "Machine3".to_string(), + component_index: 0, + }), + clock_name: "y".to_string(), + }), + strict: false, + c: 0, + }, + ], + }], + }), + }), + }; + + let edge29 = ProtoEdge { + id: "E29".to_string(), + specific_component: Some(specific_comp_dp1), + }; + + ProtoDecision { + source: Some(source_dp), + edge: Some(edge29), + } +} + +pub fn initial_transition_decision_point_EcdarUniversity_Machine() -> TransitionDecisionPoint { + let system = create_EcdarUniversity_Machine_system(); + TransitionDecisionPoint::initial(&system).unwrap() +} + +pub fn get_state_after_Administration_Machine_Researcher_composition() -> ProtoState { + ProtoState { + location_tuple: Some(ProtoLocationTuple { + locations: vec![ + ProtoLocation { + id: "L0".to_string(), + specific_component: Some(ProtoSpecificComponent { + component_name: "Administration".to_string(), + component_index: 0, + }), + }, + ProtoLocation { + id: "L5".to_string(), + specific_component: Some(ProtoSpecificComponent { + component_name: "Machine".to_string(), + component_index: 0, + }), + }, + ProtoLocation { + id: "L6".to_string(), + specific_component: Some(ProtoSpecificComponent { + component_name: "Researcher".to_string(), + component_index: 0, + }), + }, + ], + }), + federation: Some(ProtoFederation { + disjunction: Some(ProtoDisjunction { + conjunctions: vec![ProtoConjunction { + constraints: vec![ + ProtoConstraint { + x: Some(ProtoComponentClock { + specific_component: Some(ProtoSpecificComponent { + component_name: "Administration".to_string(), + component_index: 0, + }), + clock_name: "z".to_string(), + }), + y: Some(ProtoComponentClock { + specific_component: Some(ProtoSpecificComponent { + component_name: "Machine".to_string(), + component_index: 0, + }), + clock_name: "y".to_string(), + }), + strict: false, + c: 0, + }, + ProtoConstraint { + x: Some(ProtoComponentClock { + specific_component: Some(ProtoSpecificComponent { + component_name: "Machine".to_string(), + component_index: 0, + }), + clock_name: "y".to_string(), + }), + y: Some(ProtoComponentClock { + specific_component: Some(ProtoSpecificComponent { + component_name: "Researcher".to_string(), + component_index: 0, + }), + clock_name: "x".to_string(), + }), + strict: false, + c: 0, + }, + ProtoConstraint { + x: Some(ProtoComponentClock { + specific_component: Some(ProtoSpecificComponent { + component_name: "Researcher".to_string(), + component_index: 0, + }), + clock_name: "x".to_string(), + }), + y: Some(ProtoComponentClock { + specific_component: Some(ProtoSpecificComponent { + component_name: "Administration".to_string(), + component_index: 0, + }), + clock_name: "z".to_string(), + }), + strict: false, + c: 0, + }, + ], + }], + }), + }), + } +} + +pub fn get_composition_response_Administration_Machine_Researcher( +) -> Result, Status> { + let proto_decision_point = ProtoDecisionPoint { + source: Some(ProtoState { + location_tuple: Some(ProtoLocationTuple { + locations: vec![ + ProtoLocation { + id: "L0".to_string(), + specific_component: Some(ProtoSpecificComponent { + component_name: "Administration".to_string(), + component_index: 0, + }), + }, + ProtoLocation { + id: "L5".to_string(), + specific_component: Some(ProtoSpecificComponent { + component_name: "Machine".to_string(), + component_index: 0, + }), + }, + ProtoLocation { + id: "L6".to_string(), + specific_component: Some(ProtoSpecificComponent { + component_name: "Researcher".to_string(), + component_index: 0, + }), + }, + ], + }), + federation: Some(ProtoFederation { + disjunction: Some(ProtoDisjunction { + conjunctions: vec![ProtoConjunction { + constraints: vec![ + ProtoConstraint { + x: Some(ProtoComponentClock { + specific_component: Some(ProtoSpecificComponent { + component_name: "Administration".to_string(), + component_index: 0, + }), + clock_name: "z".to_string(), + }), + y: Some(ProtoComponentClock { + specific_component: Some(ProtoSpecificComponent { + component_name: "Machine".to_string(), + component_index: 0, + }), + clock_name: "y".to_string(), + }), + strict: false, + c: 0, + }, + ProtoConstraint { + x: Some(ProtoComponentClock { + specific_component: Some(ProtoSpecificComponent { + component_name: "Machine".to_string(), + component_index: 0, + }), + clock_name: "y".to_string(), + }), + y: Some(ProtoComponentClock { + specific_component: Some(ProtoSpecificComponent { + component_name: "Researcher".to_string(), + component_index: 0, + }), + clock_name: "x".to_string(), + }), + strict: false, + c: 0, + }, + ProtoConstraint { + x: Some(ProtoComponentClock { + specific_component: Some(ProtoSpecificComponent { + component_name: "Researcher".to_string(), + component_index: 0, + }), + clock_name: "x".to_string(), + }), + y: Some(ProtoComponentClock { + specific_component: Some(ProtoSpecificComponent { + component_name: "Administration".to_string(), + component_index: 0, + }), + clock_name: "z".to_string(), + }), + strict: false, + c: 0, + }, + ], + }], + }), + }), + }), + edges: vec![ + ProtoEdge { + id: "E11".to_string(), + specific_component: None, + }, + ProtoEdge { + id: "E16".to_string(), + specific_component: None, + }, + ProtoEdge { + id: "E29".to_string(), + specific_component: None, + }, + ProtoEdge { + id: "E44".to_string(), + specific_component: None, + }, + ], + }; + + let response = SimulationStepResponse { + new_decision_points: vec![proto_decision_point], + }; + + Ok(Response::new(response)) +} + +pub fn get_composition_response_Administration_Machine_Researcher_after_E29( +) -> Result, Status> { + let decisionpoint1 = ProtoDecisionPoint { + source: Some(ProtoState { + location_tuple: Some(ProtoLocationTuple { + locations: vec![ + ProtoLocation { + id: "L0".to_string(), + specific_component: Some(ProtoSpecificComponent { + component_name: "Administration".to_string(), + component_index: 0, + }), + }, + ProtoLocation { + id: "L5".to_string(), + specific_component: Some(ProtoSpecificComponent { + component_name: "Machine".to_string(), + component_index: 0, + }), + }, + ProtoLocation { + id: "L7".to_string(), + specific_component: Some(ProtoSpecificComponent { + component_name: "Researcher".to_string(), + component_index: 0, + }), + }, + ], + }), + federation: Some(ProtoFederation { + disjunction: Some(ProtoDisjunction { + conjunctions: vec![ProtoConjunction { + constraints: vec![ + ProtoConstraint { + x: Some(ProtoComponentClock { + specific_component: Some(ProtoSpecificComponent { + component_name: "Administration".to_string(), + component_index: 0, + }), + clock_name: "z".to_string(), + }), + y: Some(ProtoComponentClock { + specific_component: Some(ProtoSpecificComponent { + component_name: "Machine".to_string(), + component_index: 0, + }), + clock_name: "y".to_string(), + }), + strict: false, + c: 0, + }, + ProtoConstraint { + x: Some(ProtoComponentClock { + specific_component: Some(ProtoSpecificComponent { + component_name: "Administration".to_string(), + component_index: 0, + }), + clock_name: "z".to_string(), + }), + y: Some(ProtoComponentClock { + specific_component: Some(ProtoSpecificComponent { + component_name: "Researcher".to_string(), + component_index: 0, + }), + clock_name: "x".to_string(), + }), + strict: false, + c: 15, + }, + ProtoConstraint { + x: Some(ProtoComponentClock { + specific_component: Some(ProtoSpecificComponent { + component_name: "Machine".to_string(), + component_index: 0, + }), + clock_name: "y".to_string(), + }), + y: Some(ProtoComponentClock { + specific_component: Some(ProtoSpecificComponent { + component_name: "Administration".to_string(), + component_index: 0, + }), + clock_name: "z".to_string(), + }), + strict: false, + c: 0, + }, + ProtoConstraint { + x: Some(ProtoComponentClock { + specific_component: Some(ProtoSpecificComponent { + component_name: "Researcher".to_string(), + component_index: 0, + }), + clock_name: "x".to_string(), + }), + y: Some(ProtoComponentClock { + specific_component: None, + clock_name: "0".to_string(), + }), + strict: false, + c: 8, + }, + ProtoConstraint { + x: Some(ProtoComponentClock { + specific_component: Some(ProtoSpecificComponent { + component_name: "Researcher".to_string(), + component_index: 0, + }), + clock_name: "x".to_string(), + }), + y: Some(ProtoComponentClock { + specific_component: Some(ProtoSpecificComponent { + component_name: "Administration".to_string(), + component_index: 0, + }), + clock_name: "z".to_string(), + }), + strict: false, + c: -2, + }, + ], + }], + }), + }), + }), + edges: vec![ + ProtoEdge { + id: "E13".to_string(), + specific_component: None, + }, + ProtoEdge { + id: "E29".to_string(), + specific_component: None, + }, + ProtoEdge { + id: "E44".to_string(), + specific_component: None, + }, + ProtoEdge { + id: "E9".to_string(), + specific_component: None, + }, + ], + }; + let decisionpoint2 = ProtoDecisionPoint { + source: Some(ProtoState { + location_tuple: Some(ProtoLocationTuple { + locations: vec![ + ProtoLocation { + id: "L0".to_string(), + specific_component: Some(ProtoSpecificComponent { + component_name: "Administration".to_string(), + component_index: 0, + }), + }, + ProtoLocation { + id: "L5".to_string(), + specific_component: Some(ProtoSpecificComponent { + component_name: "Machine".to_string(), + component_index: 0, + }), + }, + ProtoLocation { + id: "U0".to_string(), + specific_component: Some(ProtoSpecificComponent { + component_name: "Researcher".to_string(), + component_index: 0, + }), + }, + ], + }), + federation: Some(ProtoFederation { + disjunction: Some(ProtoDisjunction { + conjunctions: vec![ProtoConjunction { + constraints: vec![ + ProtoConstraint { + x: Some(ProtoComponentClock { + specific_component: None, + clock_name: "0".to_string(), + }), + y: Some(ProtoComponentClock { + specific_component: Some(ProtoSpecificComponent { + component_name: "Administration".to_string(), + component_index: 0, + }), + clock_name: "z".to_string(), + }), + strict: true, + c: -15, + }, + ProtoConstraint { + x: Some(ProtoComponentClock { + specific_component: Some(ProtoSpecificComponent { + component_name: "Administration".to_string(), + component_index: 0, + }), + clock_name: "z".to_string(), + }), + y: Some(ProtoComponentClock { + specific_component: Some(ProtoSpecificComponent { + component_name: "Machine".to_string(), + component_index: 0, + }), + clock_name: "y".to_string(), + }), + strict: false, + c: 0, + }, + ProtoConstraint { + x: Some(ProtoComponentClock { + specific_component: Some(ProtoSpecificComponent { + component_name: "Machine".to_string(), + component_index: 0, + }), + clock_name: "y".to_string(), + }), + y: Some(ProtoComponentClock { + specific_component: Some(ProtoSpecificComponent { + component_name: "Researcher".to_string(), + component_index: 0, + }), + clock_name: "x".to_string(), + }), + strict: false, + c: 0, + }, + ProtoConstraint { + x: Some(ProtoComponentClock { + specific_component: Some(ProtoSpecificComponent { + component_name: "Researcher".to_string(), + component_index: 0, + }), + clock_name: "x".to_string(), + }), + y: Some(ProtoComponentClock { + specific_component: Some(ProtoSpecificComponent { + component_name: "Administration".to_string(), + component_index: 0, + }), + clock_name: "z".to_string(), + }), + strict: false, + c: 0, + }, + ], + }], + }), + }), + }), + edges: vec![ + ProtoEdge { + id: "E29".to_string(), + specific_component: None, + }, + ProtoEdge { + id: "E44".to_string(), + specific_component: None, + }, + ], + }; + let response = SimulationStepResponse { + new_decision_points: vec![decisionpoint1, decisionpoint2], + }; + + Ok(Response::new(response)) +} + +pub fn get_state_after_HalfAdm1_HalfAdm2_conjunction() -> ProtoState { + ProtoState { + location_tuple: Some(ProtoLocationTuple { + locations: vec![ + ProtoLocation { + id: "L12".to_string(), + specific_component: Some(ProtoSpecificComponent { + component_name: "HalfAdm1".to_string(), + component_index: 0, + }), + }, + ProtoLocation { + id: "L14".to_string(), + specific_component: Some(ProtoSpecificComponent { + component_name: "HalfAdm2".to_string(), + component_index: 0, + }), + }, + ], + }), + federation: Some(ProtoFederation { + disjunction: Some(ProtoDisjunction { + conjunctions: vec![ProtoConjunction { + constraints: vec![ + ProtoConstraint { + x: Some(ProtoComponentClock { + specific_component: Some(ProtoSpecificComponent { + component_name: "HalfAdm1".to_string(), + component_index: 0, + }), + clock_name: "x".to_string(), + }), + y: Some(ProtoComponentClock { + specific_component: Some(ProtoSpecificComponent { + component_name: "HalfAdm2".to_string(), + component_index: 0, + }), + clock_name: "y".to_string(), + }), + strict: false, + c: 0, + }, + ProtoConstraint { + x: Some(ProtoComponentClock { + specific_component: Some(ProtoSpecificComponent { + component_name: "HalfAdm2".to_string(), + component_index: 0, + }), + clock_name: "y".to_string(), + }), + y: Some(ProtoComponentClock { + specific_component: Some(ProtoSpecificComponent { + component_name: "HalfAdm1".to_string(), + component_index: 0, + }), + clock_name: "x".to_string(), + }), + strict: false, + c: 0, + }, + ], + }], + }), + }), + } +} + +pub fn get_conjunction_response_HalfAdm1_HalfAdm2( +) -> Result, Status> { + let proto_decision_point = ProtoDecisionPoint { + source: Some(ProtoState { + location_tuple: Some(ProtoLocationTuple { + locations: vec![ + ProtoLocation { + id: "L12".to_string(), + specific_component: Some(ProtoSpecificComponent { + component_name: "HalfAdm1".to_string(), + component_index: 0, + }), + }, + ProtoLocation { + id: "L14".to_string(), + specific_component: Some(ProtoSpecificComponent { + component_name: "HalfAdm2".to_string(), + component_index: 0, + }), + }, + ], + }), + federation: Some(ProtoFederation { + disjunction: Some(ProtoDisjunction { + conjunctions: vec![ProtoConjunction { + constraints: vec![ + ProtoConstraint { + x: Some(ProtoComponentClock { + specific_component: Some(ProtoSpecificComponent { + component_name: "HalfAdm1".to_string(), + component_index: 0, + }), + clock_name: "x".to_string(), + }), + y: Some(ProtoComponentClock { + specific_component: Some(ProtoSpecificComponent { + component_name: "HalfAdm2".to_string(), + component_index: 0, + }), + clock_name: "y".to_string(), + }), + strict: false, + c: 0, + }, + ProtoConstraint { + x: Some(ProtoComponentClock { + specific_component: Some(ProtoSpecificComponent { + component_name: "HalfAdm2".to_string(), + component_index: 0, + }), + clock_name: "y".to_string(), + }), + y: Some(ProtoComponentClock { + specific_component: Some(ProtoSpecificComponent { + component_name: "HalfAdm1".to_string(), + component_index: 0, + }), + clock_name: "x".to_string(), + }), + strict: false, + c: 0, + }, + ], + }], + }), + }), + }), + edges: vec![ + ProtoEdge { + id: "E30".to_string(), + specific_component: None, + }, + ProtoEdge { + id: "E35".to_string(), + specific_component: None, + }, + ProtoEdge { + id: "E37".to_string(), + specific_component: None, + }, + ProtoEdge { + id: "E42".to_string(), + specific_component: None, + }, + ], + }; + + let response = SimulationStepResponse { + new_decision_points: vec![proto_decision_point], + }; + + Ok(Response::new(response)) +} + +pub fn get_conjunction_response_HalfAdm1_HalfAdm2_after_E37( +) -> Result, Status> { + let new_decision_points = ProtoDecisionPoint { + source: Some(ProtoState { + location_tuple: Some(ProtoLocationTuple { + locations: vec![ + ProtoLocation { + id: "L13".to_string(), + specific_component: Some(ProtoSpecificComponent { + component_name: "HalfAdm1".to_string(), + component_index: 0, + }), + }, + ProtoLocation { + id: "L14".to_string(), + specific_component: Some(ProtoSpecificComponent { + component_name: "HalfAdm2".to_string(), + component_index: 0, + }), + }, + ], + }), + federation: Some(ProtoFederation { + disjunction: Some(ProtoDisjunction { + conjunctions: vec![ProtoConjunction { + constraints: vec![ + ProtoConstraint { + x: Some(ProtoComponentClock { + specific_component: Some(ProtoSpecificComponent { + component_name: "HalfAdm1".to_string(), + component_index: 0, + }), + clock_name: "x".to_string(), + }), + y: Some(ProtoComponentClock { + specific_component: None, + clock_name: "0".to_string(), + }), + strict: false, + c: 2, + }, + ProtoConstraint { + x: Some(ProtoComponentClock { + specific_component: Some(ProtoSpecificComponent { + component_name: "HalfAdm1".to_string(), + component_index: 0, + }), + clock_name: "x".to_string(), + }), + y: Some(ProtoComponentClock { + specific_component: Some(ProtoSpecificComponent { + component_name: "HalfAdm2".to_string(), + component_index: 0, + }), + clock_name: "y".to_string(), + }), + strict: false, + c: 0, + }, + ], + }], + }), + }), + }), + edges: vec![ + ProtoEdge { + id: "E30".to_string(), + specific_component: None, + }, + ProtoEdge { + id: "E35".to_string(), + specific_component: None, + }, + ProtoEdge { + id: "E36".to_string(), + specific_component: None, + }, + ProtoEdge { + id: "E38".to_string(), + specific_component: None, + }, + ProtoEdge { + id: "E40".to_string(), + specific_component: None, + }, + ProtoEdge { + id: "E41".to_string(), + specific_component: None, + }, + ], + }; + + let response = SimulationStepResponse { + new_decision_points: vec![new_decision_points], + }; + Ok(Response::new(response)) +} + +// A request that Chooses the FAT EDGE: +// +// ----coin? E3----> +// / +// (L5,y>=0)=====tea! E5=====> +// +pub fn create_good_request() -> tonic::Request { + let simulation_info = + create_simulation_info_from(String::from("Machine"), create_sample_json_component()); + let initial_decision_point = create_initial_decision_point(); + let chosen_source = initial_decision_point.source.clone().unwrap(); + let chosen_edge = initial_decision_point.edges[1].clone(); + + tonic::Request::new(create_simulation_step_request( + simulation_info, + chosen_source, + chosen_edge, + )) +} + +pub fn create_expected_response_to_good_request() -> Result, Status> +{ + Ok(Response::new(SimulationStepResponse { + new_decision_points: vec![create_decision_point_after_taking_E5()], + })) +} + +pub fn create_mismatched_request_1() -> Request { + let simulation_info = + create_simulation_info_from(String::from("Machine"), create_sample_json_component()); + let chosen_source = create_state_not_in_machine(); + let chosen_edge = create_edges_from_L5()[0].clone(); + + tonic::Request::new(create_simulation_step_request( + simulation_info, + chosen_source, + chosen_edge, + )) +} + +pub fn create_expected_response_to_mismatched_request_1( +) -> Result, Status> { + Err(tonic::Status::invalid_argument( + "Mismatch between decision and system, state not in system", + )) +} + +pub fn create_mismatched_request_2() -> Request { + let simulation_info = + create_simulation_info_from(String::from("Machine"), create_sample_json_component()); + + let chosen_source = create_state_setup_for_mismatch(); + let chosen_edge = create_edges_from_L5()[1].clone(); // Should not be able to choose this edge + Request::new(create_simulation_step_request( + simulation_info, + chosen_source, + chosen_edge, + )) +} + +pub fn create_expected_response_to_mismatched_request_2( +) -> Result, Status> { + Err(tonic::Status::invalid_argument( + "Mismatch between decision and system, could not make transition", + )) +} + +pub fn create_malformed_component_request() -> Request { + let simulation_info = create_simulation_info_from(String::from(""), String::from("")); + let chosen_source = create_empty_state(); + let chosen_edge = create_empty_edge(); + + Request::new(create_simulation_step_request( + simulation_info, + chosen_source, + chosen_edge, + )) +} + +pub fn create_response_to_malformed_component_request( +) -> Result, Status> { + Err(Status::invalid_argument("Malformed component, bad json")) +} + +pub fn create_malformed_composition_request() -> Request { + let simulation_info = + create_simulation_info_from(String::from(""), create_sample_json_component()); + let chosen_source = create_empty_state(); + let chosen_edge = create_empty_edge(); + + Request::new(create_simulation_step_request( + simulation_info, + chosen_source, + chosen_edge, + )) +} + +pub fn create_response_to_malformed_compostion_request( +) -> Result, Status> { + Err(Status::invalid_argument( + "Malformed composition, bad expression", + )) +} + +// A || B || C +pub fn create_composition_request() -> Request { + let comp_names = vec!["Administration", "Machine", "Researcher"]; + let sample_name = "EcdarUniversity".to_string(); + let composition_string = "Administration || Machine || Researcher".to_string(); + + let components: Vec = create_components(&comp_names, sample_name); + let simulation_info = create_simulation_info(composition_string, components); + + let edge = ProtoEdge { + id: "E29".to_string(), + specific_component: None, + }; + + let source = get_state_after_Administration_Machine_Researcher_composition(); + + let simulation_step_request = create_simulation_step_request(simulation_info, source, edge); + + Request::new(simulation_step_request) +} + +pub fn create_expected_response_to_composition_request( +) -> Result, Status> { + get_composition_response_Administration_Machine_Researcher_after_E29() +} + +// A && B +pub fn create_conjunction_request() -> Request { + let comp_names = vec!["HalfAdm1", "HalfAdm2"]; + let sample_name = "EcdarUniversity".to_string(); + let composition_string = "HalfAdm1 && HalfAdm2".to_string(); + create_composition_string(&comp_names, CompositionType::Conjunction); + + let components: Vec = create_components(&comp_names, sample_name); + let simulation_info = create_simulation_info(composition_string, components); + + let edge = ProtoEdge { + id: "E37".to_string(), + specific_component: None, + }; + + let source = get_state_after_HalfAdm1_HalfAdm2_conjunction(); + + let simulation_step_request = create_simulation_step_request(simulation_info, source, edge); + + Request::new(simulation_step_request) +} + +pub fn create_expected_response_to_conjunction_request( +) -> Result, Status> { + get_conjunction_response_HalfAdm1_HalfAdm2_after_E37() +} + +pub fn create_good_start_request() -> Request { + create_simulation_start_request(String::from("Machine"), create_sample_json_component()) +} + +pub fn create_expected_response_to_good_start_request( +) -> Result, Status> { + Ok(Response::new(SimulationStepResponse { + new_decision_points: vec![create_initial_decision_point()], + })) +} + +pub fn create_malformed_component_start_request() -> Request { + create_simulation_start_request(String::from(""), String::from("")) +} + +pub fn create_malformed_composition_start_request() -> Request { + create_simulation_start_request(String::from(""), create_sample_json_component()) +} + +// A || B || C +pub fn create_composition_start_request() -> Request { + let comp_names = vec!["Administration", "Machine", "Researcher"]; + let sample_name = "EcdarUniversity".to_string(); + + let composition = create_composition_string(&comp_names, CompositionType::Composition); + let components: Vec = create_components(&comp_names, sample_name); + + let simulation_info = create_simulation_info(composition, components); + + Request::new(SimulationStartRequest { + simulation_info: Some(simulation_info), + }) +} + +pub fn create_expected_response_to_composition_start_request( +) -> Result, Status> { + get_composition_response_Administration_Machine_Researcher() +} + +// A && B +pub fn create_conjunction_start_request() -> Request { + let comp_names = vec!["HalfAdm1", "HalfAdm2"]; + let sample_name = "EcdarUniversity".to_string(); + let composition_string = create_composition_string(&comp_names, CompositionType::Conjunction); + + let components: Vec = create_components(&comp_names, sample_name); + let simulation_info = create_simulation_info(composition_string, components); + + Request::new(SimulationStartRequest { + simulation_info: Some(simulation_info), + }) +} + +pub fn create_expected_response_to_conjunction_start_request( +) -> Result, Status> { + get_conjunction_response_HalfAdm1_HalfAdm2() +} + +pub fn create_edges_from_L5() -> Vec { + vec![ + ProtoEdge { + id: "E27".to_string(), + specific_component: None, + }, + ProtoEdge { + id: "E29".to_string(), + specific_component: None, + }, + ] +} + +// Create the decision point drawn below: +// +// -----coin? E3-----> +// / +// (L5, universe)-------tea! E5-----> +// +pub fn create_initial_decision_point() -> ProtoDecisionPoint { + ProtoDecisionPoint { + source: Some(ProtoState { + location_tuple: Some(ProtoLocationTuple { + locations: vec![ProtoLocation { + id: "L5".to_string(), + specific_component: Some(ProtoSpecificComponent { + component_name: "Machine".to_string(), + component_index: 0, + }), + }], + }), + federation: Some(ProtoFederation { + disjunction: Some(ProtoDisjunction { + conjunctions: vec![ProtoConjunction { + constraints: vec![], + }], + }), + }), + }), + edges: create_edges_from_L5(), + } +} + +// Returns the Machine component as a String, in the .json format +pub fn create_sample_json_component() -> String { + fs::read_to_string(format!("{}/Components/Machine.json", ECDAR_UNI)).unwrap() +} + +// Create the decision point drawn below: +// +// -----coin? E3-----> +// / +// (L5,y>=2)-------tea! E5-----> +// +pub fn create_decision_point_after_taking_E5() -> ProtoDecisionPoint { + ProtoDecisionPoint { + source: Some(ProtoState { + location_tuple: Some(ProtoLocationTuple { + locations: vec![ProtoLocation { + id: "L5".to_string(), + specific_component: Some(ProtoSpecificComponent { + component_name: "Machine".to_string(), + component_index: 0, + }), + }], + }), + federation: Some(ProtoFederation { + disjunction: Some(ProtoDisjunction { + conjunctions: vec![ProtoConjunction { + constraints: vec![ProtoConstraint { + x: Some(ProtoComponentClock { + specific_component: None, + clock_name: "0".to_string(), + }), + y: Some(ProtoComponentClock { + specific_component: Some(ProtoSpecificComponent { + component_name: "Machine".to_string(), + component_index: 0, + }), + clock_name: "y".to_string(), + }), + strict: false, + c: -2, + }], + }], + }), + }), + }), + edges: create_edges_from_L5(), + } +} + +// Create a simulation state with the Machine component and the decision point drawn below: +// +// -----coin? E3-----> +// / +// (ε,y>=0)-------tea! E5-----> +// +pub fn create_state_not_in_machine() -> ProtoState { + create_1tuple_state_with_single_constraint("", "Machine", 0, "0", "y", 0, false) +} + +// create a state such that can't transition via E5 +pub fn create_state_setup_for_mismatch() -> ProtoState { + create_1tuple_state_with_single_constraint("L5", "Machine", 0, "y", "0", 2, true) +} diff --git a/src/tests/grpc/mod.rs b/src/tests/grpc/mod.rs index a133abbf..700700a3 100644 --- a/src/tests/grpc/mod.rs +++ b/src/tests/grpc/mod.rs @@ -1 +1,4 @@ pub mod send_query; +pub mod simulation; +pub mod start_simulation; +pub mod take_simulation_step; diff --git a/src/tests/grpc/simulation.rs b/src/tests/grpc/simulation.rs new file mode 100644 index 00000000..1239aec4 --- /dev/null +++ b/src/tests/grpc/simulation.rs @@ -0,0 +1,61 @@ +#[cfg(test)] +mod tests { + use crate::{ + tests::Simulation::helper::{create_start_request, create_step_request}, + ProtobufServer::{self, services::ecdar_backend_server::EcdarBackend}, + }; + use test_case::test_case; + use tonic::Request; + + #[test_case( + &["Machine"], + "samples/json/EcdarUniversity", + "(Machine)" + )] + #[test_case( + &["HalfAdm1", "HalfAdm2"], + "samples/json/EcdarUniversity", + "(HalfAdm1 && HalfAdm2)" + )] + #[test_case( + &["Administration", "Machine", "Researcher"], + "samples/json/EcdarUniversity", + "(Administration || Machine || Researcher)" + )] + #[test_case( + &["HalfAdm1", "HalfAdm2", "Machine", "Researcher"], + "samples/json/EcdarUniversity", + "((HalfAdm1 && HalfAdm2) || Machine || Researcher)" + )] + #[tokio::test] + async fn start_simulation_then_take_simulation_step( + component_names: &[&str], + components_path: &str, + composition: &str, + ) { + // Arrange + let backend = ProtobufServer::ConcreteEcdarBackend::default(); + let request = Request::new(create_start_request( + component_names, + components_path, + composition, + )); + + // Act + let response = backend.start_simulation(request).await; + + // Arrange + let request = Request::new(create_step_request( + component_names, + components_path, + composition, + response, + )); + + // Act + let response = backend.take_simulation_step(request).await; + + // Assert + assert!(response.is_ok()) + } +} diff --git a/src/tests/grpc/start_simulation.rs b/src/tests/grpc/start_simulation.rs new file mode 100644 index 00000000..1f2eb539 --- /dev/null +++ b/src/tests/grpc/start_simulation.rs @@ -0,0 +1,67 @@ +#[cfg(test)] +mod test { + use crate::ProtobufServer::{ + self, + services::{ + ecdar_backend_server::EcdarBackend, SimulationStartRequest, SimulationStepResponse, + }, + }; + use test_case::test_case; + use tonic::{Request, Response, Status}; + + #[test_case( + crate::tests::Simulation::test_data::create_good_start_request(), + crate::tests::Simulation::test_data::create_expected_response_to_good_start_request(); + "given a good request, responds with correct state" + )] + #[test_case( + crate::tests::Simulation::test_data::create_composition_start_request(), + crate::tests::Simulation::test_data::create_expected_response_to_composition_start_request(); + "given a composition request, responds with correct component" + )] + #[test_case( + crate::tests::Simulation::test_data::create_conjunction_start_request(), + crate::tests::Simulation::test_data::create_expected_response_to_conjunction_start_request(); + "given a good conjunction request, responds with correct component" + )] + #[tokio::test] + async fn start_simulation__responds_as_expected( + request: Request, + expected_response: Result, Status>, + ) { + // Arrange + let backend = ProtobufServer::ConcreteEcdarBackend::default(); + + // Act + let actual_response = backend.start_simulation(request).await; + + // Assert + assert_eq!( + format!("{:?}", expected_response), + format!("{:?}", actual_response) + ); + } + + #[ignore = "Server hangs on panic"] + #[test_case( + crate::tests::Simulation::test_data::create_malformed_component_start_request(); + "given a request with a malformed component, respond with error" + )] + #[test_case( + crate::tests::Simulation::test_data::create_malformed_composition_start_request(); + "given a request with a malformed composition, respond with error" + )] + #[tokio::test] + async fn start_simulation__bad_data__responds_with_error( + request: Request, + ) { + // Arrange + let backend = ProtobufServer::ConcreteEcdarBackend::default(); + + // Act + let actual_response = backend.start_simulation(request).await; + + // Assert + assert!(actual_response.is_err()); + } +} diff --git a/src/tests/grpc/take_simulation_step.rs b/src/tests/grpc/take_simulation_step.rs new file mode 100644 index 00000000..406827c5 --- /dev/null +++ b/src/tests/grpc/take_simulation_step.rs @@ -0,0 +1,72 @@ +#[cfg(test)] +mod test { + use crate::tests::Simulation::test_data; + use crate::ProtobufServer::services::{SimulationStepRequest, SimulationStepResponse}; + use crate::ProtobufServer::{self, services::ecdar_backend_server::EcdarBackend}; + use test_case::test_case; + use tonic::{self, Request, Response, Status}; + + #[test_case( + test_data::create_good_request(), + test_data::create_expected_response_to_good_request(); + "given a good request, responds with correct state" + )] + #[test_case( + test_data::create_composition_request(), + test_data::create_expected_response_to_composition_request(); + "given a composition request, responds with correct component" + )] + #[test_case( + test_data::create_conjunction_request(), + test_data::create_expected_response_to_conjunction_request(); + "given a good conjunction request, responds with correct component" + )] + #[tokio::test] + async fn take_simulation_step__responds_as_expected( + request: Request, + expected_response: Result, Status>, + ) { + // Arrange + let backend = ProtobufServer::ConcreteEcdarBackend::default(); + + // Act + let actual_response = backend.take_simulation_step(request).await; + + // Assert + assert_eq!( + format!("{:?}", expected_response), + format!("{:?}", actual_response) + ); + } + + #[ignore = "Server hangs on panic"] + #[test_case( + test_data::create_mismatched_request_1(); + "given a request with component decision mismatch, decision referencing source not in the set of states, responds with invalid argument" + )] + #[test_case( + test_data::create_mismatched_request_2(); + "given a request with component decision mismatch, decision making transition that is not possible, responds with invalid argument" + )] + #[test_case( + test_data::create_malformed_component_request(); + "given a request with a malformed component, responds with invalid argument" + )] + #[test_case( + test_data::create_malformed_composition_request(); + "given a request with a malformed composition, responds with invalid argument" + )] + #[tokio::test] + async fn take_simulation_step__bad_data__responds_with_error( + request: Request, + ) { + // Arrange + let backend = ProtobufServer::ConcreteEcdarBackend::default(); + + // Act + let actual_response = backend.take_simulation_step(request).await; + + // Assert + assert!(actual_response.is_err()); + } +} diff --git a/src/tests/mod.rs b/src/tests/mod.rs index 25d6262e..7e8144f0 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -2,6 +2,7 @@ use crate::ProtobufServer::services::query_request::Settings; pub mod ClockReduction; pub mod ModelObjects; +pub mod Simulation; pub mod edge_ids; pub mod failure_message; pub mod grpc;