diff --git a/Cargo.lock b/Cargo.lock index 232db2d..5c9e216 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -130,6 +130,8 @@ name = "bittorrent_udp" version = "0.1.0" dependencies = [ "byteorder", + "quickcheck", + "quickcheck_macros", ] [[package]] diff --git a/bittorrent_udp/Cargo.toml b/bittorrent_udp/Cargo.toml index 6d8f756..737ea96 100644 --- a/bittorrent_udp/Cargo.toml +++ b/bittorrent_udp/Cargo.toml @@ -5,4 +5,8 @@ authors = ["Joakim FrostegÄrd "] edition = "2018" [dependencies] -byteorder = "1" \ No newline at end of file +byteorder = "1" + +[dev-dependencies] +quickcheck = "0.9" +quickcheck_macros = "0.9" \ No newline at end of file diff --git a/bittorrent_udp/src/converters/responses.rs b/bittorrent_udp/src/converters/responses.rs index 145ede9..388cc73 100644 --- a/bittorrent_udp/src/converters/responses.rs +++ b/bittorrent_udp/src/converters/responses.rs @@ -184,4 +184,62 @@ pub fn response_from_bytes( })) } } +} + + +#[cfg(test)] +mod tests { + use quickcheck::{TestResult, quickcheck}; + + use super::*; + + fn same_after_conversion(response: Response, ip_version: IpVersion) -> bool { + let mut buf = Vec::new(); + + response_to_bytes(&mut buf, response.clone(), ip_version); + let r2 = response_from_bytes(&buf[..], ip_version).unwrap(); + + let success = response == r2; + + if !success { + println!("before: {:#?}\nafter: {:#?}", response, r2); + } + + success + } + + #[test] + fn test_convert_identity_connect_response(){ + fn prop(response: ConnectResponse) -> TestResult { + TestResult::from_bool(same_after_conversion(Response::Connect(response), IpVersion::IPv4)) + } + + quickcheck(prop as fn(ConnectResponse) -> TestResult); + } + + #[test] + fn test_convert_identity_announce_response(){ + fn prop(data: (AnnounceResponse, IpVersion)) -> TestResult { + let mut r = data.0; + + if data.1 == IpVersion::IPv4 { + r.peers.retain(|peer| peer.ip_address.is_ipv4()); + } else { + r.peers.retain(|peer| peer.ip_address.is_ipv6()); + } + + TestResult::from_bool(same_after_conversion(Response::Announce(r), data.1)) + } + + quickcheck(prop as fn((AnnounceResponse, IpVersion)) -> TestResult); + } + + #[test] + fn test_convert_identity_scrape_response(){ + fn prop(response: ScrapeResponse) -> TestResult { + TestResult::from_bool(same_after_conversion(Response::Scrape(response), IpVersion::IPv4)) + } + + quickcheck(prop as fn(ScrapeResponse) -> TestResult); + } } \ No newline at end of file diff --git a/bittorrent_udp/src/types/common.rs b/bittorrent_udp/src/types/common.rs index 7bfd66d..0818359 100644 --- a/bittorrent_udp/src/types/common.rs +++ b/bittorrent_udp/src/types/common.rs @@ -47,4 +47,27 @@ pub struct PeerKey (pub u32); pub struct ResponsePeer { pub ip_address: net::IpAddr, pub port: Port, +} + + +#[cfg(test)] +impl quickcheck::Arbitrary for IpVersion { + fn arbitrary(g: &mut G) -> Self { + if bool::arbitrary(g) { + IpVersion::IPv4 + } else { + IpVersion::IPv6 + } + } +} + + +#[cfg(test)] +impl quickcheck::Arbitrary for ResponsePeer { + fn arbitrary(g: &mut G) -> Self { + Self { + ip_address: ::std::net::IpAddr::arbitrary(g), + port: Port(u16::arbitrary(g)), + } + } } \ No newline at end of file diff --git a/bittorrent_udp/src/types/response.rs b/bittorrent_udp/src/types/response.rs index 196647b..ac0fff0 100644 --- a/bittorrent_udp/src/types/response.rs +++ b/bittorrent_udp/src/types/response.rs @@ -43,3 +43,59 @@ pub enum Response { Scrape(ScrapeResponse), Error(ErrorResponse), } + + +#[cfg(test)] +impl quickcheck::Arbitrary for TorrentScrapeStatistics { + fn arbitrary(g: &mut G) -> Self { + Self { + seeders: NumberOfPeers(i32::arbitrary(g)), + completed: NumberOfDownloads(i32::arbitrary(g)), + leechers: NumberOfPeers(i32::arbitrary(g)), + } + } +} + + +#[cfg(test)] +impl quickcheck::Arbitrary for ConnectResponse { + fn arbitrary(g: &mut G) -> Self { + Self { + connection_id: ConnectionId(i64::arbitrary(g)), + transaction_id: TransactionId(i32::arbitrary(g)), + } + } +} + + +#[cfg(test)] +impl quickcheck::Arbitrary for AnnounceResponse { + fn arbitrary(g: &mut G) -> Self { + let peers = (0..u8::arbitrary(g)).map(|_| { + ResponsePeer::arbitrary(g) + }).collect(); + + Self { + transaction_id: TransactionId(i32::arbitrary(g)), + announce_interval: AnnounceInterval(i32::arbitrary(g)), + leechers: NumberOfPeers(i32::arbitrary(g)), + seeders: NumberOfPeers(i32::arbitrary(g)), + peers, + } + } +} + + +#[cfg(test)] +impl quickcheck::Arbitrary for ScrapeResponse { + fn arbitrary(g: &mut G) -> Self { + let torrent_stats = (0..u8::arbitrary(g)).map(|_| { + TorrentScrapeStatistics::arbitrary(g) + }).collect(); + + Self { + transaction_id: TransactionId(i32::arbitrary(g)), + torrent_stats, + } + } +} \ No newline at end of file