-
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
Make tracker statistics optional again #102
Conversation
ACK d24af95 |
I find it confusing that calling Is it possible to write a test to check if the starts are being collected or not? |
Hi @da2ce7 . Yes, I agree. I've already discussed it with @WarmBeer . I did not want to change anything in this PR, just to revert the bug asap. Regarding testing, the test would depend on how we refactor it. I told @WarmBeer that I see two options:
A more advanced approach could be implementing a middleware or observer pattern. The For the time being, I would merge this PR and create the discussion. We can rename the method. The method does:
Maybe we could call it We could also make small refactors before implementing a more generic approach. I think we could split the class into:
But I do not know if it makes sense or if we can go directly to a generic event/listener approach. The events could belong to the Tracker (core) domain, but you can register as many listeners as you want. One of them could be the StatsTracker. I will elaborate on those ideas in the discussion. |
@josecelano What about adding a minimal unit test so that we can check that the stats are indeed being/not being collected? |
OK, I will try. To be honest, I also wanted to do it but, I thought that would be a big effort since we have to deal with concurrency :-). And I was focused on other things. But let's try at least. I think sooner or later we have to do that kind of test. Even if we change the implementation later, what we can learn it's going to be very useful in the future for other tests. |
Commit 7abe0f5 introduced an unwanted change. Thread for statistics is always created regardless configuration. This commit reverts that change. The config option: config.tracker_usage_statistics defines wether the statistics should be enabled or not.
d24af95
to
adee3b5
Compare
src/tracker/statistics.rs
Outdated
|
||
let stats = active_stats_tracker.get_stats().await; | ||
|
||
assert_eq!(stats.tcp4_connections_handled, 0); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we have the same assert:
assert_eq!(stats.tcp4_connections_handled, 0);
assert_eq!(stats.tcp4_connections_handled, 0);
for both the active, and the inactive tests?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey @da2ce7 , It was just a copy/paste typo, but I realized the tests were passing after pushing the commit. I do not know why they pass (why the counter does not work). I'm working on that.
hey @da2ce7 I've added the test but as you can see the test for the active stats tracker should not pass because the counters are zero. I'm going to continue working on that. My first idea was an integration or e2e test since the code to enable/disable stats is in the |
Tracker statistics can be enabled or disabled using the configuration option `tracker_usage_statistics`. This commit adds tests for that behavior.
8e7d006
to
e12d8e6
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me :)
hi @da2ce7 @WarmBeer, I decided to change the testing strategy for this case. I was trying to write a test with the state-based style. I wanted the run the StatsTracker, send a message and assert that the stats change. The problem with that approach is the worker is an infinite loop. The tests passed if I waited for the worker thread for milliseconds. You can do it this way: #[tokio::test(flavor = "multi_thread")]
async fn my_test() {
let stats_tracker = StatsTracker::new_instance(...);
let result = stats_tracker.send_event(TrackerStatisticsEvent::Tcp4Announce).await;
// Wait until the worker thread process the event
println!(...) // A non-blocking wait
assert!(...);
} But I did not want to introduce a random wait because:
I came up with two ideas:
With these two solutions, you can wait until the worker ends processing all the events, and you can get the stats. I did not like it because we change the behaviour only for testing, and we did not want to refactor just to cover this case with a test. So, I started to think again about what I was trying to test. I think I could say I was: "Trying to assert that statistics are collected correctly when they are enabled" and "statistics are not collected when they are disabled". With the new test version, I only check whether the statistics are collected or not, but I do not test if they are updated correctly. Maybe we could add another test to assert that once the worker receives an event, it updates the statistics correctly. This way, we end up having two unit tests instead of one single test for both things. Of course, we are not testing if the event is sent correctly to the worker. But I suppose the goal for this test was the optional behavior. I mean, testing that we can collect or not the statistics with a configuration option. Actually, we test that the StatTracker can be enabled/disabled from the constructor, but we are not testing that the |
ACK e12d8e6 |
6f77dfe fix: use only minor version for dependencies (Jose Celano) 9e49305 test: add tests for StatsTracker (Jose Celano) 0dd95e7 refactor: extract statistics event_handler (Jose Celano) d3297cf refactor: extract StatsRepository (Jose Celano) 8874032 fix: weak tests (Jose Celano) e570110 test: add new dev dependency mockall (Jose Celano) bc3df5a fix typo (Jose Celano) a2b16ff fix: tests using mock for old service (Jose Celano) daec1fe refactor: extract stats event_listener (Jose Celano) 5b73d80 refactor: removed unused code and extract fn (Jose Celano) 720a584 refactor: use StatsEventSender to send events (Jose Celano) b784442 refactor: inject new struct StatsEventSender into TorrentTracker (Jose Celano) a334f17 refactor: extract struct StatsEventSender (Jose Celano) Pull request description: This is an experimental refactor of the [StatsTracker](https://github.com/torrust/torrust-tracker/blob/develop/src/tracker/statistics.rs). It's still a WIP. Tracker statistics are optional, and we had [problems testing that behaviour](#102). I decided to try to make that code more testable. My idea is: - Convert the StatsTracker into a StatsService that only creates the communication channel and the other services (EventSender, EventListener, ...) - A EventSender to send the events. - A EventListener that updates the statistics. - A StatisticsRepository that contains the stats. The high-level service (StatsService) only creates the StatisticsRepository used by the EventListener and the TorrusTracker. And the communication channel (with the sender and listener). TODO: - [X] Extract EventSender - [X] Extract EventListener - [x] Extract StatisticsRepository - [x] Fix StatsEventSenderMock - [x] Add tests for EnventListener Once we have the EventListener, we can add tests to check if it updates the stats correctly, which is something we did not test on [PR-112](#102). While working on the refactor, I realized the `StatsEventSenderMock` does not work correctly. It only works when the event is sent, but it does not fail if no event is sent. The obvious solution would be to store the event sent in the Mock, but the method is not mutable. ```rust struct StatsEventSenderMock { expected_event: Option<TrackerStatisticsEvent>, } impl StatsEventSenderMock { fn new() -> Self { Self { expected_event: None } } fn should_throw_event(&mut self, expected_event: TrackerStatisticsEvent) { self.expected_event = Some(expected_event); } } #[async_trait] impl TrackerStatisticsEventSender for StatsEventSenderMock { async fn send_event(&self, event: TrackerStatisticsEvent) -> Option<Result<(), SendError<TrackerStatisticsEvent>>> { if self.expected_event.is_some() { assert_eq!(event, *self.expected_event.as_ref().unwrap()); } None } } #[tokio::test] async fn it_should_send_the_upd4_connect_event_when_a_client_tries_to_connect_using_a_ip4_socket_address() { let tracker_stats_service = Box::new(TrackerStatsServiceMock::new()); let mut stats_event_sender = Box::new(StatsEventSenderMock::new()); let client_socket_address = sample_ipv4_socket_address(); stats_event_sender.should_throw_event(TrackerStatisticsEvent::Udp4Connect); let torrent_tracker = Arc::new(TorrentTracker::new(default_tracker_config(), tracker_stats_service, Some(stats_event_sender)).unwrap()); handle_connect(client_socket_address, &sample_connect_request(), torrent_tracker) .await .unwrap(); } ``` I did not want to add a mocking library only for this case. That's why I did it manually. And I do not want to make that function mutable only for the test. ACKs for top commit: da2ce7: ACK 6f77dfe Tree-SHA512: 83f3c118b78bf0544cfa7381aa124c6f367ebacf1c699142d54b7b22449234410cb6e1ae8f41474398ec18a308d8294e598bbf00f0a07ae606bdc2fa47c73e09
Commit 7abe0f5 introduced an unwanted change. Thread for statistics is always created regardless configuration.
This commit reverts that change. The config option:
config.tracker_usage_statistics
defines wether the statistics should be enabled or not.