diff --git a/crates/allure-cargotest/tests/e2e.rs b/crates/allure-cargotest/tests/e2e.rs index 462077d..6aeb2c2 100644 --- a/crates/allure-cargotest/tests/e2e.rs +++ b/crates/allure-cargotest/tests/e2e.rs @@ -615,6 +615,12 @@ fn generates_assertion_steps_by_default() { assert_eq!(json_string(passing, "status"), Some("passed")); assert!(passing.contains("\"name\":\"assert!(true)\",\"status\":\"passed\"")); assert!(passing.contains("assert_eq!")); + assert!(passing.contains( + "\"name\":\"assert_eq!(user_id, 42): expected feature flag to be enabled for user_id=42\"" + )); + assert!(passing.contains( + "\"name\":\"assert_eq!(is_valid, true): validation result was wrong for input [7, 13]\"" + )); assert!(passing.contains("assert_ne!")); assert!(passing.contains("\"name\":\"step_assertions_are_nested\"")); assert!(passing.contains("\"name\":\"assert_eq!(1 + 1, 2)\"")); @@ -624,7 +630,13 @@ fn generates_assertion_steps_by_default() { .expect("missing logs_failed_assertion_details result"); assert_has_allure_result_fields(failing); assert_eq!(json_string(failing, "status"), Some("failed")); - assert!(failing.contains("\"name\":\"assert_eq!")); + assert_eq!( + json_string(failing, "message"), + Some("validation result was wrong for input [404, 500]") + ); + assert!(failing.contains( + r#""name":"assert_eq!(\"actual\", \"expected\"): validation result was wrong for input [404, 500]""# + )); assert!(failing.contains("\"status\":\"failed\"")); assert!(failing.contains(r#""actual":"\"actual\"""#)); assert!(failing.contains(r#""expected":"\"expected\"""#)); diff --git a/crates/allure-test-macros/src/lib.rs b/crates/allure-test-macros/src/lib.rs index b189ed4..050248c 100644 --- a/crates/allure-test-macros/src/lib.rs +++ b/crates/allure-test-macros/src/lib.rs @@ -1194,6 +1194,42 @@ fn join_token_streams(streams: &[TokenStream]) -> String { .join(", ") } +#[derive(Clone)] +struct CustomAssertionMessage { + message_expr: String, + step_display_expr: String, +} + +fn custom_assertion_message(parts: &[TokenStream], start: usize) -> Option { + if parts.len() <= start { + return None; + } + + let custom = join_token_streams(&parts[start..]); + if custom.trim().is_empty() { + return None; + } + + Some(CustomAssertionMessage { + message_expr: format!("format!(\"{{}}\", format_args!({custom}))"), + step_display_expr: format!("format_args!({custom})"), + }) +} + +fn assertion_step_name(base_name: &str, custom_message: Option<&CustomAssertionMessage>) -> String { + match custom_message { + Some(custom_message) => format!( + "format!(\"{{}}: {{}}\", {base_name}, {})", + custom_message.step_display_expr + ), + None => base_name.to_string(), + } +} + +fn assertion_step_name_from_message_var(base_name: &str) -> String { + format!("format!(\"{{}}: {{}}\", {base_name}, __allure_assert_message)") +} + fn generate_assert_code(kind: AssertMacroKind, args: TokenStream) -> Option { let parts = split_macro_args(args.clone()); let condition = parts.first()?.clone(); @@ -1202,32 +1238,30 @@ fn generate_assert_code(kind: AssertMacroKind, args: TokenStream) -> Option 1 { - let custom = join_token_streams(&parts[1..]); - if custom.trim().is_empty() { - None - } else { - Some(format!("format!(\"{{}}\", format_args!({custom}))")) - } - } else { - None - }; + let custom_message = custom_assertion_message(&parts, 1); let message = custom_message + .as_ref() + .map(|custom_message| custom_message.message_expr.clone()) .unwrap_or_else(|| format!("format!(\"assertion failed: {{}}\", stringify!({condition}))")); let name = format!( "concat!(\"{}!(\", stringify!({condition}), \")\")", kind.macro_name() ); + let pass_name = assertion_step_name(&name, custom_message.as_ref()); + let fail_name = custom_message + .as_ref() + .map(|_| assertion_step_name_from_message_var(&name)) + .unwrap_or_else(|| name.clone()); let mut instrumented = String::new(); instrumented.push_str("if "); instrumented.push_str(&condition); instrumented.push_str(" { ::allure_cargotest::__private::record_assertion_pass("); - instrumented.push_str(&name); + instrumented.push_str(&pass_name); instrumented.push_str("); } else { let __allure_assert_message = "); instrumented.push_str(&message); instrumented.push_str("; ::allure_cargotest::__private::fail_assertion("); - instrumented.push_str(&name); + instrumented.push_str(&fail_name); instrumented.push_str( ", __allure_assert_message, Some(\"false\".to_string()), Some(\"true\".to_string())); }", ); @@ -1257,20 +1291,20 @@ fn generate_assert_cmp_code(kind: AssertMacroKind, args: TokenStream) -> Option< } else { "format!(\"assertion `left == right` failed\\n left: `{:?}`,\\n right: `{:?}`\", __allure_assert_left, __allure_assert_right)" }; - let message = if parts.len() > 2 { - let custom = join_token_streams(&parts[2..]); - if custom.trim().is_empty() { - default_message.to_string() - } else { - format!("format!(\"{{}}\", format_args!({custom}))") - } - } else { - default_message.to_string() - }; + let custom_message = custom_assertion_message(&parts, 2); + let message = custom_message + .as_ref() + .map(|custom_message| custom_message.message_expr.clone()) + .unwrap_or_else(|| default_message.to_string()); let name = format!( "concat!(\"{}!(\", stringify!({left}), \", \", stringify!({right}), \")\")", kind.macro_name() ); + let pass_name = assertion_step_name(&name, custom_message.as_ref()); + let fail_name = custom_message + .as_ref() + .map(|_| assertion_step_name_from_message_var(&name)) + .unwrap_or_else(|| name.clone()); let mut instrumented = String::new(); instrumented.push_str("match (&("); @@ -1284,11 +1318,11 @@ fn generate_assert_cmp_code(kind: AssertMacroKind, args: TokenStream) -> Option< instrumented.push_str( " *__allure_assert_right { ::allure_cargotest::__private::record_assertion_pass(", ); - instrumented.push_str(&name); + instrumented.push_str(&pass_name); instrumented.push_str("); } else { let __allure_assert_message = "); instrumented.push_str(&message); instrumented.push_str("; ::allure_cargotest::__private::fail_assertion("); - instrumented.push_str(&name); + instrumented.push_str(&fail_name); instrumented.push_str(", __allure_assert_message, Some(format!(\"{:?}\", __allure_assert_left)), Some(format!(\"{:?}\", __allure_assert_right))); } } }"); Some(runtime_assert_code( diff --git a/smokes/allure-cargotest/tests/assertions.rs b/smokes/allure-cargotest/tests/assertions.rs index e853005..8abf0a4 100644 --- a/smokes/allure-cargotest/tests/assertions.rs +++ b/smokes/allure-cargotest/tests/assertions.rs @@ -15,8 +15,22 @@ fn step_assertions_are_nested() { fn logs_passing_assertions() { allure.description("Verifies passing standard assertions are emitted as reviewable Allure steps."); allure.log_step("run passing assertion matrix"); + let user_id = 42; + let input = [7, 13]; + let is_valid = true; assert!(true); assert_eq!("actual", "actual"); + assert_eq!( + user_id, + 42, + "expected feature flag to be enabled for user_id={user_id}" + ); + assert_eq!( + is_valid, + true, + "validation result was wrong for input {:?}", + input + ); assert_ne!("left", "right"); helper_assertions_are_logged(); @@ -28,5 +42,11 @@ fn logs_passing_assertions() { fn logs_failed_assertion_details() { allure.description("Verifies a failed standard assertion records actual and expected values in status details."); allure.log_step("run assertion that should fail with captured details"); - assert_eq!("actual", "expected"); + let input = [404, 500]; + assert_eq!( + "actual", + "expected", + "validation result was wrong for input {:?}", + input + ); }