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

test: introduce libp2p-swarm-test #2888

Merged
merged 82 commits into from Mar 8, 2023
Merged

test: introduce libp2p-swarm-test #2888

merged 82 commits into from Mar 8, 2023

Conversation

thomaseizinger
Copy link
Contributor

@thomaseizinger thomaseizinger commented Sep 12, 2022

Description

This patch-set introduces libp2p-swarm-test. It provides utilities for quick and safe bootstrapping of tests for NetworkBehaviours. The main design features are:

  • Everything has timeouts
  • APIs don't get in your way
  • Minimal boilerplate

Closes #2884.

Notes

I did not port libp2p-kad because the testing logic was too complicated. I also did not add tests for protocols that don't already have some. That is out of scope for this PR.

Open Questions

  • Should this testing crate also pull together utilities like ArbitraryPeerId?
    • No. That should go into quickcheck-ext.
  • Can we make the Swarm::new_ephemeral API any better? At the moment it only allows to configure the underlying transport but the authentication and muxer are hardcoded. I don't want to introduce any more parameters because it will get too verbose and there wouldn't be a benefit in having the function in the first place. Should we perhaps introduce a dedicated SwarmBuilder for our tests that start with some reasonable test defaults, like a MemoryTransport?
  • Trying to port the new harness to libp2p-ping, I noticed that we'd lose the MuxerChoice as part of the prop test. If we go forward with the SwarmBuilder extension idea, we could make a function there that cycles through various muxers and authentication algorithms to make sure things work across various stacks. I am not sure it is worth it though to be honest.

Open tasks

  • Port to more crates

Change checklist

  • I have performed a self-review of my own code
    - [ ] I have made corresponding changes to the documentation
    - [ ] I have added tests that prove my fix is effective or that my feature works
    - [ ] A changelog entry has been made in the appropriate crates

Copy link
Contributor

@melekes melekes left a comment

Choose a reason for hiding this comment

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

I'm in great support of this ❤️

Copy link
Member

@mxinden mxinden left a comment

Choose a reason for hiding this comment

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

Direction looks good to me. Thanks for pushing this forward.

swarm-test/src/lib.rs Outdated Show resolved Hide resolved
swarm-test/src/lib.rs Outdated Show resolved Hide resolved
swarm/src/lib.rs Show resolved Hide resolved
swarm-test/src/lib.rs Outdated Show resolved Hide resolved
@mxinden
Copy link
Member

mxinden commented Sep 16, 2022

Should this testing crate also pull together utilities like ArbitraryPeerId?

I am guessing that defining Arbitrary on PeerId directly under the cfg[test] doesn't expose outside of the crate. Is that correct?

@mxinden
Copy link
Member

mxinden commented Sep 16, 2022

  • Can we make the Swarm::new_ephemeral API any better? At the moment it only allows to configure the underlying transport but the authentication and muxer are hardcoded. I don't want to introduce any more parameters because it will get too verbose and there wouldn't be a benefit in having the function in the first place. Should we perhaps introduce a dedicated SwarmBuilder for our tests that start with some reasonable test defaults, like a MemoryTransport?

I think all our non Transport tests should use the MemoryTransport, thus I am in favor of this default.

@mxinden
Copy link
Member

mxinden commented Sep 16, 2022

Trying to port the new harness to libp2p-ping, I noticed that we'd lose the MuxerChoice as part of the prop test. If we go forward with the SwarmBuilder extension idea, we could make a function there that cycles through various muxers and authentication algorithms to make sure things work across various stacks. I am not sure it is worth it though to be honest.

I still have to put more thoughts into this, though as an intuition I suggest that a test only tests one thing. E.g. the libp2p-ping tests test libp2p-ping and not some permutation of different transports and muxers. I think that should happen in a separate dedicated test.

@thomaseizinger
Copy link
Contributor Author

Should this testing crate also pull together utilities like ArbitraryPeerId?

I am guessing that defining Arbitrary on PeerId directly under the cfg[test] doesn't expose outside of the crate. Is that correct?

That is correct.

  • Can we make the Swarm::new_ephemeral API any better? At the moment it only allows to configure the underlying transport but the authentication and muxer are hardcoded. I don't want to introduce any more parameters because it will get too verbose and there wouldn't be a benefit in having the function in the first place. Should we perhaps introduce a dedicated SwarmBuilder for our tests that start with some reasonable test defaults, like a MemoryTransport?

I think all our non Transport tests should use the MemoryTransport, thus I am in favor of this default.

Cool, that makes this simpler :)

Trying to port the new harness to libp2p-ping, I noticed that we'd lose the MuxerChoice as part of the prop test. If we go forward with the SwarmBuilder extension idea, we could make a function there that cycles through various muxers and authentication algorithms to make sure things work across various stacks. I am not sure it is worth it though to be honest.

I still have to put more thoughts into this, though as an intuition I suggest that a test only tests one thing. E.g. the libp2p-ping tests test libp2p-ping and not some permutation of different transports and muxers. I think that should happen in a separate dedicated test.

Yeah I am tending towards the same. We should have a good muxer test harness instead!

NB implementations should not make any assumptions about the
underlying transport implementations. Defaulting this to
`MemoryTransport` allows us to simplify the API a great deal.
We remove the dynamic muxer choice because it is not the responsibility
of the ping tests to check that the muxers behave identically.
Not sure where that came from!
@mxinden
Copy link
Member

mxinden commented Sep 21, 2022

