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

Add tests to TorrentEntry #88

Merged
merged 1 commit into from
Sep 29, 2022
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
269 changes: 269 additions & 0 deletions src/tracker/torrent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,3 +98,272 @@ pub enum TorrentError {
CouldNotSendResponse,
InvalidInfoHash,
}

#[cfg(test)]
mod tests {
use std::{
net::{IpAddr, Ipv4Addr, SocketAddr},
ops::Sub,
time::Duration,
};

use aquatic_udp_protocol::{AnnounceEvent, NumberOfBytes};

use crate::{
peer::TorrentPeer,
protocol::clock::{DefaultClock, DurationSinceUnixEpoch, StoppedClock, StoppedTime, Time, WorkingClock},
torrent::TorrentEntry,
PeerId,
};

struct TorrentPeerBuilder {
peer: TorrentPeer,
}

impl TorrentPeerBuilder {
pub fn default() -> TorrentPeerBuilder {
let default_peer = TorrentPeer {
peer_id: PeerId([0u8; 20]),
peer_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080),
updated: DefaultClock::now(),
uploaded: NumberOfBytes(0),
downloaded: NumberOfBytes(0),
left: NumberOfBytes(0),
event: AnnounceEvent::Started,
};
TorrentPeerBuilder { peer: default_peer }
}

pub fn with_event_completed(mut self) -> Self {
self.peer.event = AnnounceEvent::Completed;
self
}

pub fn with_peer_address(mut self, peer_addr: SocketAddr) -> Self {
self.peer.peer_addr = peer_addr;
self
}

pub fn with_peer_id(mut self, peer_id: PeerId) -> Self {
self.peer.peer_id = peer_id;
self
}

pub fn with_number_of_bytes_left(mut self, left: i64) -> Self {
self.peer.left = NumberOfBytes(left);
self
}

pub fn updated_at(mut self, updated: DurationSinceUnixEpoch) -> Self {
self.peer.updated = updated;
self
}

pub fn into(self) -> TorrentPeer {
self.peer
}
}

/// A torrent seeder is a peer with 0 bytes left to download which
/// has not announced it has stopped
fn a_torrent_seeder() -> TorrentPeer {
TorrentPeerBuilder::default()
.with_number_of_bytes_left(0)
.with_event_completed()
.into()
}

/// A torrent leecher is a peer that is not a seeder.
/// Leecher: left > 0 OR event = Stopped
fn a_torrent_leecher() -> TorrentPeer {
TorrentPeerBuilder::default()
.with_number_of_bytes_left(1)
.with_event_completed()
.into()
}

#[test]
fn the_default_torrent_entry_should_contain_an_empty_list_of_peers() {
let torrent_entry = TorrentEntry::new();

assert_eq!(torrent_entry.get_peers(None).len(), 0);
}

#[test]
fn a_new_peer_can_be_added_to_a_torrent_entry() {
let mut torrent_entry = TorrentEntry::new();
let torrent_peer = TorrentPeerBuilder::default().into();

torrent_entry.update_peer(&torrent_peer); // Add the peer

assert_eq!(*torrent_entry.get_peers(None)[0], torrent_peer);
assert_eq!(torrent_entry.get_peers(None).len(), 1);
}

#[test]
fn a_torrent_entry_should_contain_the_list_of_peers_that_were_added_to_the_torrent() {
let mut torrent_entry = TorrentEntry::new();
let torrent_peer = TorrentPeerBuilder::default().into();

torrent_entry.update_peer(&torrent_peer); // Add the peer

assert_eq!(torrent_entry.get_peers(None), vec![&torrent_peer]);
}

#[test]
fn a_peer_can_be_updated_in_a_torrent_entry() {
let mut torrent_entry = TorrentEntry::new();
let mut torrent_peer = TorrentPeerBuilder::default().into();
torrent_entry.update_peer(&torrent_peer); // Add the peer

torrent_peer.event = AnnounceEvent::Completed; // Update the peer
torrent_entry.update_peer(&torrent_peer); // Update the peer in the torrent entry

assert_eq!(torrent_entry.get_peers(None)[0].event, AnnounceEvent::Completed);
}

#[test]
fn a_peer_should_be_removed_from_a_torrent_entry_when_the_peer_announces_it_has_stopped() {
let mut torrent_entry = TorrentEntry::new();
let mut torrent_peer = TorrentPeerBuilder::default().into();
torrent_entry.update_peer(&torrent_peer); // Add the peer

torrent_peer.event = AnnounceEvent::Stopped; // Update the peer
torrent_entry.update_peer(&torrent_peer); // Update the peer in the torrent entry

assert_eq!(torrent_entry.get_peers(None).len(), 0);
}

