From a27adf23d997663e6bf2f40c3021749faac0b7c1 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Tue, 4 Oct 2022 18:27:18 +0100 Subject: [PATCH 1/7] test: integration tests for udp tracker --- tests/udp.rs | 293 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 293 insertions(+) create mode 100644 tests/udp.rs diff --git a/tests/udp.rs b/tests/udp.rs new file mode 100644 index 000000000..be4e42e41 --- /dev/null +++ b/tests/udp.rs @@ -0,0 +1,293 @@ +/// Integration tests for UDP tracker server +/// +/// cargo test udp_tracker_server -- --nocapture + +#[macro_use] +extern crate lazy_static; + +extern crate rand; + +mod udp_tracker_server { + + use core::panic; + use std::io::Cursor; + use std::net::IpAddr; + use std::sync::atomic::{AtomicBool, Ordering}; + use std::sync::Arc; + use std::sync::RwLock; + + use rand::{thread_rng, Rng}; + + use tokio::net::UdpSocket; + use tokio::task::JoinHandle; + + use torrust_tracker::jobs::udp_tracker; + use torrust_tracker::tracker::statistics::StatsTracker; + use torrust_tracker::tracker::tracker::TorrentTracker; + use torrust_tracker::udp::MAX_PACKET_SIZE; + use torrust_tracker::{logging, static_time, Configuration}; + + use aquatic_udp_protocol::{ + AnnounceEvent, AnnounceRequest, ConnectRequest, ConnectionId, InfoHash, NumberOfBytes, NumberOfPeers, PeerId, PeerKey, + Port, Request, Response, TransactionId, + }; + + fn tracker_configuration() -> Arc { + let mut config = Configuration::default(); + //config.log_level = Some("debug".to_owned()); // Uncomment to enable logging + config.external_ip = Some("127.0.0.1".to_owned()); + config.udp_trackers[0].bind_address = "127.0.0.1:6969".to_owned(); + Arc::new(config) + } + + fn tracker_bind_address() -> String { + tracker_configuration().udp_trackers[0].bind_address.clone() + } + + pub struct UdpServer { + pub started: AtomicBool, + pub job: Option>, + } + + impl UdpServer { + pub fn new() -> Self { + Self { + started: AtomicBool::new(false), + job: None, + } + } + + pub async fn start(&mut self, configuration: Arc) { + if !self.started.load(Ordering::Relaxed) { + // Set the time of Torrust app starting + lazy_static::initialize(&static_time::TIME_AT_APP_START); + + // Initialize stats tracker + let stats_tracker = StatsTracker::new_running_instance(); + + // Initialize Torrust tracker + let tracker = match TorrentTracker::new(configuration.clone(), Box::new(stats_tracker)) { + Ok(tracker) => Arc::new(tracker), + Err(error) => { + panic!("{}", error) + } + }; + + // Initialize logging + logging::setup_logging(&configuration); + + // Start the UDP tracker job + self.job = Some(udp_tracker::start_job(&configuration.udp_trackers[0], tracker.clone())); + + self.started.store(true, Ordering::Relaxed); + } + } + } + + lazy_static! { + static ref SERVER: RwLock = RwLock::new(UdpServer::new()); + } + + async fn start_udp_server(configuration: Arc) { + SERVER.write().unwrap().start(configuration.clone()).await; + } + + struct UdpClient { + socket: Arc, + } + + impl UdpClient { + async fn bind(local_address: &str) -> Self { + let socket = UdpSocket::bind(local_address).await.unwrap(); + Self { + socket: Arc::new(socket), + } + } + + async fn connect(&self, remote_address: &str) { + self.socket.connect(remote_address).await.unwrap() + } + + async fn send(&self, bytes: &[u8]) -> usize { + self.socket.writable().await.unwrap(); + self.socket.send(bytes).await.unwrap() + } + + async fn receive(&self, bytes: &mut [u8]) -> usize { + self.socket.readable().await.unwrap(); + self.socket.recv(bytes).await.unwrap() + } + } + + /// Creates a new UdpClient connected to a Udp server + async fn new_connected_udp_client(remote_address: &str) -> UdpClient { + let client = UdpClient::bind(&source_address(ephemeral_random_port())).await; + client.connect(remote_address).await; + client + } + + struct UdpTrackerClient { + pub udp_client: UdpClient, + } + + impl UdpTrackerClient { + async fn send(&self, request: Request) -> usize { + // Write request into a buffer + let request_buffer = vec![0u8; MAX_PACKET_SIZE]; + let mut cursor = Cursor::new(request_buffer); + + let request_data = match request.write(&mut cursor) { + Ok(_) => { + let position = cursor.position() as usize; + let inner_request_buffer = cursor.get_ref(); + // Return slice which contains written request data + &inner_request_buffer[..position] + } + Err(_) => panic!("could not write request to bytes."), + }; + + self.udp_client.send(&request_data).await + } + + async fn receive(&self) -> Response { + let mut response_buffer = [0u8; MAX_PACKET_SIZE]; + + let payload_size = self.udp_client.receive(&mut response_buffer).await; + + Response::from_bytes(&response_buffer[..payload_size], true).unwrap() + } + } + + /// Creates a new UdpTrackerClient connected to a Udp Tracker server + async fn new_connected_udp_tracker_client(remote_address: &str) -> UdpTrackerClient { + let udp_client = new_connected_udp_client(remote_address).await; + UdpTrackerClient { udp_client } + } + + fn empty_udp_request() -> [u8; MAX_PACKET_SIZE] { + [0; MAX_PACKET_SIZE] + } + + fn empty_buffer() -> [u8; MAX_PACKET_SIZE] { + [0; MAX_PACKET_SIZE] + } + + /// Generates a random ephemeral port for a client source address + fn ephemeral_random_port() -> u16 { + // todo: this may produce random test failures because two test can try to bind the same port. + // We could either use the same client for all tests (slower) or + // create a pool of available ports (with read/write lock) + let mut rng = thread_rng(); + rng.gen_range(49152..65535) + } + + /// Generates the source address for the UDP client + fn source_address(port: u16) -> String { + format!("127.0.0.1:{}", port) + } + + fn is_error_response(response: &Response, error_message: &str) -> bool { + match response { + Response::Error(error_response) => return error_response.message.starts_with(error_message), + _ => return false, + }; + } + + fn is_connect_response(response: &Response, transaction_id: TransactionId) -> bool { + match response { + Response::Connect(connect_response) => return connect_response.transaction_id == transaction_id, + _ => return false, + }; + } + + fn is_ipv4_announce_response(response: &Response) -> bool { + match response { + Response::AnnounceIpv4(_) => return true, + _ => return false, + }; + } + + // #[tokio::test] + // async fn should_return_a_bad_request_response_when_the_client_sends_an_empty_request() { + // start_udp_server(tracker_configuration().clone()).await; + + // let client = new_connected_udp_client(&tracker_bind_address()).await; + + // client.send(&empty_udp_request()).await; + + // let mut buffer = empty_buffer(); + // client.receive(&mut buffer).await; + // let response = Response::from_bytes(&buffer, true).unwrap(); + + // assert!(is_error_response(&response, "bad request")); + // } + + // #[tokio::test] + // async fn should_return_a_connect_response_when_the_client_sends_a_connection_request() { + // start_udp_server(tracker_configuration().clone()).await; + + // let client = new_connected_udp_tracker_client(&tracker_bind_address()).await; + + // let connect_request = ConnectRequest { + // transaction_id: TransactionId(123), + // }; + + // client.send(connect_request.into()).await; + + // let response = client.receive().await; + + // assert!(is_connect_response(&response, TransactionId(123))); + // } + + #[tokio::test] + async fn should_return_an_announce_response_when_the_client_sends_an_announce_request() { + start_udp_server(tracker_configuration().clone()).await; + + let client = new_connected_udp_tracker_client(&tracker_bind_address()).await; + + // todo: extract client.connect() -> ConnectionId + + // Get connection id before sending the announce request + + let connect_request = ConnectRequest { + transaction_id: TransactionId(123), + }; + + client.send(connect_request.into()).await; + + let response = client.receive().await; + + let connection_id = match response { + Response::Connect(connect_response) => connect_response.connection_id, + _ => panic!("error connecting to udp server {:?}", response), + }; + + // Send announce request + + let client_ip = match client.udp_client.socket.local_addr().unwrap().ip() { + IpAddr::V4(ip4) => ip4, + _ => panic!("error: IPV6 addresses cannot be used for the client ip in the announce request. Try to use IPV4."), + }; + + let announce_request = AnnounceRequest { + connection_id: ConnectionId(8724592475294857), + transaction_id: TransactionId(123i32), + info_hash: InfoHash([0u8; 20]), + peer_id: PeerId([255u8; 20]), + bytes_downloaded: NumberOfBytes(0i64), + bytes_uploaded: NumberOfBytes(0i64), + bytes_left: NumberOfBytes(0i64), + event: AnnounceEvent::Started, + ip_address: Some(client_ip), + key: PeerKey(0u32), + peers_wanted: NumberOfPeers(1i32), + port: Port(client.udp_client.socket.local_addr().unwrap().port()), + }; + + client.send(announce_request.into()).await; + + let response = client.receive().await; + + assert!(is_ipv4_announce_response(&response)); + } +} From 946e80a7a20e9cb31eb15eb04e2b89912ba7e6d6 Mon Sep 17 00:00:00 2001 From: Mick van Dijke Date: Fri, 7 Oct 2022 16:31:11 +0200 Subject: [PATCH 2/7] refactor: run tests with own udp tracker (#98) * refactor: run tests with own udp tracker * fixup! refactor: run tests with own udp tracker --- src/logging.rs | 4 +++ tests/udp.rs | 96 +++++++++++++++++++++++--------------------------- 2 files changed, 49 insertions(+), 51 deletions(-) diff --git a/src/logging.rs b/src/logging.rs index 209c9f848..7552a5459 100644 --- a/src/logging.rs +++ b/src/logging.rs @@ -18,6 +18,10 @@ pub fn setup_logging(cfg: &Configuration) { }, }; + if log_level == log::LevelFilter::Off { + return; + } + if let Err(_err) = fern::Dispatch::new() .format(|out, message, record| { out.finish(format_args!( diff --git a/tests/udp.rs b/tests/udp.rs index be4e42e41..ecfd879ce 100644 --- a/tests/udp.rs +++ b/tests/udp.rs @@ -1,20 +1,14 @@ /// Integration tests for UDP tracker server /// /// cargo test udp_tracker_server -- --nocapture - -#[macro_use] -extern crate lazy_static; - extern crate rand; mod udp_tracker_server { - use core::panic; use std::io::Cursor; - use std::net::IpAddr; + use std::net::Ipv4Addr; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; - use std::sync::RwLock; use rand::{thread_rng, Rng}; @@ -34,19 +28,15 @@ mod udp_tracker_server { fn tracker_configuration() -> Arc { let mut config = Configuration::default(); - //config.log_level = Some("debug".to_owned()); // Uncomment to enable logging - config.external_ip = Some("127.0.0.1".to_owned()); - config.udp_trackers[0].bind_address = "127.0.0.1:6969".to_owned(); + config.log_level = Some("off".to_owned()); // "off" is necessary when running multiple trackers + config.udp_trackers[0].bind_address = format!("127.0.0.1:{}", ephemeral_random_port()); Arc::new(config) } - fn tracker_bind_address() -> String { - tracker_configuration().udp_trackers[0].bind_address.clone() - } - pub struct UdpServer { pub started: AtomicBool, pub job: Option>, + pub bind_address: Option, } impl UdpServer { @@ -54,6 +44,7 @@ mod udp_tracker_server { Self { started: AtomicBool::new(false), job: None, + bind_address: None, } } @@ -76,20 +67,22 @@ mod udp_tracker_server { // Initialize logging logging::setup_logging(&configuration); + let udp_tracker_config = &configuration.udp_trackers[0]; + // Start the UDP tracker job - self.job = Some(udp_tracker::start_job(&configuration.udp_trackers[0], tracker.clone())); + self.job = Some(udp_tracker::start_job(&udp_tracker_config, tracker.clone())); + + self.bind_address = Some(udp_tracker_config.bind_address.clone()); self.started.store(true, Ordering::Relaxed); } } } - lazy_static! { - static ref SERVER: RwLock = RwLock::new(UdpServer::new()); - } - - async fn start_udp_server(configuration: Arc) { - SERVER.write().unwrap().start(configuration.clone()).await; + async fn new_running_udp_server(configuration: Arc) -> UdpServer { + let mut udp_server = UdpServer::new(); + udp_server.start(configuration).await; + udp_server } struct UdpClient { @@ -207,43 +200,49 @@ mod udp_tracker_server { }; } - // #[tokio::test] - // async fn should_return_a_bad_request_response_when_the_client_sends_an_empty_request() { - // start_udp_server(tracker_configuration().clone()).await; + #[tokio::test] + async fn should_return_a_bad_request_response_when_the_client_sends_an_empty_request() { + let configuration = tracker_configuration(); - // let client = new_connected_udp_client(&tracker_bind_address()).await; + let udp_server = new_running_udp_server(configuration).await; - // client.send(&empty_udp_request()).await; + let client = new_connected_udp_client(&udp_server.bind_address.unwrap()).await; - // let mut buffer = empty_buffer(); - // client.receive(&mut buffer).await; - // let response = Response::from_bytes(&buffer, true).unwrap(); + client.send(&empty_udp_request()).await; - // assert!(is_error_response(&response, "bad request")); - // } + let mut buffer = empty_buffer(); + client.receive(&mut buffer).await; + let response = Response::from_bytes(&buffer, true).unwrap(); - // #[tokio::test] - // async fn should_return_a_connect_response_when_the_client_sends_a_connection_request() { - // start_udp_server(tracker_configuration().clone()).await; + assert!(is_error_response(&response, "bad request")); + } - // let client = new_connected_udp_tracker_client(&tracker_bind_address()).await; + #[tokio::test] + async fn should_return_a_connect_response_when_the_client_sends_a_connection_request() { + let configuration = tracker_configuration(); - // let connect_request = ConnectRequest { - // transaction_id: TransactionId(123), - // }; + let udp_server = new_running_udp_server(configuration).await; - // client.send(connect_request.into()).await; + let client = new_connected_udp_tracker_client(&udp_server.bind_address.unwrap()).await; - // let response = client.receive().await; + let connect_request = ConnectRequest { + transaction_id: TransactionId(123), + }; - // assert!(is_connect_response(&response, TransactionId(123))); - // } + client.send(connect_request.into()).await; + + let response = client.receive().await; + + assert!(is_connect_response(&response, TransactionId(123))); + } #[tokio::test] async fn should_return_an_announce_response_when_the_client_sends_an_announce_request() { - start_udp_server(tracker_configuration().clone()).await; + let configuration = tracker_configuration(); + + let udp_server = new_running_udp_server(configuration).await; - let client = new_connected_udp_tracker_client(&tracker_bind_address()).await; + let client = new_connected_udp_tracker_client(&udp_server.bind_address.unwrap()).await; // todo: extract client.connect() -> ConnectionId @@ -264,13 +263,8 @@ mod udp_tracker_server { // Send announce request - let client_ip = match client.udp_client.socket.local_addr().unwrap().ip() { - IpAddr::V4(ip4) => ip4, - _ => panic!("error: IPV6 addresses cannot be used for the client ip in the announce request. Try to use IPV4."), - }; - let announce_request = AnnounceRequest { - connection_id: ConnectionId(8724592475294857), + connection_id: ConnectionId(connection_id.0), transaction_id: TransactionId(123i32), info_hash: InfoHash([0u8; 20]), peer_id: PeerId([255u8; 20]), @@ -278,7 +272,7 @@ mod udp_tracker_server { bytes_uploaded: NumberOfBytes(0i64), bytes_left: NumberOfBytes(0i64), event: AnnounceEvent::Started, - ip_address: Some(client_ip), + ip_address: Some(Ipv4Addr::new(0, 0, 0, 0)), key: PeerKey(0u32), peers_wanted: NumberOfPeers(1i32), port: Port(client.udp_client.socket.local_addr().unwrap().port()), From f5aee0325c0dc10e902287dc74056b7f4e870940 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 19 Oct 2022 11:44:46 +0100 Subject: [PATCH 3/7] fix: re-format with rust nightly --- tests/udp.rs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/tests/udp.rs b/tests/udp.rs index ecfd879ce..49f8e7c16 100644 --- a/tests/udp.rs +++ b/tests/udp.rs @@ -10,22 +10,19 @@ mod udp_tracker_server { use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; + use aquatic_udp_protocol::{ + AnnounceEvent, AnnounceRequest, ConnectRequest, ConnectionId, InfoHash, NumberOfBytes, NumberOfPeers, PeerId, PeerKey, + Port, Request, Response, TransactionId, + }; use rand::{thread_rng, Rng}; - use tokio::net::UdpSocket; use tokio::task::JoinHandle; - use torrust_tracker::jobs::udp_tracker; use torrust_tracker::tracker::statistics::StatsTracker; use torrust_tracker::tracker::tracker::TorrentTracker; use torrust_tracker::udp::MAX_PACKET_SIZE; use torrust_tracker::{logging, static_time, Configuration}; - use aquatic_udp_protocol::{ - AnnounceEvent, AnnounceRequest, ConnectRequest, ConnectionId, InfoHash, NumberOfBytes, NumberOfPeers, PeerId, PeerKey, - Port, Request, Response, TransactionId, - }; - fn tracker_configuration() -> Arc { let mut config = Configuration::default(); config.log_level = Some("off".to_owned()); // "off" is necessary when running multiple trackers From 1f258c1cd01c75d003d0ab130cfa58d31fa4f1b7 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 19 Oct 2022 12:17:28 +0100 Subject: [PATCH 4/7] test: integration test for udp scrape request --- tests/udp.rs | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 52 insertions(+), 2 deletions(-) diff --git a/tests/udp.rs b/tests/udp.rs index 49f8e7c16..31f631fd6 100644 --- a/tests/udp.rs +++ b/tests/udp.rs @@ -12,7 +12,7 @@ mod udp_tracker_server { use aquatic_udp_protocol::{ AnnounceEvent, AnnounceRequest, ConnectRequest, ConnectionId, InfoHash, NumberOfBytes, NumberOfPeers, PeerId, PeerKey, - Port, Request, Response, TransactionId, + Port, Request, Response, ScrapeRequest, TransactionId, }; use rand::{thread_rng, Rng}; use tokio::net::UdpSocket; @@ -164,7 +164,7 @@ mod udp_tracker_server { /// Generates a random ephemeral port for a client source address fn ephemeral_random_port() -> u16 { - // todo: this may produce random test failures because two test can try to bind the same port. + // todo: this may produce random test failures because two tests can try to bind the same port. // We could either use the same client for all tests (slower) or // create a pool of available ports (with read/write lock) let mut rng = thread_rng(); @@ -197,6 +197,13 @@ mod udp_tracker_server { }; } + fn is_scrape_response(response: &Response) -> bool { + match response { + Response::Scrape(_) => return true, + _ => return false, + }; + } + #[tokio::test] async fn should_return_a_bad_request_response_when_the_client_sends_an_empty_request() { let configuration = tracker_configuration(); @@ -281,4 +288,47 @@ mod udp_tracker_server { assert!(is_ipv4_announce_response(&response)); } + + #[tokio::test] + async fn should_return_a_scrape_response_when_the_client_sends_a_scrape_request() { + let configuration = tracker_configuration(); + + let udp_server = new_running_udp_server(configuration).await; + + let client = new_connected_udp_tracker_client(&udp_server.bind_address.unwrap()).await; + + // todo: extract client.connect() -> ConnectionId + + // Get connection id before sending the announce request + + let connect_request = ConnectRequest { + transaction_id: TransactionId(123i32), + }; + + client.send(connect_request.into()).await; + + let response = client.receive().await; + + let connection_id = match response { + Response::Connect(connect_response) => connect_response.connection_id, + _ => panic!("error connecting to udp server {:?}", response), + }; + + // Send scrape request + + // Full scrapes are not allowed so it will return "bad request" error with empty vector + let info_hashes = vec![InfoHash([0u8; 20])]; + + let scrape_request = ScrapeRequest { + connection_id: ConnectionId(connection_id.0), + transaction_id: TransactionId(123i32), + info_hashes, + }; + + client.send(scrape_request.into()).await; + + let response = client.receive().await; + + assert!(is_scrape_response(&response)); + } } From 508803a73aaa651ee50afe99551fd18fcc15f141 Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Wed, 19 Oct 2022 12:36:35 +0100 Subject: [PATCH 5/7] refactor: extract function --- tests/udp.rs | 47 +++++++++++++++-------------------------------- 1 file changed, 15 insertions(+), 32 deletions(-) diff --git a/tests/udp.rs b/tests/udp.rs index 31f631fd6..83fec1fb3 100644 --- a/tests/udp.rs +++ b/tests/udp.rs @@ -240,6 +240,19 @@ mod udp_tracker_server { assert!(is_connect_response(&response, TransactionId(123))); } + async fn send_connection_request(transaction_id: TransactionId, client: &UdpTrackerClient) -> ConnectionId { + let connect_request = ConnectRequest { transaction_id }; + + client.send(connect_request.into()).await; + + let response = client.receive().await; + + match response { + Response::Connect(connect_response) => connect_response.connection_id, + _ => panic!("error connecting to udp server {:?}", response), + } + } + #[tokio::test] async fn should_return_an_announce_response_when_the_client_sends_an_announce_request() { let configuration = tracker_configuration(); @@ -248,22 +261,7 @@ mod udp_tracker_server { let client = new_connected_udp_tracker_client(&udp_server.bind_address.unwrap()).await; - // todo: extract client.connect() -> ConnectionId - - // Get connection id before sending the announce request - - let connect_request = ConnectRequest { - transaction_id: TransactionId(123), - }; - - client.send(connect_request.into()).await; - - let response = client.receive().await; - - let connection_id = match response { - Response::Connect(connect_response) => connect_response.connection_id, - _ => panic!("error connecting to udp server {:?}", response), - }; + let connection_id = send_connection_request(TransactionId(123), &client).await; // Send announce request @@ -297,22 +295,7 @@ mod udp_tracker_server { let client = new_connected_udp_tracker_client(&udp_server.bind_address.unwrap()).await; - // todo: extract client.connect() -> ConnectionId - - // Get connection id before sending the announce request - - let connect_request = ConnectRequest { - transaction_id: TransactionId(123i32), - }; - - client.send(connect_request.into()).await; - - let response = client.receive().await; - - let connection_id = match response { - Response::Connect(connect_response) => connect_response.connection_id, - _ => panic!("error connecting to udp server {:?}", response), - }; + let connection_id = send_connection_request(TransactionId(123), &client).await; // Send scrape request From d2c69fa5f531520192de03e66335fa842e06ee7a Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Fri, 21 Oct 2022 09:24:58 +0100 Subject: [PATCH 6/7] fix: test tear up --- tests/udp.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/udp.rs b/tests/udp.rs index 83fec1fb3..00bb42366 100644 --- a/tests/udp.rs +++ b/tests/udp.rs @@ -51,7 +51,7 @@ mod udp_tracker_server { lazy_static::initialize(&static_time::TIME_AT_APP_START); // Initialize stats tracker - let stats_tracker = StatsTracker::new_running_instance(); + let stats_tracker = StatsTracker::new_active_instance(); // Initialize Torrust tracker let tracker = match TorrentTracker::new(configuration.clone(), Box::new(stats_tracker)) { From 5dcea43da6fe6e7590ff69f4ac04200d2394acde Mon Sep 17 00:00:00 2001 From: Jose Celano Date: Fri, 21 Oct 2022 13:44:22 +0100 Subject: [PATCH 7/7] fix: initialize loggin once `setup_logging` cannot be called twice becuase it panics. --- src/logging.rs | 42 ++++++++++++++++++++++++------------------ tests/udp.rs | 2 +- 2 files changed, 25 insertions(+), 19 deletions(-) diff --git a/src/logging.rs b/src/logging.rs index 7552a5459..5d0efa8a4 100644 --- a/src/logging.rs +++ b/src/logging.rs @@ -1,27 +1,32 @@ -use log::info; +use std::str::FromStr; +use std::sync::Once; + +use log::{info, LevelFilter}; use crate::Configuration; +static INIT: Once = Once::new(); + pub fn setup_logging(cfg: &Configuration) { - let log_level = match &cfg.log_level { - None => log::LevelFilter::Info, - Some(level) => match level.as_str() { - "off" => log::LevelFilter::Off, - "trace" => log::LevelFilter::Trace, - "debug" => log::LevelFilter::Debug, - "info" => log::LevelFilter::Info, - "warn" => log::LevelFilter::Warn, - "error" => log::LevelFilter::Error, - _ => { - panic!("Unknown log level encountered: '{}'", level.as_str()); - } - }, - }; - - if log_level == log::LevelFilter::Off { + let level = config_level_or_default(&cfg.log_level); + + if level == log::LevelFilter::Off { return; } + INIT.call_once(|| { + stdout_config(level); + }); +} + +fn config_level_or_default(log_level: &Option) -> LevelFilter { + match log_level { + None => log::LevelFilter::Info, + Some(level) => LevelFilter::from_str(level).unwrap(), + } +} + +fn stdout_config(level: LevelFilter) { if let Err(_err) = fern::Dispatch::new() .format(|out, message, record| { out.finish(format_args!( @@ -32,11 +37,12 @@ pub fn setup_logging(cfg: &Configuration) { message )) }) - .level(log_level) + .level(level) .chain(std::io::stdout()) .apply() { panic!("Failed to initialize logging.") } + info!("logging initialized."); } diff --git a/tests/udp.rs b/tests/udp.rs index 00bb42366..b391b922f 100644 --- a/tests/udp.rs +++ b/tests/udp.rs @@ -25,7 +25,7 @@ mod udp_tracker_server { fn tracker_configuration() -> Arc { let mut config = Configuration::default(); - config.log_level = Some("off".to_owned()); // "off" is necessary when running multiple trackers + config.log_level = Some("off".to_owned()); config.udp_trackers[0].bind_address = format!("127.0.0.1:{}", ephemeral_random_port()); Arc::new(config) }