#[cfg(test)]
#[allow(dead_code)] pub mod helpers {
use crate::stubs::MessageBusStub;
use crate::{server_versions, Client};
use std::sync::Arc;
pub fn create_test_client() -> (Client, Arc<MessageBusStub>) {
create_test_client_with_version(server_versions::SIZE_RULES)
}
pub fn create_test_client_with_version(server_version: i32) -> (Client, Arc<MessageBusStub>) {
let message_bus = Arc::new(MessageBusStub::default());
let client = Client::stubbed(message_bus.clone(), server_version);
(client, message_bus)
}
pub fn create_test_client_with_ordered_proto_responses(responses: Vec<crate::messages::ResponseMessage>) -> (Client, Arc<MessageBusStub>) {
let message_bus = Arc::new(MessageBusStub::with_ordered_responses(responses));
let client = Client::stubbed(message_bus.clone(), server_versions::SIZE_RULES);
(client, message_bus)
}
#[cfg(feature = "sync")]
pub fn create_blocking_test_client() -> (crate::client::blocking::Client, Arc<MessageBusStub>) {
create_blocking_test_client_with_version(server_versions::SIZE_RULES)
}
#[cfg(feature = "sync")]
pub fn create_blocking_test_client_with_version(server_version: i32) -> (crate::client::blocking::Client, Arc<MessageBusStub>) {
let message_bus = Arc::new(MessageBusStub::default());
let client = crate::client::blocking::Client::stubbed(message_bus.clone(), server_version);
(client, message_bus)
}
#[cfg(feature = "sync")]
pub fn create_blocking_test_client_with_ordered_proto_responses(
responses: Vec<crate::messages::ResponseMessage>,
) -> (crate::client::blocking::Client, Arc<MessageBusStub>) {
let message_bus = Arc::new(MessageBusStub::with_ordered_responses(responses));
let client = crate::client::blocking::Client::stubbed(message_bus.clone(), server_versions::SIZE_RULES);
(client, message_bus)
}
pub fn assert_request_msg_id(message_bus: &MessageBusStub, index: usize, expected: crate::messages::OutgoingMessages) {
let request_messages = message_bus.request_messages.read().unwrap();
assert!(
request_messages.len() > index,
"Expected at least {} request messages, got {}",
index + 1,
request_messages.len()
);
assert_proto_msg_id(&request_messages[index], expected);
}
pub fn request_message_count(message_bus: &MessageBusStub) -> usize {
message_bus.request_messages.read().unwrap().len()
}
pub fn decode_request_proto<T: prost::Message + Default>(message_bus: &MessageBusStub, index: usize) -> T {
let request_messages = message_bus.request_messages.read().unwrap();
T::decode(&request_messages[index][crate::messages::MESSAGE_ID_LEN..]).unwrap()
}
pub fn assert_request_proto<T>(message_bus: &MessageBusStub, index: usize, expected_msg_id: crate::messages::OutgoingMessages, expected: &T)
where
T: prost::Message + Default + PartialEq + std::fmt::Debug,
{
assert_request_msg_id(message_bus, index, expected_msg_id);
let actual: T = decode_request_proto(message_bus, index);
assert_eq!(&actual, expected, "request {index} body mismatch");
}
pub fn assert_request<B: crate::testdata::builders::RequestEncoder>(message_bus: &MessageBusStub, index: usize, expected: &B) {
assert_request_proto(message_bus, index, B::MSG_ID, &expected.to_proto());
}
pub fn text_response(s: impl Into<String>) -> crate::messages::ResponseMessage {
crate::messages::ResponseMessage::from(&s.into().replace('|', "\0"))
}
pub fn proto_response(msg_type: crate::messages::IncomingMessages, bytes: Vec<u8>) -> crate::messages::ResponseMessage {
crate::messages::ResponseMessage::from_protobuf(msg_type as i32, bytes)
}
pub fn binary_proto<M: prost::Message>(msg_id: i32, proto: &M) -> Vec<u8> {
crate::messages::encode_protobuf_message(msg_id, &proto.encode_to_vec())
}
pub const UNKNOWN_MESSAGE_ID: i32 = 9799;
pub fn unknown_message_frame() -> Vec<u8> {
crate::messages::encode_protobuf_message(UNKNOWN_MESSAGE_ID, &[0x08, 0x64])
}
pub fn next_valid_id_frame(order_id: i32) -> Vec<u8> {
use crate::testdata::builders::{orders::next_valid_id, ResponseProtoEncoder};
binary_proto(
crate::messages::IncomingMessages::NextValidId as i32,
&next_valid_id().order_id(order_id).to_proto(),
)
}
pub fn managed_accounts_frame(accounts: &str) -> Vec<u8> {
binary_proto(
crate::messages::IncomingMessages::ManagedAccounts as i32,
&crate::proto::ManagedAccounts {
accounts_list: Some(accounts.to_string()),
},
)
}
pub fn error_envelope(request_id: Option<i32>, code: Option<i32>, msg: impl Into<String>) -> crate::proto::ErrorMessage {
crate::proto::ErrorMessage {
id: request_id,
error_time: None,
error_code: code,
error_msg: Some(msg.into()),
advanced_order_reject_json: None,
}
}
pub fn proto_error_response(request_id: i32, code: i32, msg: impl Into<String>) -> crate::messages::ResponseMessage {
proto_response(
crate::messages::IncomingMessages::Error,
prost::Message::encode_to_vec(&error_envelope(Some(request_id), Some(code), msg)),
)
}
pub fn error_frame(request_id: i32, code: i32, msg: impl Into<String>) -> Vec<u8> {
binary_proto(
crate::messages::IncomingMessages::Error as i32,
&error_envelope(Some(request_id), Some(code), msg),
)
}
pub fn binary_text(msg_id: i32, payload: &str) -> Vec<u8> {
let mut data = Vec::with_capacity(4 + payload.len());
data.extend_from_slice(&msg_id.to_be_bytes());
data.extend_from_slice(payload.as_bytes());
data
}
pub fn body(text: &str) -> Vec<u8> {
let fields: Vec<&str> = text.split_terminator('|').collect();
let msg_id: i32 = fields[0].parse().expect("body() fixture must start with a numeric msg_id");
debug_assert_ne!(
msg_id,
crate::messages::IncomingMessages::Error as i32,
"Error frames must use error_frame() — protobuf-framed since PR-D1"
);
let payload: String = fields[1..].iter().map(|f| format!("{f}\0")).collect();
binary_text(msg_id, &payload)
}
pub fn handshake_response_frame(server_version: i32, zone: &str) -> Vec<u8> {
format!("{server_version}\020240120 12:00:00 {zone}\0").into_bytes()
}
pub fn handshake_frames(server_version: i32, zone: &str, next_order_id: i32) -> Vec<Vec<u8>> {
vec![
handshake_response_frame(server_version, zone),
next_valid_id_frame(next_order_id),
managed_accounts_frame(TEST_ACCOUNT),
]
}
pub const FARM_OK_MSG: &str = "Market data farm connection is OK:usfarm";
pub fn farm_ok_frame_42() -> Vec<u8> {
error_frame(crate::client::ids::RequestId::nth(42).raw(), 2104, FARM_OK_MSG)
}
pub fn farm_ok_frame_unrouted() -> Vec<u8> {
error_frame(-1, 2104, FARM_OK_MSG)
}
pub fn execution_data_frame(client_id: i32, request_id: i32, order_id: i32, execution_id: &str) -> Vec<u8> {
use crate::testdata::builders::ResponseProtoEncoder;
let response = crate::testdata::builders::orders::execution_data()
.client_id(client_id)
.request_id(request_id)
.order_id(order_id)
.execution_id(execution_id);
binary_proto(crate::messages::IncomingMessages::ExecutionData as i32, &response.to_proto())
}
pub fn order_status_frame(client_id: i32, order_id: i32, status: crate::orders::OrderStatusKind) -> Vec<u8> {
use crate::testdata::builders::ResponseProtoEncoder;
let response = crate::testdata::builders::orders::order_status()
.client_id(client_id)
.order_id(order_id)
.status(status);
binary_proto(crate::messages::IncomingMessages::OrderStatus as i32, &response.to_proto())
}
pub fn order_status_response(status: crate::testdata::builders::orders::OrderStatusResponse) -> crate::messages::ResponseMessage {
use crate::testdata::builders::ResponseProtoEncoder;
proto_response(crate::messages::IncomingMessages::OrderStatus, status.encode_proto())
}
#[derive(Debug)]
pub struct NoticeTestData;
impl crate::subscriptions::StreamDecoder<NoticeTestData> for NoticeTestData {
const RESPONSE_MESSAGE_IDS: &'static [crate::messages::IncomingMessages] = &[crate::messages::IncomingMessages::HistogramData];
fn decode(_context: &crate::subscriptions::DecoderContext, _msg: &crate::messages::ResponseMessage) -> Result<NoticeTestData, crate::Error> {
Ok(NoticeTestData)
}
}
#[derive(Default)]
pub struct CapturingSink {
notices: std::sync::Mutex<Vec<crate::messages::Notice>>,
}
impl CapturingSink {
pub fn notices(&self) -> Vec<crate::messages::Notice> {
self.notices.lock().unwrap().clone()
}
pub fn last(&self) -> Option<crate::messages::Notice> {
self.notices.lock().unwrap().last().cloned()
}
pub fn count(&self) -> usize {
self.notices.lock().unwrap().len()
}
}
impl crate::transport::common::NoticeSink for CapturingSink {
fn deliver(&self, notice: crate::messages::Notice) {
self.notices.lock().unwrap().push(notice);
}
}
pub fn test_notice(code: i32, message: impl Into<String>) -> crate::messages::Notice {
crate::messages::Notice::synthesized(code, message.into())
}
pub mod constants {
pub const TEST_ACCOUNT: &str = "DU1234567";
pub const TEST_ACCOUNT_2: &str = "DU7654321";
pub const TEST_ACCOUNT_3: &str = "DU9876543";
pub const TEST_MODEL_CODE: &str = "TARGET2024";
pub const TEST_MODEL_CODE_2: &str = "GROWTH2024";
pub const TEST_CONTRACT_ID: i32 = 1001;
pub const TEST_CONTRACT_ID_2: i32 = 2002;
pub const TEST_ORDER_ID: i32 = 5001;
pub const TEST_ORDER_ID_2: i32 = 5002;
pub const TEST_TICKER_ID: i32 = 100;
pub const TEST_TICKER_ID_2: i32 = 200;
pub const TEST_REQ_ID_FIRST: i32 = crate::client::ids::REQUEST_ID_FLOOR;
pub const TEST_ORDER_ID_SEED: i32 = 9000;
}
pub use constants::*;
fn wire_msg_id(bytes: &[u8]) -> Option<i32> {
bytes
.split_first_chunk::<{ crate::messages::MESSAGE_ID_LEN }>()
.map(|(header, _)| i32::from_be_bytes(*header))
}
pub fn assert_proto_msg_id(bytes: &[u8], expected: crate::messages::OutgoingMessages) {
assert_eq!(wire_msg_id(bytes), Some(expected as i32 + crate::messages::PROTOBUF_MSG_ID));
}
pub fn count_proto_msgs(messages: &[Vec<u8>], expected: crate::messages::OutgoingMessages) -> usize {
let target = Some(expected as i32 + crate::messages::PROTOBUF_MSG_ID);
messages.iter().filter(|m| wire_msg_id(m) == target).count()
}
pub fn tws_error_notice(code: i32, message: impl Into<String>) -> crate::Error {
crate::Error::Notice(crate::messages::Notice::synthesized(code, message.into()))
}
pub fn assert_tws_error_message(err: crate::Error, expected_code: i32, expected_substring: &str) {
match err {
crate::Error::Notice(notice) => {
assert_eq!(notice.code, expected_code, "wrong error code");
assert!(
notice.message.contains(expected_substring),
"error message {:?} does not contain {expected_substring:?}",
notice.message
);
}
other => panic!("expected Error::Notice(code={expected_code}), got {other:?}"),
}
}
pub fn assert_decimal_parse_error<T: std::fmt::Debug>(result: Result<T, crate::Error>, offending_value: &str) {
match result {
Err(crate::Error::Parse(_, value, msg)) => {
assert_eq!(value, offending_value, "error should carry the offending wire value");
assert!(msg.contains("invalid decimal wire value"), "unexpected message: {msg}");
}
other => panic!("expected Error::Parse for {offending_value:?}, got {other:?}"),
}
}
pub fn assert_missing_field<T: std::fmt::Debug>(result: Result<T, crate::Error>, field: &str, message: &str) {
match result {
Err(crate::Error::Parse(_, name, reason)) => {
assert_eq!(name, field);
assert_eq!(reason, format!("missing in {message}"));
}
other => panic!("expected Error::Parse for a missing {field} in {message}, got {other:?}"),
}
}
pub fn assert_rejects_text_framing<T: std::fmt::Debug>(
expected: crate::messages::IncomingMessages,
text_frame: &str,
decode: impl FnOnce(&crate::messages::ResponseMessage) -> Result<T, crate::Error>,
) {
let message = crate::messages::ResponseMessage::from(text_frame);
assert_eq!(
message.message_type(),
expected,
"fixture is framed as the wrong message type; the decoder never reads it"
);
match decode(&message) {
Err(crate::Error::UnexpectedWireFormat(_)) => {}
other => panic!("expected Error::UnexpectedWireFormat for a text-framed {expected:?}, got {other:?}"),
}
}
}
#[cfg(test)]
#[allow(dead_code)] pub mod wire_enum {
pub fn check_wire_enum_round_trip<T>(table: &[(T, &'static str)])
where
T: std::fmt::Display + std::fmt::Debug + PartialEq + std::str::FromStr<Err = crate::Error> + crate::ToField,
{
for (variant, wire) in table {
assert_eq!(variant.to_string(), *wire, "Display for {variant:?}");
assert_eq!(&T::from_str(wire).unwrap(), variant, "FromStr({wire})");
assert_eq!(variant.to_field(), *wire, "ToField for {variant:?}");
}
}
pub fn check_wire_enum_rejects_unknown<T>(unknowns: &[&str])
where
T: std::str::FromStr<Err = crate::Error> + std::fmt::Debug,
{
for &s in unknowns {
let err = T::from_str(s);
assert!(
matches!(err, Err(crate::Error::Parse(_, _, _))),
"expected Parse error for {s:?}, got {err:?}",
);
}
}
const PROBE_CODES: std::ops::RangeInclusive<i32> = -8..=64;
pub fn check_wire_code_round_trip<T>(table: &[(T, i32)], unknown: fn(i32) -> T)
where
T: From<i32> + Into<i32> + Copy + PartialEq + std::fmt::Debug,
{
for &(variant, code) in table {
assert_eq!(T::from(code), variant, "From({code})");
assert_eq!(variant.into(), code, "i32::from({variant:?})");
}
for code in PROBE_CODES {
if table.iter().any(|&(_, listed)| listed == code) {
continue;
}
assert_eq!(
T::from(code),
unknown(code),
"code {code} is modeled by a variant the table does not list"
);
}
}
}
#[cfg(test)]
pub mod source_scan {
use std::path::Path;
pub fn visit_production_sources(visit: &mut impl FnMut(&Path, &str)) {
visit_dir(&Path::new(env!("CARGO_MANIFEST_DIR")).join("src"), visit);
}
fn visit_dir(dir: &Path, visit: &mut impl FnMut(&Path, &str)) {
let entries = std::fs::read_dir(dir).unwrap_or_else(|e| panic!("read_dir {}: {e}", dir.display()));
for entry in entries.flatten() {
let path = entry.path();
if entry.file_type().is_ok_and(|t| t.is_dir()) {
visit_dir(&path, visit);
continue;
}
let name = path.file_name().and_then(|n| n.to_str()).unwrap_or_default();
if !name.ends_with(".rs") || name == "tests.rs" || name.ends_with("_tests.rs") {
continue;
}
let contents = std::fs::read_to_string(&path).unwrap_or_else(|e| panic!("read {}: {e}", path.display()));
visit(&path, &contents);
}
}
}
#[cfg(test)]
#[path = "test_utils_tests.rs"]
mod tests;