Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhance XCM Debugging with Log Capture in Unit Tests #7594

Open
wants to merge 37 commits into
base: master
Choose a base branch
from

Conversation

raymondkfcheung
Copy link
Contributor

@raymondkfcheung raymondkfcheung commented Feb 17, 2025

Description

This PR introduces a lightweight log-capturing mechanism for XCM unit tests, simplifying debugging by enabling structured log assertions. It partially addresses #6119 and #6125, offering an optional way to verify logs in tests while remaining unobtrusive in normal execution.

Key Changes

  • Introduces a log capture utility in sp_tracing.
  • Adds XCM test examples demonstrating how and when to use log capturing.

Review Notes:

  • The log capture mechanism is opt-in and does not affect existing tests unless explicitly used.
  • The implementation is minimal and does not add complexity to existing test setups.
  • It provides a structured alternative to sp_tracing::init_for_tests() for log verification in automated tests.

@raymondkfcheung raymondkfcheung requested a review from a team as a code owner February 17, 2025 14:58
@raymondkfcheung raymondkfcheung self-assigned this Feb 17, 2025
@raymondkfcheung raymondkfcheung marked this pull request as draft February 17, 2025 14:58
@raymondkfcheung raymondkfcheung changed the title Assert on captured logs on XCM Simulator Improve XCM Debugging by Capturing Logs in Unit Tests Feb 17, 2025
@raymondkfcheung raymondkfcheung added the T6-XCM This PR/Issue is related to XCM. label Feb 17, 2025
@raymondkfcheung
Copy link
Contributor Author

Currently, there is no straightforward way to verify if log messages are correctly output during unit tests.

I initially explored using tracing-test, but it only captures logs at the global scope (see source). This means that when testing inside Relay::execute_with(|| { ... }), the logs are not captured in global_buf(), making it unsuitable for verifying log output in such cases.

For sp_tracing::try_init_simple() and sp_tracing::init_for_tests(), they are designed to enable log output but do not provide a way to capture logs for assertions in tests.

Given this, I've implemented a custom log capture mechanism within sp_tracing to support verifying logs in unit tests, particularly for XCM-related tests. Let me know if you have any thoughts on this approach!

@acatangiu
Copy link
Contributor

Given this, I've implemented a custom log capture mechanism within sp_tracing to support verifying logs in unit tests, particularly for XCM-related tests. Let me know if you have any thoughts on this approach!

if this is simple, then sure, it's nice to have, otherwise I don't think we really need to verify this in tests - we have many devs acting as canaries 😆

@raymondkfcheung
Copy link
Contributor Author

Yeah, this isn’t meant to replace devs acting as canaries, but rather to make debugging more efficient when needed. The implementation isn’t difficult, and there’s a use case where rollback clears events, making it hard to assert them—capturing and asserting logs could be an easier alternative in such cases.

@raymondkfcheung raymondkfcheung marked this pull request as ready for review February 19, 2025 16:27
@raymondkfcheung raymondkfcheung added the T10-tests This PR/Issue is related to tests. label Feb 19, 2025
@paritytech-workflow-stopper
Copy link

All GitHub workflows were cancelled due to failure one of the required jobs.
Failed workflow url: https://github.com/paritytech/polkadot-sdk/actions/runs/13432779325
Failed job name: check-try-runtime

@raymondkfcheung raymondkfcheung changed the title Improve XCM Debugging by Capturing Logs in Unit Tests Enhance XCM Debugging with Log Capture in Unit Tests Feb 20, 2025
@serban300 serban300 self-requested a review February 21, 2025 08:43
Copy link
Contributor

@serban300 serban300 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personally I'm not familiar with the topic of capturing logs in tests. I'm surprised we don't do this in any other test yet. But anyway, seems like a more complex topic and that part of the code might take longer to merge.

I would suggest moving the XCM test changes without log capturing in a separate PR, which could be reviewed and merge immediately. And having the log capturing PR separate. After the log capturing is merged, we can improve the previously added XCM tests with log capturing.

@raymondkfcheung
Copy link
Contributor Author

I would suggest moving the XCM test changes without log capturing in a separate PR, which could be reviewed and merge immediately. And having the log capturing PR separate. After the log capturing is merged, we can improve the previously added XCM tests with log capturing.

@serban300 My changes to the XCM tests are meant to illustrate how test_log_capture can be used, so I included them in this PR. But if you prefer a clearer separation, I suggest splitting into two PRs:

  1. This PR focuses on adding test_log_capture to sp_tracing.
  2. A separate PR adds XCM tests as examples.

Let me know what you think!

Copy link
Contributor

@acatangiu acatangiu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is simple and clear enough, I have no objections

@serban300
Copy link
Contributor

@serban300 My changes to the XCM tests are meant to illustrate how test_log_capture can be used, so I included them in this PR. But if you prefer a clearer separation, I suggest splitting into two PRs:

1. This PR focuses on adding `test_log_capture` to `sp_tracing`.

2. A separate PR adds XCM tests as examples.

Let me know what you think!

Ok, if the tests are only for showcasing the log capturing feature, we can keep the PR as it is.

@raymondkfcheung
Copy link
Contributor Author

Ok, if the tests are only for showcasing the log capturing feature, we can keep the PR as it is.

Yes, showcasing only.

@raymondkfcheung
Copy link
Contributor Author

/cmd fmt

///
/// For more details, see [`tracing-test`](https://crates.io/crates/tracing-test).
#[cfg(feature = "std")]
pub use tracing_test;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would move this inside the test_log_capture mod. Also I wouldn't add examples related to this. There should be examples in the tracing-test crate.

Copy link
Contributor Author

@raymondkfcheung raymondkfcheung Feb 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that makes sense. Maybe we can remove tracing-test and implement our own log capture logic, as discussed here.

/// assert!(test_log_capture::logs_contain("test log message"));
/// ```
#[cfg(feature = "std")]
pub mod test_log_capture {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would guard this under a test feature as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK

);

// Ensure an error occurred
assert!(result.is_err(), "Expected an error due to invalid destination");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Could we move the assert!(test_log_capture::logs_contain("XCM validate_send failed")); after this line ? I think it would make sense to check if the logged error is the one that we expected right here, after we know that we got an error.

Comment on lines +338 to +339
pub fn capture_with_max_level<F: FnOnce()>(max_level: impl Into<LevelFilter>, f: F) {
let log_capture = MockWriter::new(&global_buf());
Copy link
Contributor

@serban300 serban300 Feb 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personally I think it would be a bit more flexible if we avoided using a closure and if we avoided using a global buffer.

I would do something like:

	pub fn init_buffer_logger(
		max_level: impl Into<LevelFilter>,
	) -> (DefaultGuard, Arc<Mutex<Vec<u8>>>) {
		let buf = Arc::new(Mutex::new(Vec::new()));
		let subscriber = tracing_subscriber::fmt()
			.with_max_level(max_level)
			.with_writer(MockMakeWriter::new(buf.clone()))
			.finish();
		(subscriber::set_default(subscriber), buf)
	}

(not sure about the naming)

The current MockWriter wouldn't work with this approach. We would have to use the MockWriter from tracing-subscriber or something similar. But it's really small and this way we could remove the tracing-test dependency as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this approach won’t work unless we implement our own Writer, similar to what we did here:
log_capture_test.rs#L13.

I'm okay with removing tracing-test completely if we go with this approach.

Comment on lines +330 to +336
/// test_log_capture::capture_with_max_level(Level::INFO, || {
/// tracing::info!("This will be captured at INFO level");
/// tracing::debug!("This will be captured at DEBUG level");
/// });
///
/// assert!(test_log_capture::logs_contain("INFO level"));
/// assert!(!test_log_capture::logs_contain("DEBUG level"));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I played with this a bit and in my experiments indeed it works when I do tracing::info, but it doesn't work with log::info. I think a lot of the logs are emitted using log:: methods.

Maybe I'm doing something wrong. But just saying in case it's worth looking into.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven’t tested with log, but tracing seems to be the preferred way to log messages. To keep it simple, we can focus on tracing for now.

Comment on lines +141 to +158
let attempted_count = events
.iter()
.filter(|e| {
matches!(
e.event,
relay_chain::RuntimeEvent::XcmPallet(pallet_xcm::Event::Attempted { .. })
)
})
.count();
let sent_count = events
.iter()
.filter(|e| {
matches!(
e.event,
relay_chain::RuntimeEvent::XcmPallet(pallet_xcm::Event::Sent { .. })
)
})
.count();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we plan to count the number of logs in other places as well it would be worth deduplicating this logic.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
T6-XCM This PR/Issue is related to XCM. T10-tests This PR/Issue is related to tests.
Projects
Status: In-Review
Development

Successfully merging this pull request may close these issues.

3 participants