Skip to content

Commit 4043962

Browse files
committed
Merge #1242: Overhaul core Tracker: add tests for torrent mod
21c865d test: [#1240] add tests for bittorrent_tracker_core::torrent::services (Jose Celano) ef87954 refactor: minor changes (Jose Celano) 0d0c601 test: [#1240] add tests for TorrentsManager (Jose Celano) 6a15e06 test: [#1240] add tests for DatabasePersistentTorrentRepository (Jose Celano) f048157 test: [#1240] add tests for InMemoryTorrentRepository (Jose Celano) 4198bc6 refactor: [#1240] reorganize InMemoryTorrentRepository tests (Jose Celano) ed13294 docs: add testing section to README (Jose Celano) Pull request description: Overhaul core Tracker: add tests for `torrent` mod ### Sub-tasks - [x] `repository` mod. - [x] `in_memory` mod. - [x] `persisted` mod. - [x] `manager` mod. - [x] `services` mod. ACKs for top commit: josecelano: ACK 21c865d Tree-SHA512: 17c37cd853dac1934b65fc8afd1592e3e2adccecde532c4dd477c809037d911fa195ddb79d18b5b33f9aef8a499250acdef92b1ad1c232b5aa5b31db9525ee4c
2 parents 982470e + 21c865d commit 4043962

File tree

9 files changed

+964
-166
lines changed

9 files changed

+964
-166
lines changed

packages/primitives/src/lib.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,5 @@ use bittorrent_primitives::info_hash::InfoHash;
1818
/// Duration since the Unix Epoch.
1919
pub type DurationSinceUnixEpoch = Duration;
2020

21-
pub type PersistentTorrents = BTreeMap<InfoHash, u32>;
21+
pub type PersistentTorrent = u32;
22+
pub type PersistentTorrents = BTreeMap<InfoHash, PersistentTorrent>;

packages/tracker-core/README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,22 @@ You usually don’t need to use this library directly. Instead, you should use t
1010

1111
[Crate documentation](https://docs.rs/bittorrent-tracker-core).
1212

13+
## Testing
14+
15+
Show coverage report:
16+
17+
```console
18+
cargo +stable llvm-cov
19+
```
20+
21+
Export coverage report to `lcov` format:
22+
23+
```console
24+
cargo +stable llvm-cov --lcov --output-path=./.coverage/lcov.info
25+
```
26+
27+
If you use Visual Studio Code, you can use the [Coverage Gutters](https://marketplace.visualstudio.com/items?itemName=semasquare.vscode-coverage-gutters) extension to view the coverage lines.
28+
1329
## License
1430

1531
The project is licensed under the terms of the [GNU AFFERO GENERAL PUBLIC LICENSE](./LICENSE).

packages/tracker-core/src/announce_handler.rs

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -82,17 +82,11 @@ impl AnnounceHandler {
8282
/// needed for a `announce` request response.
8383
#[must_use]
8484
fn upsert_peer_and_get_stats(&self, info_hash: &InfoHash, peer: &peer::Peer) -> SwarmMetadata {
85-
let swarm_metadata_before = match self.in_memory_torrent_repository.get_opt_swarm_metadata(info_hash) {
86-
Some(swarm_metadata) => swarm_metadata,
87-
None => SwarmMetadata::zeroed(),
88-
};
85+
let swarm_metadata_before = self.in_memory_torrent_repository.get_swarm_metadata(info_hash);
8986

9087
self.in_memory_torrent_repository.upsert_peer(info_hash, peer);
9188

92-
let swarm_metadata_after = match self.in_memory_torrent_repository.get_opt_swarm_metadata(info_hash) {
93-
Some(swarm_metadata) => swarm_metadata,
94-
None => SwarmMetadata::zeroed(),
95-
};
89+
let swarm_metadata_after = self.in_memory_torrent_repository.get_swarm_metadata(info_hash);
9690

9791
if swarm_metadata_before != swarm_metadata_after {
9892
self.persist_stats(info_hash, &swarm_metadata_after);

packages/tracker-core/src/core_tests.rs

Lines changed: 80 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,74 @@ pub fn sample_info_hash() -> InfoHash {
3030
.expect("String should be a valid info hash")
3131
}
3232

33+
/// # Panics
34+
///
35+
/// Will panic if the string representation of the info hash is not a valid info hash.
36+
#[must_use]
37+
pub fn sample_info_hash_one() -> InfoHash {
38+
"3b245504cf5f11bbdbe1201cea6a6bf45aee1bc0" // DevSkim: ignore DS173237
39+
.parse::<InfoHash>()
40+
.expect("String should be a valid info hash")
41+
}
42+
43+
/// # Panics
44+
///
45+
/// Will panic if the string representation of the info hash is not a valid info hash.
46+
#[must_use]
47+
pub fn sample_info_hash_two() -> InfoHash {
48+
"99c82bb73505a3c0b453f9fa0e881d6e5a32a0c1" // DevSkim: ignore DS173237
49+
.parse::<InfoHash>()
50+
.expect("String should be a valid info hash")
51+
}
52+
53+
/// # Panics
54+
///
55+
/// Will panic if the string representation of the info hash is not a valid info hash.
56+
#[must_use]
57+
pub fn sample_info_hash_alphabetically_ordered_after_sample_info_hash_one() -> InfoHash {
58+
"99c82bb73505a3c0b453f9fa0e881d6e5a32a0c1" // DevSkim: ignore DS173237
59+
.parse::<InfoHash>()
60+
.expect("String should be a valid info hash")
61+
}
62+
3363
/// Sample peer whose state is not relevant for the tests.
3464
#[must_use]
3565
pub fn sample_peer() -> Peer {
36-
complete_peer()
66+
Peer {
67+
peer_id: PeerId(*b"-qB00000000000000000"),
68+
peer_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(126, 0, 0, 1)), 8080),
69+
updated: DurationSinceUnixEpoch::new(1_669_397_478_934, 0),
70+
uploaded: NumberOfBytes::new(0),
71+
downloaded: NumberOfBytes::new(0),
72+
left: NumberOfBytes::new(0), // No bytes left to download
73+
event: AnnounceEvent::Completed,
74+
}
75+
}
76+
77+
#[must_use]
78+
pub fn sample_peer_one() -> Peer {
79+
Peer {
80+
peer_id: PeerId(*b"-qB00000000000000001"),
81+
peer_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(126, 0, 0, 1)), 8081),
82+
updated: DurationSinceUnixEpoch::new(1_669_397_478_934, 0),
83+
uploaded: NumberOfBytes::new(0),
84+
downloaded: NumberOfBytes::new(0),
85+
left: NumberOfBytes::new(0), // No bytes left to download
86+
event: AnnounceEvent::Completed,
87+
}
88+
}
89+
90+
#[must_use]
91+
pub fn sample_peer_two() -> Peer {
92+
Peer {
93+
peer_id: PeerId(*b"-qB00000000000000002"),
94+
peer_addr: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(126, 0, 0, 2)), 8082),
95+
updated: DurationSinceUnixEpoch::new(1_669_397_478_934, 0),
96+
uploaded: NumberOfBytes::new(0),
97+
downloaded: NumberOfBytes::new(0),
98+
left: NumberOfBytes::new(0), // No bytes left to download
99+
event: AnnounceEvent::Completed,
100+
}
37101
}
38102

