Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions nexosim/src/server/codegen/simulation.v1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -583,6 +583,8 @@ pub mod schedule_query_request {
}
#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
pub struct ScheduleQueryReply {
/// This field is hoisted because protobuf3 does not support `repeated` within
/// a `oneof`. It is Always empty if an error is returned
#[prost(bytes = "vec", repeated, tag = "1")]
pub replies: ::prost::alloc::vec::Vec<::prost::alloc::vec::Vec<u8>>,
/// Always returns exactly 1 variant.
Expand Down
5 changes: 5 additions & 0 deletions nexosim/tests/codegen/simulation.v1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -583,6 +583,8 @@ pub mod schedule_query_request {
}
#[derive(Clone, PartialEq, Eq, Hash, ::prost::Message)]
pub struct ScheduleQueryReply {
/// This field is hoisted because protobuf3 does not support `repeated` within
/// a `oneof`. It is Always empty if an error is returned
#[prost(bytes = "vec", repeated, tag = "1")]
pub replies: ::prost::alloc::vec::Vec<::prost::alloc::vec::Vec<u8>>,
/// Always returns exactly 1 variant.
Expand Down Expand Up @@ -742,6 +744,7 @@ pub enum ErrorCode {
DuplicateQuerySource = 24,
DuplicateEventSink = 25,
InvalidBenchConfig = 26,
BenchAlreadyBuilt = 27,
/// Simulation runtime error.
SimulationPanic = 40,
SimulationNotStarted = 42,
Expand Down Expand Up @@ -788,6 +791,7 @@ impl ErrorCode {
Self::DuplicateQuerySource => "DUPLICATE_QUERY_SOURCE",
Self::DuplicateEventSink => "DUPLICATE_EVENT_SINK",
Self::InvalidBenchConfig => "INVALID_BENCH_CONFIG",
Self::BenchAlreadyBuilt => "BENCH_ALREADY_BUILT",
Self::SimulationPanic => "SIMULATION_PANIC",
Self::SimulationNotStarted => "SIMULATION_NOT_STARTED",
Self::SimulationTerminated => "SIMULATION_TERMINATED",
Expand Down Expand Up @@ -828,6 +832,7 @@ impl ErrorCode {
"DUPLICATE_QUERY_SOURCE" => Some(Self::DuplicateQuerySource),
"DUPLICATE_EVENT_SINK" => Some(Self::DuplicateEventSink),
"INVALID_BENCH_CONFIG" => Some(Self::InvalidBenchConfig),
"BENCH_ALREADY_BUILT" => Some(Self::BenchAlreadyBuilt),
"SIMULATION_PANIC" => Some(Self::SimulationPanic),
"SIMULATION_NOT_STARTED" => Some(Self::SimulationNotStarted),
"SIMULATION_TERMINATED" => Some(Self::SimulationTerminated),
Expand Down
6 changes: 6 additions & 0 deletions nexosim/tests/integration/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ mod injector;
mod model_scheduling;
mod serialization;
#[cfg(all(feature = "server", not(miri)))]
mod server_basic;
#[cfg(all(feature = "server", not(miri)))]
mod server_scheduling;
#[cfg(not(miri))]
mod simulation_clock_sync;
Expand All @@ -20,3 +22,7 @@ mod simulation_scheduling;
mod simulation_ticked_mode;
#[cfg(not(miri))]
mod simulation_timeout;

// Shared server test utils.
#[cfg(all(feature = "server", not(miri)))]
mod server_utils;
240 changes: 240 additions & 0 deletions nexosim/tests/integration/server_basic.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
use prost_types::Timestamp;
use serde::{Deserialize, Serialize};

use nexosim::model::{Context, Model};
use nexosim::ports::QuerySource;
use nexosim::simulation::{Mailbox, SimInit};

use super::server_utils::get_client;
use super::server_utils::grpc_client::{
BuildReply, BuildRequest, Error, ErrorCode, InitReply, InitRequest, Path, ProcessQueryReply,
ProcessQueryRequest, StepUntilRequest, TerminateReply, TerminateRequest, build_reply,
init_reply, process_query_reply, step_until_request, terminate_reply,
};
use crate::some_deadline_secs;

macro_rules! assert_resp_ok {
($resp:ident, $type:ident, $module:ident) => {
match $resp.into_inner() {
$type {
result: Some($module::Result::Empty(())),
} => (),
a => panic!("Expected Ok, got: {:?}", a),
};
};
}
macro_rules! assert_resp_err {
($resp:ident, $type:ident, $module:ident, $code:expr) => {
match $resp.into_inner() {
$type {
result: Some($module::Result::Error(Error { code, .. })),
} if code == $code as i32 => (),
a => panic!("Expected Err: {:?}, got: {:?}", $code, a),
};
};
}

#[derive(Serialize, Deserialize)]
struct SimpleModel;
#[Model]
impl SimpleModel {
async fn query(&mut self, value: i64, cx: &Context<Self>) -> i64 {
value * cx.time().as_secs()
}
}

fn simple_bench(_: u8) -> Result<SimInit, Box<dyn std::error::Error>> {
let simple = SimpleModel;
let simple_mbox = Mailbox::new();

let mut bench = SimInit::new();

QuerySource::new()
.connect(SimpleModel::query, &simple_mbox)
.bind_endpoint(&mut bench, "query")?;

// Bench assembly.
bench = bench.add_model(simple, simple_mbox, "simple");
Ok(bench)
}

#[tokio::test]
async fn init_before_build_fail() {
let (mut client, signal, handle) = get_client(simple_bench).await;

let resp = client
.init(InitRequest {
time: Some(Timestamp::default()),
})
.await
.unwrap();

assert_resp_err!(resp, InitReply, init_reply, ErrorCode::BenchNotBuilt);

// Shutdown.
signal.send(()).unwrap();
handle.await.unwrap();
}

#[tokio::test]
async fn subsequent_build_fail() {
let (mut client, signal, handle) = get_client(simple_bench).await;

let resp = client.build(BuildRequest { cfg: vec![0] }).await.unwrap();
assert_resp_ok!(resp, BuildReply, build_reply);

let resp = client.build(BuildRequest { cfg: vec![0] }).await.unwrap();
assert_resp_err!(resp, BuildReply, build_reply, ErrorCode::BenchAlreadyBuilt);

// Shutdown.
signal.send(()).unwrap();
handle.await.unwrap();
}

#[tokio::test]
async fn build_terminate_build() {
let (mut client, signal, handle) = get_client(simple_bench).await;

let resp = client.build(BuildRequest { cfg: vec![0] }).await.unwrap();
assert_resp_ok!(resp, BuildReply, build_reply);

let resp = client.terminate(TerminateRequest {}).await.unwrap();
assert_resp_ok!(resp, TerminateReply, terminate_reply);

let resp = client.build(BuildRequest { cfg: vec![0] }).await.unwrap();
assert_resp_ok!(resp, BuildReply, build_reply);

// Shutdown.
signal.send(()).unwrap();
handle.await.unwrap();
}

#[tokio::test]
async fn build_after_init_fail() {
let (mut client, signal, handle) = get_client(simple_bench).await;

let resp = client.build(BuildRequest { cfg: vec![0] }).await.unwrap();
assert_resp_ok!(resp, BuildReply, build_reply);

let resp = client
.init(InitRequest {
time: Some(Timestamp::default()),
})
.await
.unwrap();
assert_resp_ok!(resp, InitReply, init_reply);

let _ = client
.step_until(StepUntilRequest {
deadline: some_deadline_secs!(3, step_until_request),
})
.await
.unwrap();

let resp = client.build(BuildRequest { cfg: vec![0] }).await.unwrap();
assert_resp_err!(resp, BuildReply, build_reply, ErrorCode::BenchAlreadyBuilt);

// Verify that the simulation is sill alive and at a right ts.
let resp = client
.process_query(ProcessQueryRequest {
source: Some(Path {
segments: vec!["query".to_string()],
}),
request: vec![3],
})
.await
.unwrap();
match resp.into_inner() {
ProcessQueryReply {
result: Some(process_query_reply::Result::Empty(())),
replies,
} if replies == vec![vec![9]] => (),
a => panic!("Expected replies: [[9]], got: {:?}", a),
};

// Shutdown.
signal.send(()).unwrap();
handle.await.unwrap();
}

#[tokio::test]
async fn subsequent_init_fail() {
let (mut client, signal, handle) = get_client(simple_bench).await;

let resp = client.build(BuildRequest { cfg: vec![0] }).await.unwrap();
assert_resp_ok!(resp, BuildReply, build_reply);

// First init.
let resp = client
.init(InitRequest {
time: Some(Timestamp::default()),
})
.await
.unwrap();
assert_resp_ok!(resp, InitReply, init_reply);

// Step the simulation.
let _ = client
.step_until(StepUntilRequest {
deadline: some_deadline_secs!(2, step_until_request),
})
.await
.unwrap();

// Second init attempt.
let resp = client
.init(InitRequest {
time: Some(Timestamp::default()),
})
.await
.unwrap();
assert_resp_err!(resp, InitReply, init_reply, ErrorCode::BenchNotBuilt);

// Verify that the simulation is sill alive and at a right ts.
let resp = client
.process_query(ProcessQueryRequest {
source: Some(Path {
segments: vec!["query".to_string()],
}),
request: vec![3],
})
.await
.unwrap();
match resp.into_inner() {
ProcessQueryReply {
result: Some(process_query_reply::Result::Empty(())),
replies,
} if replies == vec![vec![6]] => (),
a => panic!("Expected replies: [[6]], got: {:?}", a),
};

// Shutdown.
signal.send(()).unwrap();
handle.await.unwrap();
}

#[tokio::test]
async fn build_after_complete_cycle() {
let (mut client, signal, handle) = get_client(simple_bench).await;

let resp = client.build(BuildRequest { cfg: vec![0] }).await.unwrap();
assert_resp_ok!(resp, BuildReply, build_reply);

let resp = client
.init(InitRequest {
time: Some(Timestamp::default()),
})
.await
.unwrap();
assert_resp_ok!(resp, InitReply, init_reply);

let resp = client.terminate(TerminateRequest {}).await.unwrap();
assert_resp_ok!(resp, TerminateReply, terminate_reply);

let resp = client.build(BuildRequest { cfg: vec![0] }).await.unwrap();
assert_resp_ok!(resp, BuildReply, build_reply);

// Shutdown.
signal.send(()).unwrap();
handle.await.unwrap();
}
Loading
Loading