Should this testing crate also pull together utilities like ArbitraryPeerId?

I am guessing that defining Arbitrary on PeerId directly under the cfg[test] doesn't expose outside of the crate. Is that correct?

That is correct.

That is unfortunate. In that case, I am in favor of providing an ArbitraryPeerId from this new crate.

Copy link
Contributor

@elenaf9 elenaf9 left a comment

Choose a reason for hiding this comment

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

Nice!

swarm-test/src/lib.rs Outdated Show resolved Hide resolved
swarm-test/src/lib.rs Outdated Show resolved Hide resolved
swarm/src/lib.rs Show resolved Hide resolved
///
/// Especially (2) is crucial for our usage of this function.
/// If a [`Swarm`] is not polled, nothing within it makes progress.
/// This can "starve" the other swarm which for example may wait for another message to be sent on a connection.
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 spent a good two hours of debugging timeouts in the test and unwoken tasks until it dawned on me why things were not working. @elenaf9 I believe this might also be the reason for your timeouts in #3345.

This function may seem like a beast but after trying various solutions locally, I am convinced it makes sense to add this to the test-harness and use this instead of future::join for testing Swarms.

Copy link
Member

@mxinden mxinden left a comment

Choose a reason for hiding this comment

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

Just looking at the diff, this is already a great step forward.

A couple of comments. Nothing major. Good to merge from my end.

swarm-test/src/lib.rs Show resolved Hide resolved
swarm-test/src/lib.rs Show resolved Hide resolved
Comment on lines +127 to +140
while res1.len() < NUM_EVENTS_SWARM_1 || res2.len() < NUM_EVENTS_SWARM_2 {
match futures::future::select(swarm1.next_swarm_event(), swarm2.next_swarm_event()).await {
Either::Left((o1, _)) => {
if let Ok(o1) = o1.try_into_output() {
res1.push(o1);
}
}
Either::Right((o2, _)) => {
if let Ok(o2) = o2.try_into_output() {
res2.push(o2);
}
}
}
}
Copy link
Member

Choose a reason for hiding this comment

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

Long term it would be nice to detect missed wake-ups in a NetworkBehaviour implementation with libp2p-swarm-test. If I am not mistaken, that is not possible with the above while select! loop as long as the other swarm triggers a wake-up.

Lets defer to a future pull request.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is kind of a conflicting requirement. I added this because it is very easy to poll a Swarm in a wrong way in a test, thus it is often the test that is buggy, not the NetworkBehaviour implementation.

swarm-test/src/lib.rs Outdated Show resolved Hide resolved
swarm-test/src/lib.rs Show resolved Hide resolved
let peer_id = PeerId::from(identity.public());

let transport = MemoryTransport::default()
.or_transport(libp2p_tcp::async_io::Transport::default())
Copy link
Member

Choose a reason for hiding this comment

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

Why do we need the TCP transport? Is this needed for libp2p-autonat?

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, and dcutr needs it too.

Comment on lines +256 to +276
let event = loop {
futures::select!(
event = swarm1.select_next_some() => {
if let SwarmEvent::Behaviour(request_response::Event::Message {
peer,
message: request_response::Message::Request { request, channel, .. }
}) = event {
assert_eq!(&request, &ping);
assert_eq!(&peer, &peer2_id);

drop(channel);
continue;
}
},
event = swarm2.select_next_some() => {
if let SwarmEvent::Behaviour(ev) = event {
break ev;
}
},
)
};
Copy link
Member

Choose a reason for hiding this comment

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

Can this not be done via the new shiny drive function?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Probably, I've introduced that one only at the very end!

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 am not sure actually, this one is a bit different because the two peers operate autonomously. drive is useful if you want to "step" through a test in a more linear fashion because it gives you access to the Swarms inbetween. Here, we can just create to tasks that operate independently.

thomaseizinger and others added 3 commits March 8, 2023 19:53
Co-authored-by: Max Inden <mail@max-inden.de>
@mergify mergify bot merged commit 7069d78 into master Mar 8, 2023
@mergify mergify bot deleted the swarm-test branch March 8, 2023 09:36
umgefahren pushed a commit to umgefahren/rust-libp2p that referenced this pull request Mar 8, 2024
Circular dependencies are problematic in several ways:

- They result in cognitive overhead for developers, in trying to figure out what depends on what.
- They present `cargo` with limits in what order the crates can be compiled in.
- They invalidate build caches unnecessarily thus forcing `cargo` to rebuild certain crates.
- They cause problems with tooling such as `release-please`.

To actually break the circular dependencies, this patch inlines the uses of `development_transport` in the examples and tests for all sub-crates. This is only meant to be a short-term fix until libp2p#3111 and libp2p#2888 are fixed.

To ensure we don't accidentally reintroduce this dependency, we add a basic CI that queries `cargo metadata` using `jq`.

Resolves libp2p#3053.
Fixes libp2p#3223.
Related: libp2p#2918 (comment)
Related: googleapis/release-please#1662
umgefahren pushed a commit to umgefahren/rust-libp2p that referenced this pull request Mar 8, 2024
This patch-set introduces `libp2p-swarm-test`. It provides utilities for quick and safe bootstrapping of tests for `NetworkBehaviour`s. The main design features are:

- Everything has timeouts
- APIs don't get in your way
- Minimal boilerplate

Closes libp2p#2884.

Pull-Request: libp2p#2888.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Introduce libp2p-swarm-test crate
4 participants