From 123b65bf406783a7bc997c76cd7796b15c8bdb14 Mon Sep 17 00:00:00 2001 From: Dirk Engling Date: Sun, 1 Nov 2026 01:51:42 +0100 Subject: Add some tests for the actual trackerlogic, something I put off for too long --- tests/t_trackerlogic.c | 243 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 243 insertions(+) create mode 100644 tests/t_trackerlogic.c (limited to 'tests/t_trackerlogic.c') diff --git a/tests/t_trackerlogic.c b/tests/t_trackerlogic.c new file mode 100644 index 0000000..d3d8755 --- /dev/null +++ b/tests/t_trackerlogic.c @@ -0,0 +1,243 @@ +/* This software was written by Dirk Engling + It is considered beerware. Prost. Skol. Cheers or whatever. + + $id$ */ + +/* System */ +#include +#include +#include +#include + +/* Opentracker */ +#include "ot_mutex.h" +#include "trackerlogic.h" +#include "test.h" + +/* The swarm itself: what an announce does to a torrent, and what a scrape + says about it afterwards. The counts are read back through the udp scrape + because that is the one reply the tracker builds as plain numbers. */ + +static struct ot_workstruct ws; + +static void hash_named(ot_hash hash, const char *name) { + memset(hash, 0, sizeof(ot_hash)); + memcpy(hash, name, strlen(name) < sizeof(ot_hash) ? strlen(name) : sizeof(ot_hash)); +} + +/* An IPv4 peer arrives as the v4 mapped form the tracker stores */ +static void peer_v4(ot_peer6 peer, uint8_t last, uint16_t port) { + ot_ip6 ip = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 10, 0, 0, 0}; + uint16_t net_port = htons(port); + + ip[15] = last; + memset(peer, 0, sizeof(ot_peer6)); + OT_SETIP(peer, ip); + OT_SETPORT(peer, &net_port); + OT_PEERFLAG(peer) = 0; +} + +static void peer_v6(ot_peer6 peer, uint8_t last, uint16_t port) { + ot_ip6 ip = {0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + uint16_t net_port = htons(port); + + ip[15] = last; + memset(peer, 0, sizeof(ot_peer6)); + OT_SETIP(peer, ip); + OT_SETPORT(peer, &net_port); + OT_PEERFLAG(peer) = 0; +} + +static void announce(ot_hash *hash, ot_peer6 peer) { + ws.hash = hash; + memcpy(ws.peer, peer, sizeof(ot_peer6)); + ws.reply = ws.outbuf; + add_peer_to_torrent_and_return_peers(FLAG_TCP, &ws, 50); +} + +/* seeders, downloads completed, leechers */ +static void scrape(ot_hash hash, uint32_t counts[3]) { + char reply[12]; + + return_udp_scrape_for_torrent(hash, reply); + counts[0] = ntohl(((uint32_t *)reply)[0]); + counts[1] = ntohl(((uint32_t *)reply)[1]); + counts[2] = ntohl(((uint32_t *)reply)[2]); +} + +static void case_an_announce_makes_a_torrent(void) { + ot_hash hash; + ot_peer6 peer; + uint32_t counts[3]; + size_t before = mutex_get_torrent_count(); + + test_case("the first announce for a hash creates its torrent"); + hash_named(hash, "first-announce"); + peer_v4(peer, 1, 6881); + announce(&hash, peer); + + CHECK_INT((int)(mutex_get_torrent_count() - before), 1); + scrape(hash, counts); + CHECK_INT((int)counts[2], 1); /* one leecher */ + CHECK_INT((int)counts[0], 0); +} + +static void case_the_same_peer_twice_is_one_peer(void) { + ot_hash hash; + ot_peer6 peer; + uint32_t counts[3]; + + test_case("announcing twice renews a peer rather than adding another"); + hash_named(hash, "renewing"); + peer_v4(peer, 2, 6881); + announce(&hash, peer); + announce(&hash, peer); + + scrape(hash, counts); + CHECK_INT((int)counts[2], 1); +} + +static void case_a_port_makes_a_different_peer(void) { + ot_hash hash; + ot_peer6 peer; + uint32_t counts[3]; + + test_case("the same address on another port is another peer"); + hash_named(hash, "two-ports"); + peer_v4(peer, 3, 6881); + announce(&hash, peer); + peer_v4(peer, 3, 6882); + announce(&hash, peer); + + scrape(hash, counts); + CHECK_INT((int)counts[2], 2); +} + +static void case_seeding_is_counted_apart(void) { + ot_hash hash; + ot_peer6 peer; + uint32_t counts[3]; + + test_case("a seeder counts as complete, a leecher as incomplete"); + hash_named(hash, "seed-and-leech"); + peer_v4(peer, 4, 6881); + OT_PEERFLAG(peer) |= PEER_FLAG_SEEDING; + announce(&hash, peer); + peer_v4(peer, 5, 6881); + announce(&hash, peer); + + scrape(hash, counts); + CHECK_INT((int)counts[0], 1); + CHECK_INT((int)counts[2], 1); +} + +static void case_completion_without_seeding_is_not_believed(void) { + ot_hash hash; + ot_peer6 peer; + uint32_t counts[3]; + + test_case("a peer that claims to have completed but is still leeching is not counted"); + hash_named(hash, "claiming"); + peer_v4(peer, 6, 6881); + OT_PEERFLAG(peer) |= PEER_FLAG_COMPLETED; + announce(&hash, peer); + + scrape(hash, counts); + CHECK_INT((int)counts[1], 0); + CHECK_INT((int)counts[2], 1); /* still a leecher */ +} + +static void case_completing_is_counted_once(void) { + ot_hash hash; + ot_peer6 peer; + uint32_t counts[3]; + + test_case("a seeder reporting completion is counted once, however often it says so"); + hash_named(hash, "completing"); + peer_v4(peer, 7, 6881); + OT_PEERFLAG(peer) |= PEER_FLAG_COMPLETED | PEER_FLAG_SEEDING; + announce(&hash, peer); + announce(&hash, peer); + announce(&hash, peer); + + scrape(hash, counts); + CHECK_INT((int)counts[1], 1); + CHECK_INT((int)counts[0], 1); +} + +static void case_a_peer_can_leave(void) { + ot_hash hash; + ot_peer6 peer; + uint32_t counts[3]; + + test_case("a peer that stops is gone from the swarm"); + hash_named(hash, "leaving"); + peer_v4(peer, 7, 6881); + announce(&hash, peer); + peer_v4(peer, 8, 6881); + announce(&hash, peer); + scrape(hash, counts); + CHECK_INT((int)counts[2], 2); + + ws.hash = &hash; + memcpy(ws.peer, peer, sizeof(ot_peer6)); + remove_peer_from_torrent(FLAG_TCP, &ws); + + scrape(hash, counts); + CHECK_INT((int)counts[2], 1); +} + +static void case_the_families_are_kept_apart(void) { + ot_hash hash; + ot_peer6 peer; + uint32_t counts[3]; + + test_case("a v4 and a v6 peer of one swarm are both counted"); + hash_named(hash, "dual-stack"); + peer_v4(peer, 9, 6881); + announce(&hash, peer); + peer_v6(peer, 9, 6881); + announce(&hash, peer); + + scrape(hash, counts); + CHECK_INT((int)counts[2], 2); +} + +static void case_a_hash_nobody_announced(void) { + ot_hash hash; + uint32_t counts[3]; + + test_case("a hash nobody has announced scrapes as nothing at all"); + hash_named(hash, "never-seen"); + scrape(hash, counts); + CHECK_INT((int)counts[0], 0); + CHECK_INT((int)counts[1], 0); + CHECK_INT((int)counts[2], 0); +} + +void test_trackerlogic(void) { + sigset_t signal_mask; + + /* accesslist_init starts a thread that waits for SIGHUP and is stopped by + being sent one, so the signal has to be blocked here as the tracker + blocks it, or stopping the tracker logic ends this process instead */ + sigemptyset(&signal_mask); + sigaddset(&signal_mask, SIGHUP); + pthread_sigmask(SIG_BLOCK, &signal_mask, NULL); + + trackerlogic_init(); + CHECK_INT(workstruct_init(&ws), 0); + + case_an_announce_makes_a_torrent(); + case_the_same_peer_twice_is_one_peer(); + case_a_port_makes_a_different_peer(); + case_seeding_is_counted_apart(); + case_completion_without_seeding_is_not_believed(); + case_completing_is_counted_once(); + case_a_peer_can_leave(); + case_the_families_are_kept_apart(); + case_a_hash_nobody_announced(); + + workstruct_deinit(&ws); + trackerlogic_deinit(); +} -- cgit v1.3