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

Remove unwrap() in integration/e2e tests #512

Merged
merged 8 commits into from
Jan 30, 2025

Conversation

spacebear21
Copy link
Collaborator

Partially address #487.

There are other places to clean up, notably unit tests, that can be addressed in later PRs.

The commit messages might be a little repetitive but I wanted to keep each commit bite-sized for easier review. I can squash them if preferred.

@spacebear21 spacebear21 requested a review from DanGould January 26, 2025 23:47
@coveralls
Copy link
Collaborator

coveralls commented Jan 26, 2025

Pull Request Test Coverage Report for Build 13057514318

Details

  • 19 of 19 (100.0%) changed or added relevant lines in 1 file are covered.
  • No unchanged relevant lines lost coverage.
  • Overall coverage increased (+0.005%) to 78.544%

Totals Coverage Status
Change from base Build 13057114932: 0.005%
Covered Lines: 3646
Relevant Lines: 4642

💛 - Coveralls

Copy link
Collaborator

@nothingmuch nothingmuch left a comment

Choose a reason for hiding this comment

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

cACK, partial review with a few comments.

Main takeaway is I'm not sure about when .expect() is preferred over .map_err() with format! and bubbling up...

ISTM that the benefits, in descending priority order are:

  1. eliminate unwrap() to avoid vague failures without context (though ? doesn't materially improve that), and to make any snippets copied from tests be better behaved in whatever context they end up in (where ? does make a material difference)
  2. simplifying types for things that should never fail
  3. concision helping readability

is that correct?

fn find_free_port() -> u16 {
let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
listener.local_addr().unwrap().port()
assert!(payjoin_sent??.unwrap_or(false), "Payjoin send was not detected");
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm confused by .unwrap().unwrap_or(Some(false)).unwrap() becoming payjoin_sent??.unwrap_or(false).

It's a Result<Result<Option<bool>, Elapsed>, JoinError>, where the Option originates from the mpsc's recv(), the `bool is the value sent through the channel.

So isn't this equivalent to payjoin_sent.unwrap().unwrap().unwrap_or(false) instead, i.e. that a timeout bubbles up instead of becoming an assertion error? isn't it more appropriate for the assertion to fail in case of a timeout in which case .unwrap_or(Some(false)).ok_or(...)? would be closer in spirit to what it was, with i guess an anyhow error in the ... indicating the mpsc recv should have worked

also, using await? on the task seems like it reduce some of the noise... if you agree with this suggestion and the previous paragraph, i guess this implies the unwrap_or(Some(false).ok_or() is better done inside the task, so that await?? can bubble the task joining error if any, followed by the ok_or, and leaving payjoin_sent as just a bool?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Great catch! I applied your suggestion, with expect instead of ok_or since the rx channel should never close early.

pub fn take_directory_handle(&mut self) -> Option<JoinHandle<Result<(), BoxSendSyncError>>> {
self.directory.1.take()
pub fn take_directory_handle(&mut self) -> JoinHandle<Result<(), BoxSendSyncError>> {
self.directory.1.take().expect("directory handle not found")
Copy link
Collaborator

Choose a reason for hiding this comment

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

hmm, i prefer the simpler type here over expecting the caller to bubble this up, since this kind of thing should never fail a test but indicates the test environment is not as expected, having an expect message puts that kind of failure into context...

but i want to make sure i'm not missing anything about tests returning Result, is it equivalent to the test runner doing .unwrap() for us? i found this mentioned in rust by example, which links to a seemingly stale doc link that leads back to rust by example discussing this in the context of main (https://doc.rust-lang.org/edition-guide/rust-2018/error-handling-and-panics/question-mark-in-main-and-tests.html) and ? is ~impossible to websearch for

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yes, when an Error result bubbles up all the way to the test runner it fails the test and prints a debug representation of the error. The way it's displayed is slightly cleaner than when calling unwrap directly, e.g.:

unwrap:

---- integration::v1::v1_to_v1_p2pkh stdout ----
thread 'integration::v1::v1_to_v1_p2pkh' panicked at payjoin/tests/integration.rs:998:51:
called `Result::unwrap()` on an `Err` value: PsbtEncoding(ConsensusEncoding(Io(Error { kind: UnexpectedEof, error: None })))
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

?:

---- integration::v1::v1_to_v1_p2pkh stdout ----
Error: PsbtEncoding(ConsensusEncoding(Io(Error { kind: UnexpectedEof, error: None })))

Ok(ClientBuilder::new()
.danger_accept_invalid_certs(true)
.use_rustls_tls()
.add_root_certificate(reqwest::tls::Certificate::from_der(cert_der.as_slice()).unwrap()))
.add_root_certificate(reqwest::tls::Certificate::from_der(cert_der.as_slice())?))
Copy link
Collaborator

Choose a reason for hiding this comment

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

in line with the remark on take_... variants, is this perhaps better handled with .expect()?

@spacebear21 spacebear21 force-pushed the no-unwrap-in-tests branch 2 times, most recently from 5ca026e to 988ded4 Compare January 28, 2025 20:12
@spacebear21
Copy link
Collaborator Author

spacebear21 commented Jan 28, 2025

Main takeaway is I'm not sure about when .expect() is preferred over .map_err() with format! and bubbling up...

ISTM that the benefits, in descending priority order are:

1. eliminate unwrap() to avoid vague failures without context (though ? doesn't materially improve that), and to make any snippets copied from tests be better behaved in whatever context they end up in (where ? does make a material difference)
2. simplifying types for things that should never fail
3. concision helping readability

is that correct?

Yes - I'd add another benefit which is that expect() panics immediately whereas bubbling up with map_err()? allows us to handle errors appropriately, which helps towards #421 and also opens the door for testing things we expect to fail with specific errors, e.g. #339.

Copy link
Contributor

@DanGould DanGould left a comment

Choose a reason for hiding this comment

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

ACK 988ded4

I left some comments on further cleanups, but I think everything here is pointed in the right direction i.e. cleans up tech debt rather than takes new on and don't see big reasons not to merge and address the concerns in a follow up referencing the issues stated here.

@@ -137,7 +137,7 @@ mod e2e {

#[cfg(feature = "v2")]
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn send_receive_payjoin() {
async fn send_receive_payjoin() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: Why not use BoxSendSyncError here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It would have to be imported in mod e2e but that results in a "unused" warning when the v2 feature is disabled. I suppose we could feature-gate the import, or put the v1 and v2 tests in separate modules like we do in integration.rs

Copy link
Contributor

Choose a reason for hiding this comment

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

That's a good reason. It would also be more organized to separate v1, v2 modules (and even separate files that use super::* but is not a necessity.

payjoin-test-utils/src/lib.rs Outdated Show resolved Hide resolved
payjoin/tests/integration.rs Show resolved Hide resolved
payjoin/tests/integration.rs Outdated Show resolved Hide resolved
payjoin/tests/integration.rs Outdated Show resolved Hide resolved
payjoin-cli/tests/e2e.rs Outdated Show resolved Hide resolved
@spacebear21
Copy link
Collaborator Author

My latest push addresses the minor issues Dan brought up, and should be good to merge as-is.

Copy link
Contributor

@DanGould DanGould left a comment

Choose a reason for hiding this comment

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

ACK 2f14846

@@ -137,7 +137,7 @@ mod e2e {

#[cfg(feature = "v2")]
#[tokio::test(flavor = "multi_thread", worker_threads = 4)]
async fn send_receive_payjoin() {
async fn send_receive_payjoin() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
Copy link
Contributor

Choose a reason for hiding this comment

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

That's a good reason. It would also be more organized to separate v1, v2 modules (and even separate files that use super::* but is not a necessity.

@DanGould DanGould merged commit 01107a8 into payjoin:master Jan 30, 2025
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants