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::handlerudp::handler::handle_connect
udp::handler::handle_connectudp::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_connectudp::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::handlersmod.I've added one test for the connect request.