39103
#[must_use]
@@ -110,7 +174,21 @@ pub fn initialize_handlers(config: &Configuration) -> (Arc<AnnounceHandler>, Arc
110174

111175
/// # Panics
112176
///
113-
/// Will panic if the temporary file path is not a valid UFT string.
177+
/// Will panic if the temporary database file path is not a valid UFT string.
178+
#[cfg(test)]
179+
#[must_use]
180+
pub fn ephemeral_configuration() -> Core {
181+
let mut config = Core::default();
182+
183+
let temp_file = ephemeral_sqlite_database();
184+
temp_file.to_str().unwrap().clone_into(&mut config.database.path);
185+
186+
config
187+
}
188+
189+
/// # Panics
190+
///
191+
/// Will panic if the temporary database file path is not a valid UFT string.
114192
#[cfg(test)]
115193
#[must_use]
116194
pub fn ephemeral_configuration_for_listed_tracker() -> Core {

packages/tracker-core/src/torrent/manager.rs

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,3 +61,151 @@ impl TorrentsManager {
6161
}
6262
}
6363
}
64+
65+
#[cfg(test)]
66+
mod tests {
67+
68+
use std::sync::Arc;
69+
70+
use torrust_tracker_configuration::Core;
71+
use torrust_tracker_torrent_repository::entry::EntrySync;
72+
73+
use super::{DatabasePersistentTorrentRepository, TorrentsManager};
74+
use crate::core_tests::{ephemeral_configuration, sample_info_hash};
75+
use crate::databases::setup::initialize_database;
76+
use crate::torrent::repository::in_memory::InMemoryTorrentRepository;
77+
78+
struct TorrentsManagerDeps {
79+
config: Arc<Core>,
80+
in_memory_torrent_repository: Arc<InMemoryTorrentRepository>,
81+
database_persistent_torrent_repository: Arc<DatabasePersistentTorrentRepository>,
82+
}
83+
84+
fn initialize_torrents_manager() -> (Arc<TorrentsManager>, Arc<TorrentsManagerDeps>) {
85+
let config = ephemeral_configuration();
86+
initialize_torrents_manager_with(config.clone())
87+
}
88+
89+
fn initialize_torrents_manager_with(config: Core) -> (Arc<TorrentsManager>, Arc<TorrentsManagerDeps>) {
90+
let in_memory_torrent_repository = Arc::new(InMemoryTorrentRepository::default());
91+
let database = initialize_database(&config);
92+
let database_persistent_torrent_repository = Arc::new(DatabasePersistentTorrentRepository::new(&database));
93+
94+
let torrents_manager = Arc::new(TorrentsManager::new(
95+
&config,
96+
&in_memory_torrent_repository,
97+
&database_persistent_torrent_repository,
98+
));
99+
100+
(
101+
torrents_manager,
102+
Arc::new(TorrentsManagerDeps {
103+
config: Arc::new(config),
104+
in_memory_torrent_repository,
105+
database_persistent_torrent_repository,
106+
}),
107+
)
108+
}
109+
110+
#[test]
111+
fn it_should_load_the_numbers_of_downloads_for_all_torrents_from_the_database() {
112+
let (torrents_manager, services) = initialize_torrents_manager();
113+
114+
let infohash = sample_info_hash();
115+
116+
services.database_persistent_torrent_repository.save(&infohash, 1).unwrap();
117+
118+
torrents_manager.load_torrents_from_database().unwrap();
119+
120+
assert_eq!(
121+
services
122+
.in_memory_torrent_repository
123+
.get(&infohash)
124+
.unwrap()
125+
.get_swarm_metadata()
126+
.downloaded,
127+
1
128+
);
129+
}
130+
131+
mod cleaning_torrents {
132+
use std::ops::Add;
133+
use std::sync::Arc;
134+
use std::time::Duration;
135+
136+
use bittorrent_primitives::info_hash::InfoHash;
137+
use torrust_tracker_clock::clock::stopped::Stopped;
138+
use torrust_tracker_clock::clock::{self};
139+
use torrust_tracker_primitives::DurationSinceUnixEpoch;
140+
141+
use crate::core_tests::{ephemeral_configuration, sample_info_hash, sample_peer};
142+
use crate::torrent::manager::tests::{initialize_torrents_manager, initialize_torrents_manager_with};
143+
use crate::torrent::repository::in_memory::InMemoryTorrentRepository;
144+
145+
#[test]
146+
fn it_should_remove_peers_that_have_not_been_updated_after_a_cutoff_time() {
147+
let (torrents_manager, services) = initialize_torrents_manager();
148+
149+
let infohash = sample_info_hash();
150+
151+
clock::Stopped::local_set(&Duration::from_secs(0));
152+
153+
// Add a peer to the torrent
154+
let mut peer = sample_peer();
155+
peer.updated = DurationSinceUnixEpoch::new(0, 0);
156+
let () = services.in_memory_torrent_repository.upsert_peer(&infohash, &peer);
157+
158+
// Simulate the time has passed 1 second more than the max peer timeout.
159+
clock::Stopped::local_add(&Duration::from_secs(
160+
(services.config.tracker_policy.max_peer_timeout + 1).into(),
161+
))
162+
.unwrap();
163+
164+
torrents_manager.cleanup_torrents();
165+
166+
assert!(services.in_memory_torrent_repository.get(&infohash).is_none());
167+
}
168+
169+
fn add_a_peerless_torrent(infohash: &InfoHash, in_memory_torrent_repository: &Arc<InMemoryTorrentRepository>) {
170+
// Add a peer to the torrent
171+
let mut peer = sample_peer();
172+
peer.updated = DurationSinceUnixEpoch::new(0, 0);
173+
let () = in_memory_torrent_repository.upsert_peer(infohash, &peer);
174+
175+
// Remove the peer. The torrent is now peerless.
176+
in_memory_torrent_repository.remove_inactive_peers(peer.updated.add(Duration::from_secs(1)));
177+
}
178+
179+
#[test]
180+
fn it_should_remove_torrents_that_have_no_peers_when_it_is_configured_to_do_so() {
181+
let mut config = ephemeral_configuration();
182+
config.tracker_policy.remove_peerless_torrents = true;
183+
184+
let (torrents_manager, services) = initialize_torrents_manager_with(config);
185+
186+
let infohash = sample_info_hash();
187+
188+
add_a_peerless_torrent(&infohash, &services.in_memory_torrent_repository);
189+
190+
torrents_manager.cleanup_torrents();
191+
192+
assert!(services.in_memory_torrent_repository.get(&infohash).is_none());
193+
}
194+
195+
#[test]
196+
fn it_should_retain_peerless_torrents_when_it_is_configured_to_do_so() {
197+
let mut config = ephemeral_configuration();
198+
config.tracker_policy.remove_peerless_torrents = false;
199+
200+
let (torrents_manager, services) = initialize_torrents_manager_with(config);
201+
202+
let infohash = sample_info_hash();
203+
204+
add_a_peerless_torrent(&infohash, &services.in_memory_torrent_repository);
205+
206+
torrents_manager.cleanup_torrents();
207+
208+
assert!(services.in_memory_torrent_repository.get(&infohash).is_some());
209+
}
210+
}
211+
}

packages/tracker-core/src/torrent/mod.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ pub mod manager;
2929
pub mod repository;
3030
pub mod services;
3131

32-
use torrust_tracker_torrent_repository::TorrentsSkipMapMutexStd;
32+
use torrust_tracker_torrent_repository::{EntryMutexStd, TorrentsSkipMapMutexStd};
3333

34-
pub type Torrents = TorrentsSkipMapMutexStd; // Currently Used
34+
// Currently used types from the torrent repository crate.
35+
pub type Torrents = TorrentsSkipMapMutexStd;
36+
pub type TorrentEntry = EntryMutexStd;

0 commit comments

Comments
 (0)