Skip to content

Commit

Permalink
feat(gtest): Add option to check for if a message panicked on RunResu…
Browse files Browse the repository at this point in the history
…lt (#3670)

Co-authored-by: Shamil <[email protected]>
Co-authored-by: StackOverflowExcept1on <[email protected]>
Co-authored-by: Dmitry Novikov <[email protected]>
  • Loading branch information
4 people authored Feb 7, 2024
1 parent 769d1f5 commit b447136
Show file tree
Hide file tree
Showing 5 changed files with 82 additions and 45 deletions.
15 changes: 5 additions & 10 deletions examples/waiter/tests/mx_lock_access.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
use demo_waiter::{Command, LockContinuation, LockStaticAccessSubcommand, MxLockContinuation};
use gear_core::ids::MessageId;
use gtest::{Program, System};
use utils::{assert_panicked, USER_ID};

mod utils;
pub const USER_ID: u64 = 10;

#[test]
fn drop_mx_lock_guard_from_different_msg_fails() {
Expand Down Expand Up @@ -39,14 +38,10 @@ fn access_mx_lock_guard_from_different_msg_fails(
let lock_access_result =
program.send(USER_ID, Command::MxLockStaticAccess(lock_access_subcommand));

assert_panicked(
&lock_access_result,
&format!(
"Mutex guard held by message {} is being accessed by message {}",
lock_msg_id,
lock_access_result.sent_message_id()
),
);
lock_access_result.assert_panicked_with(format!(
"Mutex guard held by message {lock_msg_id} is being accessed by message {}",
lock_access_result.sent_message_id(),
));
}

fn init_fixture(system: &System) -> (Program<'_>, MessageId) {
Expand Down
16 changes: 5 additions & 11 deletions examples/waiter/tests/rw_lock_access.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@ use demo_waiter::{
};
use gear_core::ids::MessageId;
use gtest::{Program, System};
use utils::{assert_panicked, USER_ID};

mod utils;
pub const USER_ID: u64 = 10;

#[test]
fn drop_r_lock_guard_from_different_msg_fails() {
Expand Down Expand Up @@ -83,15 +82,10 @@ fn access_rw_lock_guard_from_different_msg_fails(
Command::RwLockStaticAccess(lock_type, lock_access_subcommand),
);

assert_panicked(
&lock_access_result,
&format!(
"{:?} lock guard held by message {} is being accessed by message {}",
lock_type,
lock_msg_id,
lock_access_result.sent_message_id()
),
);
lock_access_result.assert_panicked_with(format!(
"{lock_type:?} lock guard held by message {lock_msg_id} is being accessed by message {}",
lock_access_result.sent_message_id(),
));
}

fn init_fixture(system: &System, lock_type: RwLockType) -> (Program<'_>, MessageId) {
Expand Down
18 changes: 0 additions & 18 deletions examples/waiter/tests/utils.rs

This file was deleted.

72 changes: 71 additions & 1 deletion gtest/src/log.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ use gear_core::{
ids::{MessageId, ProgramId},
message::{Payload, StoredMessage},
};
use gear_core_errors::{ErrorReplyReason, ReplyCode, SuccessReplyReason};
use gear_core_errors::{ErrorReplyReason, ReplyCode, SimpleExecutionError, SuccessReplyReason};
use std::{convert::TryInto, fmt::Debug};

/// A log that emitted by a program, for user defined logs,
Expand All @@ -34,6 +34,7 @@ pub struct CoreLog {
destination: ProgramId,
payload: Payload,
reply_code: Option<ReplyCode>,
reply_to: Option<MessageId>,
}

impl CoreLog {
Expand Down Expand Up @@ -61,6 +62,11 @@ impl CoreLog {
pub fn reply_code(&self) -> Option<ReplyCode> {
self.reply_code
}

/// Get the reply destination that the reply code was sent to.
pub fn reply_to(&self) -> Option<MessageId> {
self.reply_to
}
}

impl From<StoredMessage> for CoreLog {
Expand All @@ -73,6 +79,9 @@ impl From<StoredMessage> for CoreLog {
reply_code: other
.details()
.and_then(|d| d.to_reply_details().map(|d| d.to_reply_code())),
reply_to: other
.details()
.and_then(|d| d.to_reply_details().map(|d| d.to_message_id())),
}
}
}
Expand All @@ -88,6 +97,7 @@ pub struct DecodedCoreLog<T: Codec + Debug> {
destination: ProgramId,
payload: T,
reply_code: Option<ReplyCode>,
reply_to: Option<MessageId>,
}

impl<T: Codec + Debug> DecodedCoreLog<T> {
Expand All @@ -100,6 +110,7 @@ impl<T: Codec + Debug> DecodedCoreLog<T> {
destination: log.destination,
payload,
reply_code: log.reply_code,
reply_to: log.reply_to,
})
}
}
Expand Down Expand Up @@ -144,6 +155,7 @@ pub struct Log {
destination: Option<ProgramId>,
payload: Option<Payload>,
reply_code: Option<ReplyCode>,
reply_to: Option<MessageId>,
}

impl<ID, T> From<(ID, T)> for Log
Expand Down Expand Up @@ -257,6 +269,18 @@ impl Log {

self
}

/// Set the reply destination for this log.
#[track_caller]
pub fn reply_to(mut self, reply_to: MessageId) -> Self {
if self.reply_to.is_some() {
panic!("Reply destination was already set for this log");
}

self.reply_to = Some(reply_to);

self
}
}

impl PartialEq<StoredMessage> for Log {
Expand Down Expand Up @@ -286,6 +310,7 @@ impl<T: Codec + Debug> PartialEq<DecodedCoreLog<T>> for Log {
destination: other.destination,
payload: other.payload.encode().try_into().unwrap(),
reply_code: other.reply_code,
reply_to: other.reply_to,
};

core_log.eq(self)
Expand All @@ -305,6 +330,10 @@ impl PartialEq<CoreLog> for Log {
return false;
}

if self.reply_to.is_some() && self.reply_to != other.reply_to {
return false;
}

if let Some(source) = self.source {
if source != other.source {
return false;
Expand Down Expand Up @@ -396,6 +425,47 @@ impl RunResult {
.flat_map(DecodedCoreLog::try_from_log)
.collect()
}

/// If the main message panicked.
pub fn main_panicked(&self) -> bool {
self.main_panic_log().is_some()
}

/// Asserts that the main message panicked and that the panic contained a
/// given message.
#[track_caller]
pub fn assert_panicked_with(&self, msg: impl Into<String>) {
let panic_log = self.main_panic_log();
assert!(panic_log.is_some(), "Program did not panic");
let msg = msg.into();
let payload = String::from_utf8(
panic_log
.expect("Asserted using `Option::is_some()`")
.payload()
.into(),
)
.expect("Unable to decode panic message");

assert!(
payload.starts_with(&format!("Panic occurred: panicked with '{msg}'")),
"expected panic message that contains `{msg}`, but the actual panic message is `{payload}`"
);
}

/// Trying to get the panic log.
fn main_panic_log(&self) -> Option<&CoreLog> {
let main_log = self
.log
.iter()
.find(|log| log.reply_to == Some(self.message_id))?;
let is_panic = matches!(
main_log.reply_code(),
Some(ReplyCode::Error(ErrorReplyReason::Execution(
SimpleExecutionError::UserspacePanic
)))
);
is_panic.then_some(main_log)
}
}

#[test]
Expand Down
6 changes: 1 addition & 5 deletions gtest/src/program.rs
Original file line number Diff line number Diff line change
Expand Up @@ -853,12 +853,8 @@ mod tests {

let init_msg_payload = String::from("InvalidInput");
let run_result = prog.send(user_id, init_msg_payload);
assert!(run_result.main_failed());

let log = run_result.log();
let panic_msg_payload =
String::from_utf8(log[0].payload().into()).expect("Unable to decode panic message");
assert!(panic_msg_payload.contains("panicked with 'Failed to load destination"));
run_result.assert_panicked_with("Failed to load destination: Decode(Error)");

let run_result = prog.send(user_id, String::from("should_be_skipped"));

Expand Down

0 comments on commit b447136

Please sign in to comment.