#[test]
fn torrent_stats_change_when_a_previously_known_peer_announces_it_has_completed_the_torrent() {
let mut torrent_entry = TorrentEntry::new();
let mut torrent_peer = TorrentPeerBuilder::default().into();

torrent_entry.update_peer(&torrent_peer); // Add the peer

torrent_peer.event = AnnounceEvent::Completed; // Update the peer
let stats_have_changed = torrent_entry.update_peer(&torrent_peer); // Update the peer in the torrent entry

assert!(stats_have_changed);
}

#[test]
fn torrent_stats_should_not_change_when_a_peer_announces_it_has_completed_the_torrent_if_it_is_the_first_announce_from_the_peer(
) {
let mut torrent_entry = TorrentEntry::new();
let torrent_peer_announcing_complete_event = TorrentPeerBuilder::default().with_event_completed().into();

// Add a peer that did not exist before in the entry
let torrent_stats_have_not_changed = !torrent_entry.update_peer(&torrent_peer_announcing_complete_event);

assert!(torrent_stats_have_not_changed);
}

#[test]
fn a_torrent_entry_could_filter_out_peers_with_a_given_socket_address() {
let mut torrent_entry = TorrentEntry::new();
let peer_socket_address = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080);
let torrent_peer = TorrentPeerBuilder::default().with_peer_address(peer_socket_address).into();
torrent_entry.update_peer(&torrent_peer); // Add peer

// Get peers excluding the one we have just added
let peers = torrent_entry.get_peers(Some(&peer_socket_address));

assert_eq!(peers.len(), 0);
}

fn peer_id_from_i32(number: i32) -> PeerId {
let peer_id = number.to_le_bytes();
PeerId([
0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, peer_id[0], peer_id[1], peer_id[2],
peer_id[3],
])
}

#[test]
fn the_tracker_should_limit_the_list_of_peers_to_74_when_clients_scrape_torrents() {
let mut torrent_entry = TorrentEntry::new();

// We add one more peer than the scrape limit
for peer_number in 1..=74 + 1 {
let torrent_peer = TorrentPeerBuilder::default()
.with_peer_id(peer_id_from_i32(peer_number))
.into();
torrent_entry.update_peer(&torrent_peer);
}

let peers = torrent_entry.get_peers(None);

assert_eq!(peers.len(), 74)
}

#[test]
fn torrent_stats_should_have_the_number_of_seeders_for_a_torrent() {
let mut torrent_entry = TorrentEntry::new();
let torrent_seeder = a_torrent_seeder();

torrent_entry.update_peer(&torrent_seeder); // Add seeder

assert_eq!(torrent_entry.get_stats().0, 1);
}

#[test]
fn torrent_stats_should_have_the_number_of_leechers_for_a_torrent() {
let mut torrent_entry = TorrentEntry::new();
let torrent_leecher = a_torrent_leecher();

torrent_entry.update_peer(&torrent_leecher); // Add leecher

assert_eq!(torrent_entry.get_stats().2, 1);
}

#[test]
fn torrent_stats_should_have_the_number_of_peers_that_having_announced_at_least_two_events_the_latest_one_is_the_completed_event(
) {
let mut torrent_entry = TorrentEntry::new();
let mut torrent_peer = TorrentPeerBuilder::default().into();
torrent_entry.update_peer(&torrent_peer); // Add the peer

// Announce "Completed" torrent download event.
torrent_peer.event = AnnounceEvent::Completed;
torrent_entry.update_peer(&torrent_peer); // Update the peer

let number_of_previously_known_peers_with_completed_torrent = torrent_entry.get_stats().1;

assert_eq!(number_of_previously_known_peers_with_completed_torrent, 1);
}

#[test]
fn torrent_stats_should_not_include_a_peer_in_the_completed_counter_if_the_peer_has_announced_only_one_event() {
let mut torrent_entry = TorrentEntry::new();
let torrent_peer_announcing_complete_event = TorrentPeerBuilder::default().with_event_completed().into();

// Announce "Completed" torrent download event.
// It's the first event announced from this peer.
torrent_entry.update_peer(&torrent_peer_announcing_complete_event); // Add the peer

let number_of_peers_with_completed_torrent = torrent_entry.get_stats().1;

assert_eq!(number_of_peers_with_completed_torrent, 0);
}

#[test]
fn a_torrent_entry_should_remove_a_peer_not_updated_after_a_timeout_in_seconds() {
let mut torrent_entry = TorrentEntry::new();

let timeout = 120u32;

let now = WorkingClock::now();
StoppedClock::local_set(&now);

let timeout_seconds_before_now = now.sub(Duration::from_secs(timeout as u64));
let inactive_peer = TorrentPeerBuilder::default()
.updated_at(timeout_seconds_before_now.sub(Duration::from_secs(1)))
.into();
torrent_entry.update_peer(&inactive_peer); // Add the peer

torrent_entry.remove_inactive_peers(timeout);

assert_eq!(torrent_entry.peers.len(), 0);
}
}