-
Notifications
You must be signed in to change notification settings - Fork 44
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 udp::handlers
#82
Conversation
I'm trying to add a second test for the same function: pub async fn handle_connect(
remote_addr: SocketAddr,
request: &ConnectRequest,
tracker: Arc<TorrentTracker>,
) -> Result<Response, ServerError> {
let connection_id = get_connection_id(&remote_addr);
let response = Response::from(ConnectResponse {
transaction_id: request.transaction_id,
connection_id,
});
// send stats event
match remote_addr {
SocketAddr::V4(_) => {
tracker.send_stats_event(TrackerStatisticsEvent::Udp4Connect).await;
}
SocketAddr::V6(_) => {
tracker.send_stats_event(TrackerStatisticsEvent::Udp6Connect).await;
}
}
Ok(response)
} I want to check that I'm trying to use Mockall, but It seems you can not mock an async method even if you move it into a separate trait. |
bc5d6c0
to
a63b243
Compare
a63b243
to
beb2570
Compare
hi @WarmBeer @da2ce7 , I've tried two things:
In the first case, as I mentioned, you cannot mock an async method. In the second case, I do not know how to inject a mutable reference in a thread-safe way. On the other hand, I was wondering why the method is async. The reason is that the underlying method is also async: impl TorrentTracker {
pub async fn get_stats(&self) -> RwLockReadGuard<'_, TrackerStatistics> {
self.stats_tracker.get_stats().await
}
}
impl StatsTracker {
pub async fn send_event(&self, event: TrackerStatisticsEvent) -> Option<Result<(), SendError<TrackerStatisticsEvent>>> {
if let Some(tx) = &self.channel_sender {
Some(tx.send(event).await)
} else {
None
}
}
}
impl<T> Sender<T> {
pub(crate) fn new(chan: chan::Tx<T, Semaphore>) -> Sender<T> {
Sender { chan }
}
pub async fn send(&self, value: T) -> Result<(), SendError<T>> {
match self.reserve().await {
Ok(permit) => {
permit.send(value);
Ok(())
}
Err(_) => Err(SendError(value)),
}
} but in the pub async fn handle_connect(
remote_addr: SocketAddr,
request: &ConnectRequest,
tracker: Arc<TorrentTracker>,
) -> Result<Response, ServerError> {
let connection_id = get_connection_id(&remote_addr);
let response = Response::from(ConnectResponse {
transaction_id: request.transaction_id,
connection_id,
});
// send stats event
match remote_addr {
SocketAddr::V4(_) => {
tracker.send_stats_event(TrackerStatisticsEvent::Udp4Connect).await;
}
SocketAddr::V6(_) => {
tracker.send_stats_event(TrackerStatisticsEvent::Udp6Connect).await;
}
}
Ok(response)
} We do not send the response until we receive the confirmation that the event has been sent. Something like: pub fn handle_connect(
&self,
remote_addr: SocketAddr,
request: &ConnectRequest,
tracker: Arc<TorrentTracker>,
) -> Result<Response, ServerError> {
let connection_id = get_connection_id(&remote_addr);
let response = Response::from(ConnectResponse {
transaction_id: request.transaction_id,
connection_id,
});
// send stats event
match remote_addr {
SocketAddr::V4(_) => {
self.trigger_event(TrackerStatisticsEvent::Udp4Connect);
}
SocketAddr::V6(_) => {
self.trigger_event(TrackerStatisticsEvent::Udp6Connect);
}
}
Ok(response)
} The pub async fn start(&self) {
loop {
let mut data = [0; MAX_PACKET_SIZE];
let socket = self.socket.clone();
let tracker = self.tracker.clone();
let request_handler = self.request_handler.clone();
tokio::select! {
_ = tokio::signal::ctrl_c() => {
info!("Stopping UDP server: {}..", socket.local_addr().unwrap());
break;
}
Ok((valid_bytes, remote_addr)) = socket.recv_from(&mut data) => {
let payload = data[..valid_bytes].to_vec();
debug!("Received {} bytes from {}", payload.len(), remote_addr);
debug!("{:?}", payload);
let response = request_handler.handle(remote_addr, payload, tracker);
UdpServer::send_response(socket, remote_addr, response).await;
// We get the list of pending events, and we send them.
// Removing them from the list.
tracker.send_events(request_handler.consume_events()).await
}
}
}
} With this change:
|
beb2570
to
2ac704d
Compare
Hi @josecelano ,
This was done intentional. It only sends the event to a queue, so it should be pretty much instant. The events queue then gets processed by a separate stats worker thread.
I think this is a good improvement. 👍 |
2ac704d
to
e1aee11
Compare
udp::handler
udp::handler::handle_connect
udp::handler::handle_connect
udp::handler::handle_connect
e1aee11
to
7a641d2
Compare
hi @da2ce7 @WarmBeer, I have added the missing tests we discussed this morning. Finally, I didn't use a closure because the closure would be only the factory, but I needed a trait anyway to mock the final object in the test. The closure allows me only to inline the service instantiation. The solution @WarmBeer told me seems to work. I've changed the TorrentTracker constructor: pub struct TorrentTracker {
pub config: Arc<Configuration>,
...
stats_tracker: Box<dyn TrackerStatsService>,
database: Box<dyn Database>,
...
}
impl TorrentTracker {
pub fn new(config: Arc<Configuration>, stats_tracker: Box<dyn TrackerStatsService>) -> Result<TorrentTracker, r2d2::Error> {
let database = database::connect_database(&config.db_driver, &config.db_path)?;
Ok(TorrentTracker {
config: config.clone(),
mode: config.mode,
keys: RwLock::new(std::collections::HashMap::new()),
whitelist: RwLock::new(std::collections::HashSet::new()),
torrents: RwLock::new(std::collections::BTreeMap::new()),
stats_tracker,
database,
})
} The I've also created my own mock for the I've segregated the responsibilities into two traits:
I joined both traits in the |
7a641d2
to
ba6b26d
Compare
udp::handler::handle_connect
udp::handlers
7bb4c9f
to
09004e9
Compare
09004e9
to
a25dc42
Compare
a25dc42
to
7abe0f5
Compare
I continue adding more tests to other parts of the app. Now the
udp::handlers
mod.I've added one test for the connect request.