blob: 6b2745e67e2d85356a9b7a03990573259786f595 [file] [log] [blame]
Mitja Nikolaus03e412b2018-09-18 17:50:15 +02001"""Tests for the stats management command module."""
Mitja Nikolaus03e412b2018-09-18 17:50:15 +02002from io import StringIO
3from datetime import datetime, timedelta
4import unittest
5
6import pytz
7
8from django.core.management import call_command
9from django.test import TestCase
10
11from crashreport_stats.models import (
12 Version,
13 VersionDaily,
14 RadioVersion,
15 RadioVersionDaily,
16 StatsMetadata,
17)
18from crashreport_stats.tests.utils import Dummy
19
20from crashreports.models import Crashreport, HeartBeat
21
22# pylint: disable=too-many-public-methods
23
24
25class StatsCommandVersionsTestCase(TestCase):
26 """Test the generation of Version stats with the stats command."""
27
28 # The class of the version type to be tested
29 version_class = Version
30 # The attribute name characterising the unicity of a stats entry (the
31 # named identifier)
32 unique_entry_name = "build_fingerprint"
33 # The collection of unique entries to post
34 unique_entries = Dummy.BUILD_FINGERPRINTS
35
36 def _create_reports(
37 self, report_type, unique_entry_name, device, number, **kwargs
38 ):
39 # Create reports with distinct timestamps
Mitja Nikolausfd452f82018-11-07 11:53:59 +010040 report_date = datetime.now(pytz.utc)
41 if report_type == HeartBeat:
42 report_date = report_date.date()
Mitja Nikolaus03e412b2018-09-18 17:50:15 +020043 for i in range(number):
Mitja Nikolaus03e412b2018-09-18 17:50:15 +020044 report_attributes = {
45 self.unique_entry_name: unique_entry_name,
46 "device": device,
Mitja Nikolausfd452f82018-11-07 11:53:59 +010047 "date": report_date - timedelta(days=i),
Mitja Nikolaus03e412b2018-09-18 17:50:15 +020048 }
49 report_attributes.update(**kwargs)
50 Dummy.create_dummy_report(report_type, **report_attributes)
51
52 def test_stats_calculation(self):
53 """Test generation of a Version instance."""
54 user = Dummy.create_dummy_user()
55 device = Dummy.create_dummy_device(user=user)
56 heartbeat = Dummy.create_dummy_report(HeartBeat, device=device)
57
58 # Expect that we do not have the Version before updating the stats
59 get_params = {
60 self.unique_entry_name: getattr(heartbeat, self.unique_entry_name)
61 }
62 self.assertRaises(
63 self.version_class.DoesNotExist,
64 self.version_class.objects.get,
65 **get_params
66 )
67
68 # Run the command to update the database
69 call_command("stats", "update")
70
71 # Assume that a corresponding Version instance has been created
72 version = self.version_class.objects.get(**get_params)
73 self.assertIsNotNone(version)
74
75 def _assert_older_report_updates_version_date(self, report_type):
76 """Validate that older reports sent later affect the version date."""
77 user = Dummy.create_dummy_user()
78 device = Dummy.create_dummy_device(user=user)
79 report = Dummy.create_dummy_report(report_type, device=device)
80
81 # Run the command to update the database
82 call_command("stats", "update")
83
84 get_params = {
85 self.unique_entry_name: getattr(report, self.unique_entry_name)
86 }
87 version = self.version_class.objects.get(**get_params)
88
Mitja Nikolausfd452f82018-11-07 11:53:59 +010089 report_date = (
90 report.date.date() if report_type == Crashreport else report.date
91 )
92 self.assertEqual(report_date, version.first_seen_on)
Mitja Nikolaus03e412b2018-09-18 17:50:15 +020093
94 # Create a new report from an earlier point in time
Mitja Nikolausfd452f82018-11-07 11:53:59 +010095 report_date_2 = report.date - timedelta(weeks=1)
Mitja Nikolaus03e412b2018-09-18 17:50:15 +020096 Dummy.create_dummy_report(
Mitja Nikolausfd452f82018-11-07 11:53:59 +010097 report_type, device=device, date=report_date_2
Mitja Nikolaus03e412b2018-09-18 17:50:15 +020098 )
99
100 # Run the command to update the database
101 call_command("stats", "update")
102
103 # Get the same version object from before
104 version = self.version_class.objects.get(**get_params)
105
106 # Validate that the date matches the report recently sent
Mitja Nikolausfd452f82018-11-07 11:53:59 +0100107 if report_type == Crashreport:
108 report_date_2 = report_date_2.date()
109 self.assertEqual(report_date_2, version.first_seen_on)
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200110
111 def test_older_heartbeat_updates_version_date(self):
112 """Validate updating version date with older heartbeats."""
113 self._assert_older_report_updates_version_date(HeartBeat)
114
115 def test_older_crash_report_updates_version_date(self):
116 """Validate updating version date with older crash reports."""
117 self._assert_older_report_updates_version_date(Crashreport)
118
119 def test_entries_are_unique(self):
120 """Validate the entries' unicity and value."""
121 # Create some reports
Mitja Nikolausfd452f82018-11-07 11:53:59 +0100122 for unique_entry, username in zip(self.unique_entries, Dummy.USERNAMES):
123 user = Dummy.create_dummy_user(username=username)
124 device = Dummy.create_dummy_device(user=user)
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200125 self._create_reports(HeartBeat, unique_entry, device, 10)
126
127 # Run the command to update the database
128 call_command("stats", "update")
129
130 # Check whether the correct amount of distinct versions have been
131 # created
132 versions = self.version_class.objects.all()
133 for version in versions:
134 self.assertIn(
135 getattr(version, self.unique_entry_name), self.unique_entries
136 )
137 self.assertEqual(len(versions), len(self.unique_entries))
138
139 def _assert_counter_distribution_is_correct(
140 self, report_type, numbers, counter_attribute_name, **kwargs
141 ):
142 """Validate a counter distribution in the database."""
143 if len(numbers) != len(self.unique_entries):
144 raise ValueError(
145 "The length of the numbers list must match the "
146 "length of self.unique_entries in the test class"
147 "({} != {})".format(len(numbers), len(self.unique_entries))
148 )
149 # Create some reports
Mitja Nikolausfd452f82018-11-07 11:53:59 +0100150 for unique_entry, num, username in zip(
151 self.unique_entries, numbers, Dummy.USERNAMES
152 ):
153 user = Dummy.create_dummy_user(username=username)
154 device = Dummy.create_dummy_device(user=user)
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200155 self._create_reports(
156 report_type, unique_entry, device, num, **kwargs
157 )
158
159 # Run the command to update the database
160 call_command("stats", "update")
161
162 # Check whether the numbers of reports match
163 for version in self.version_class.objects.all():
164 unique_entry_name = getattr(version, self.unique_entry_name)
165 num = numbers[self.unique_entries.index(unique_entry_name)]
166 self.assertEqual(num, getattr(version, counter_attribute_name))
167
168 def test_heartbeats_counter(self):
169 """Test the calculation of the heartbeats counter."""
170 numbers = [10, 7, 8, 5]
171 counter_attribute_name = "heartbeats"
172 self._assert_counter_distribution_is_correct(
173 HeartBeat, numbers, counter_attribute_name
174 )
175
176 def test_crash_reports_counter(self):
177 """Test the calculation of the crashreports counter."""
178 numbers = [2, 5, 0, 3]
179 counter_attribute_name = "prob_crashes"
180 boot_reason_param = {"boot_reason": Crashreport.BOOT_REASON_UNKOWN}
181 self._assert_counter_distribution_is_correct(
182 Crashreport, numbers, counter_attribute_name, **boot_reason_param
183 )
184
185 def test_smpl_reports_counter(self):
186 """Test the calculation of the smpl reports counter."""
187 numbers = [1, 3, 4, 0]
188 counter_attribute_name = "smpl"
189 boot_reason_param = {"boot_reason": Crashreport.BOOT_REASON_RTC_ALARM}
190 self._assert_counter_distribution_is_correct(
191 Crashreport, numbers, counter_attribute_name, **boot_reason_param
192 )
193
194 def test_other_reports_counter(self):
195 """Test the calculation of the other reports counter."""
196 numbers = [0, 2, 1, 2]
197 counter_attribute_name = "other"
198 boot_reason_param = {"boot_reason": "random boot reason"}
199 self._assert_counter_distribution_is_correct(
200 Crashreport, numbers, counter_attribute_name, **boot_reason_param
201 )
202
Mitja Nikolause0df5a32018-09-21 15:59:54 +0200203 def _assert_accumulated_counters_are_correct(
204 self, report_type, counter_attribute_name, **kwargs
205 ):
206 """Validate a counter distribution with reports of different devices."""
207 # Create some devices and corresponding reports
208 devices = [
209 Dummy.create_dummy_device(Dummy.create_dummy_user(username=name))
210 for name in Dummy.USERNAMES
211 ]
212 num_reports = 5
213 for device in devices:
214 self._create_reports(
215 report_type,
216 self.unique_entries[0],
217 device,
218 num_reports,
219 **kwargs
220 )
221
222 # Run the command to update the database
223 call_command("stats", "update")
224
225 # Check whether the numbers of reports match
226 version = self.version_class.objects.get(
227 **{self.unique_entry_name: self.unique_entries[0]}
228 )
229 self.assertEqual(
230 len(Dummy.USERNAMES) * num_reports,
231 getattr(version, counter_attribute_name),
232 )
233
234 def test_accumulated_heartbeats_counter(self):
235 """Test heartbeats counter with reports from different devices."""
236 report_type = HeartBeat
237 counter_attribute_name = "heartbeats"
238 self._assert_accumulated_counters_are_correct(
239 report_type, counter_attribute_name
240 )
241
242 def test_accumulated_crash_reports_counter(self):
243 """Test crash reports counter with reports from different devices."""
244 report_type = Crashreport
245 counter_attribute_name = "prob_crashes"
246 boot_reason_param = {"boot_reason": Crashreport.CRASH_BOOT_REASONS[0]}
247 self._assert_accumulated_counters_are_correct(
248 report_type, counter_attribute_name, **boot_reason_param
249 )
250
251 def test_accumulated_smpl_reports_counter(self):
252 """Test smpl reports counter with reports from different devices."""
253 report_type = Crashreport
254 counter_attribute_name = "smpl"
255 boot_reason_param = {"boot_reason": Crashreport.SMPL_BOOT_REASONS[0]}
256 self._assert_accumulated_counters_are_correct(
257 report_type, counter_attribute_name, **boot_reason_param
258 )
259
260 def test_accumulated_other_reports_counter(self):
261 """Test other reports counter with reports from different devices."""
262 report_type = Crashreport
263 counter_attribute_name = "other"
264 boot_reason_param = {"boot_reason": "random boot reason"}
265 self._assert_accumulated_counters_are_correct(
266 report_type, counter_attribute_name, **boot_reason_param
267 )
268
Mitja Nikolaus42e400f2018-10-01 15:15:04 +0200269 def test_reset_deletion_of_unrelated_version(self):
270 """Test delete functionality of the reset command."""
271 # Create a version instance that is not related to any reports
272 Dummy.create_dummy_version(
273 self.version_class,
274 **{self.unique_entry_name: self.unique_entries[0]}
275 )
276
277 # Run the command to reset the database
278 call_command("stats", "reset")
279
280 # Check whether the unrelated version instance has been deleted
281 self.assertFalse(
282 self.version_class.objects.filter(
283 **{self.unique_entry_name: self.unique_entries[0]}
284 ).exists()
285 )
286
287 def _assert_reset_updates_counter(
288 self, report_type, counter_attribute_name, **kwargs
289 ):
290 # Create a device and corresponding reports
291 device = Dummy.create_dummy_device(Dummy.create_dummy_user())
292 num_reports = 5
293 self._create_reports(
294 report_type, self.unique_entries[0], device, num_reports, **kwargs
295 )
296
297 # Create a version instance with wrong numbers
298 wrong_num_of_reports = 4
299 Dummy.create_dummy_version(
300 self.version_class,
301 **{
302 self.unique_entry_name: self.unique_entries[0],
303 counter_attribute_name: wrong_num_of_reports,
304 }
305 )
306
307 # Run the command to reset the database
308 call_command("stats", "reset")
309
310 # Check whether the numbers of reports do match
311 version = self.version_class.objects.get(
312 **{self.unique_entry_name: self.unique_entries[0]}
313 )
314 self.assertEqual(num_reports, getattr(version, counter_attribute_name))
315
316 def test_reset_update_heartbeat_counter(self):
317 """Test update of the heartbeat counter using the reset command."""
318 self._assert_reset_updates_counter(HeartBeat, "heartbeats")
319
320 def test_reset_update_crash_report_counter(self):
321 """Test update of the crash report counter using the reset command."""
322 boot_reason_param = {"boot_reason": Crashreport.CRASH_BOOT_REASONS[0]}
323 self._assert_reset_updates_counter(
324 Crashreport, "prob_crashes", **boot_reason_param
325 )
326
327 def test_reset_update_smpl_report_counter(self):
328 """Test update of the smpl report counter using the reset command."""
329 boot_reason_param = {"boot_reason": Crashreport.SMPL_BOOT_REASONS[0]}
330 self._assert_reset_updates_counter(
331 Crashreport, "smpl", **boot_reason_param
332 )
333
334 def test_reset_update_other_report_counter(self):
335 """Test update of the other report counter using the reset command."""
336 boot_reason_param = {"boot_reason": "random boot reason"}
337 self._assert_reset_updates_counter(
338 Crashreport, "other", **boot_reason_param
339 )
340
Mitja Nikolaus0ff39472018-09-24 15:00:04 +0200341 def _assert_updating_twice_gives_correct_counters(
342 self, report_type, counter_attribute_name, **boot_reason_param
343 ):
Mitja Nikolausfd452f82018-11-07 11:53:59 +0100344 # Create a two devices and a corresponding reports for 2 different
345 # versions
346 device_1 = Dummy.create_dummy_device(Dummy.create_dummy_user())
Mitja Nikolaus0ff39472018-09-24 15:00:04 +0200347 num_reports = 5
348 self._create_reports(
349 report_type,
350 self.unique_entries[0],
Mitja Nikolausfd452f82018-11-07 11:53:59 +0100351 device_1,
Mitja Nikolaus0ff39472018-09-24 15:00:04 +0200352 num_reports,
353 **boot_reason_param
354 )
Mitja Nikolausfd452f82018-11-07 11:53:59 +0100355 device_2 = Dummy.create_dummy_device(
356 Dummy.create_dummy_user(username=Dummy.USERNAMES[1])
357 )
Mitja Nikolaus0ff39472018-09-24 15:00:04 +0200358 self._create_reports(
359 report_type,
360 self.unique_entries[1],
Mitja Nikolausfd452f82018-11-07 11:53:59 +0100361 device_2,
Mitja Nikolaus0ff39472018-09-24 15:00:04 +0200362 num_reports,
363 **boot_reason_param
364 )
365
366 # Run the command to update the database
367 call_command("stats", "update")
368
369 # Check whether the numbers of reports match for both versions
370 version_1 = self.version_class.objects.get(
371 **{self.unique_entry_name: self.unique_entries[0]}
372 )
373 self.assertEqual(
374 num_reports, getattr(version_1, counter_attribute_name)
375 )
376 version_2 = self.version_class.objects.get(
377 **{self.unique_entry_name: self.unique_entries[1]}
378 )
379 self.assertEqual(
380 num_reports, getattr(version_2, counter_attribute_name)
381 )
382
383 # Create another report for the first version
384 report_new_attributes = {
385 self.unique_entry_name: self.unique_entries[0],
Mitja Nikolausfd452f82018-11-07 11:53:59 +0100386 "device": device_1,
Mitja Nikolaus0ff39472018-09-24 15:00:04 +0200387 **boot_reason_param,
388 }
389 Dummy.create_dummy_report(report_type, **report_new_attributes)
390
391 # Run the command to update the database again
392 call_command("stats", "update")
393
394 # Check whether the numbers of reports match:
395 # Assert that the first version's counter is increased by one
396 version_1 = self.version_class.objects.get(
397 **{self.unique_entry_name: self.unique_entries[0]}
398 )
399 self.assertEqual(
400 num_reports + 1, getattr(version_1, counter_attribute_name)
401 )
402 # Assert that the second versions counter is unchanged
403 version_2 = self.version_class.objects.get(
404 **{self.unique_entry_name: self.unique_entries[1]}
405 )
406 self.assertEqual(
407 num_reports, getattr(version_2, counter_attribute_name)
408 )
409
410 def test_updating_heartbeats_counter_twice(self):
411 """Test updating the heartbeats counter twice."""
412 self._assert_updating_twice_gives_correct_counters(
413 HeartBeat, "heartbeats"
414 )
415
416 def test_updating_crash_reports_counter_twice(self):
417 """Test updating the crash reports counter twice."""
418 boot_reason_param = {"boot_reason": Crashreport.CRASH_BOOT_REASONS[0]}
419 self._assert_updating_twice_gives_correct_counters(
420 Crashreport, "prob_crashes", **boot_reason_param
421 )
422
423 def test_updating_smpl_reports_counter_twice(self):
424 """Test updating the smpl reports counter twice."""
425 boot_reason_param = {"boot_reason": Crashreport.SMPL_BOOT_REASONS[0]}
426 self._assert_updating_twice_gives_correct_counters(
427 Crashreport, "smpl", **boot_reason_param
428 )
429
430 def test_updating_other_reports_counter_twice(self):
431 """Test updating the other reports counter twice."""
432 boot_reason_param = {"boot_reason": "random boot reason"}
433 self._assert_updating_twice_gives_correct_counters(
434 Crashreport, "other", **boot_reason_param
435 )
436
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200437 def _assert_reports_with_same_timestamp_are_counted(
438 self, report_type, counter_attribute_name, **kwargs
439 ):
440 """Validate that reports with the same timestamp are counted.
441
442 Reports from different devices but the same timestamp should be
443 counted as independent reports.
444 """
445 # Create a report
446 device1 = Dummy.create_dummy_device(user=Dummy.create_dummy_user())
447 report1 = Dummy.create_dummy_report(
448 report_type, device=device1, **kwargs
449 )
450
451 # Create a second report with the same timestamp but from another device
452 device2 = Dummy.create_dummy_device(
453 user=Dummy.create_dummy_user(username=Dummy.USERNAMES[1])
454 )
455 Dummy.create_dummy_report(
456 report_type, device=device2, date=report1.date, **kwargs
457 )
458
459 # Run the command to update the database
460 call_command("stats", "update")
461
462 # Get the corresponding version instance from the database
463 get_params = {
464 self.unique_entry_name: getattr(report1, self.unique_entry_name)
465 }
466 version = self.version_class.objects.get(**get_params)
467
468 # Assert that both reports are counted
469 self.assertEqual(getattr(version, counter_attribute_name), 2)
470
471 @unittest.skip(
472 "Duplicates are dropped based on their timestamp at the moment. This is"
473 "to be adapted so that they are dropped taking into account the device"
474 "UUID as well."
475 )
476 def test_heartbeats_with_same_timestamp_are_counted(self):
477 """Validate that heartbeats with same timestamp are counted."""
478 counter_attribute_name = "heartbeats"
479 self._assert_reports_with_same_timestamp_are_counted(
480 HeartBeat, counter_attribute_name
481 )
482
483 @unittest.skip(
484 "Duplicates are dropped based on their timestamp at the moment. This is"
485 "to be adapted so that they are dropped taking into account the device"
486 "UUID as well."
487 )
488 def test_crash_reports_with_same_timestamp_are_counted(self):
489 """Validate that crash report duplicates are ignored."""
490 counter_attribute_name = "prob_crashes"
491 for unique_entry, boot_reason in zip(
492 self.unique_entries, Crashreport.CRASH_BOOT_REASONS
493 ):
494 params = {
495 "boot_reason": boot_reason,
496 self.unique_entry_name: unique_entry,
497 }
498 self._assert_reports_with_same_timestamp_are_counted(
499 Crashreport, counter_attribute_name, **params
500 )
501
502 @unittest.skip(
503 "Duplicates are dropped based on their timestamp at the moment. This is"
504 "to be adapted so that they are dropped taking into account the device"
505 "UUID as well."
506 )
507 def test_smpl_reports_with_same_timestamp_are_counted(self):
508 """Validate that smpl report duplicates are ignored."""
509 counter_attribute_name = "smpl"
510 for unique_entry, boot_reason in zip(
511 self.unique_entries, Crashreport.SMPL_BOOT_REASONS
512 ):
513 params = {
514 "boot_reason": boot_reason,
515 self.unique_entry_name: unique_entry,
516 }
517 self._assert_reports_with_same_timestamp_are_counted(
518 Crashreport, counter_attribute_name, **params
519 )
520
521 @unittest.skip(
522 "Duplicates are dropped based on their timestamp at the moment. This is"
523 "to be adapted so that they are dropped taking into account the device"
524 "UUID as well."
525 )
526 def test_other_reports_with_same_timestamp_are_counted(self):
527 """Validate that other report duplicates are ignored."""
528 counter_attribute_name = "other"
529 params = {"boot_reason": "random boot reason"}
530 self._assert_reports_with_same_timestamp_are_counted(
531 Crashreport, counter_attribute_name, **params
532 )
533
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200534 def _assert_older_reports_update_released_on_date(
535 self, report_type, **kwargs
536 ):
537 """Test updating of the released_on date.
538
539 Validate that the released_on date is updated once an older report is
540 sent.
541 """
542 # Create a report
543 device = Dummy.create_dummy_device(user=Dummy.create_dummy_user())
544 report = Dummy.create_dummy_report(report_type, device=device, **kwargs)
545
546 # Run the command to update the database
547 call_command("stats", "update")
548
549 # Get the corresponding version instance from the database
550 version = self.version_class.objects.get(
551 **{self.unique_entry_name: getattr(report, self.unique_entry_name)}
552 )
553
554 # Assert that the released_on date matches the first report date
Mitja Nikolausfd452f82018-11-07 11:53:59 +0100555 report_date = (
556 report.date.date() if report_type == Crashreport else report.date
557 )
558 self.assertEqual(version.released_on, report_date)
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200559
560 # Create a second report with the a timestamp earlier in time
561 report_2_date = report.date - timedelta(days=1)
562 Dummy.create_dummy_report(
563 report_type, device=device, date=report_2_date, **kwargs
564 )
565
566 # Run the command to update the database
567 call_command("stats", "update")
568
569 # Get the corresponding version instance from the database
570 version = self.version_class.objects.get(
571 **{self.unique_entry_name: getattr(report, self.unique_entry_name)}
572 )
573
574 # Assert that the released_on date matches the older report date
Mitja Nikolausfd452f82018-11-07 11:53:59 +0100575 if report_type == Crashreport:
576 report_2_date = report_2_date.date()
577 self.assertEqual(version.released_on, report_2_date)
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200578
579 def _assert_newer_reports_do_not_update_released_on_date(
580 self, report_type, **kwargs
581 ):
582 """Test updating of the released_on date.
583
584 Validate that the released_on date is not updated once a newer report is
585 sent.
586 """
587 # Create a report
588 device = Dummy.create_dummy_device(user=Dummy.create_dummy_user())
589 report = Dummy.create_dummy_report(report_type, device=device, **kwargs)
Mitja Nikolausfd452f82018-11-07 11:53:59 +0100590 report_1_date = (
591 report.date.date() if report_type == Crashreport else report.date
592 )
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200593
594 # Run the command to update the database
595 call_command("stats", "update")
596
597 # Get the corresponding version instance from the database
598 version = self.version_class.objects.get(
599 **{self.unique_entry_name: getattr(report, self.unique_entry_name)}
600 )
601
602 # Assert that the released_on date matches the first report date
603 self.assertEqual(version.released_on, report_1_date)
604
605 # Create a second report with the a timestamp later in time
606 report_2_date = report.date + timedelta(days=1)
607 Dummy.create_dummy_report(
608 report_type, device=device, date=report_2_date, **kwargs
609 )
610
611 # Run the command to update the database
612 call_command("stats", "update")
613
614 # Get the corresponding version instance from the database
615 version = self.version_class.objects.get(
616 **{self.unique_entry_name: getattr(report, self.unique_entry_name)}
617 )
618
619 # Assert that the released_on date matches the older report date
620 self.assertEqual(version.released_on, report_1_date)
621
622 def test_older_heartbeat_updates_released_on_date(self):
623 """Validate that older heartbeats update the release date."""
624 self._assert_older_reports_update_released_on_date(HeartBeat)
625
626 def test_older_crash_report_updates_released_on_date(self):
627 """Validate that older crash reports update the release date."""
628 self._assert_older_reports_update_released_on_date(Crashreport)
629
630 def test_newer_heartbeat_does_not_update_released_on_date(self):
631 """Validate that newer heartbeats don't update the release date."""
632 self._assert_newer_reports_do_not_update_released_on_date(HeartBeat)
633
634 def test_newer_crash_report_does_not_update_released_on_date(self):
635 """Validate that newer crash reports don't update the release date."""
636 self._assert_newer_reports_do_not_update_released_on_date(Crashreport)
637
638 def _assert_manually_changed_released_on_date_is_not_updated(
639 self, report_type, **kwargs
640 ):
641 """Test updating of manually changed released_on dates.
642
643 Validate that a manually changed released_on date is not updated when
644 new reports are sent.
645 """
646 # Create a report
647 device = Dummy.create_dummy_device(user=Dummy.create_dummy_user())
648 report = Dummy.create_dummy_report(report_type, device=device, **kwargs)
649
650 # Run the command to update the database
651 call_command("stats", "update")
652
653 # Get the corresponding version instance from the database
654 version = self.version_class.objects.get(
655 **{self.unique_entry_name: getattr(report, self.unique_entry_name)}
656 )
657
658 # Assert that the released_on date matches the first report date
Mitja Nikolausfd452f82018-11-07 11:53:59 +0100659 report_date = (
660 report.date.date() if report_type == Crashreport else report.date
661 )
662 self.assertEqual(version.released_on, report_date)
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200663
664 # Create a second report with a timestamp earlier in time
665 report_2_date = report.date - timedelta(days=1)
666 Dummy.create_dummy_report(
667 report_type, device=device, date=report_2_date, **kwargs
668 )
669
670 # Manually change the released_on date
Mitja Nikolausfd452f82018-11-07 11:53:59 +0100671 version_release_date = report_date + timedelta(days=1)
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200672 version.released_on = version_release_date
673 version.save()
674
675 # Run the command to update the database
676 call_command("stats", "update")
677
678 # Get the corresponding version instance from the database
679 version = self.version_class.objects.get(
680 **{self.unique_entry_name: getattr(report, self.unique_entry_name)}
681 )
682
683 # Assert that the released_on date still matches the date is was
684 # manually changed to
Mitja Nikolausfd452f82018-11-07 11:53:59 +0100685 self.assertEqual(version.released_on, version_release_date)
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200686
687 def test_manually_changed_released_on_date_is_not_updated_by_heartbeat(
688 self
689 ):
690 """Test update of manually changed released_on date with heartbeat."""
691 self._assert_manually_changed_released_on_date_is_not_updated(HeartBeat)
692
693 def test_manually_changed_released_on_date_is_not_updated_by_crash_report(
694 self
695 ):
696 """Test update of manually changed released_on date with crashreport."""
697 self._assert_manually_changed_released_on_date_is_not_updated(
698 Crashreport
699 )
700
701
702# pylint: disable=too-many-ancestors
703class StatsCommandRadioVersionsTestCase(StatsCommandVersionsTestCase):
704 """Test the generation of RadioVersion stats with the stats command."""
705
706 version_class = RadioVersion
707 unique_entry_name = "radio_version"
708 unique_entries = Dummy.RADIO_VERSIONS
709
710
711class CommandDebugOutputTestCase(TestCase):
712 """Test the reset and update commands debug output."""
713
714 # Additional positional arguments to pass to the commands
715 _CMD_ARGS = ["--no-color", "-v 2"]
716
717 # The stats models
718 _STATS_MODELS = [Version, VersionDaily, RadioVersion, RadioVersionDaily]
719 # The models that will generate an output
720 _ALL_MODELS = _STATS_MODELS + [StatsMetadata]
721 _COUNTER_NAMES = ["heartbeats", "crashes", "smpl", "other"]
722 _COUNTER_ACTIONS = ["created", "updated"]
723
724 def _assert_command_output_matches(self, command, number, facts, models):
725 """Validate the debug output of a command.
726
727 The debug output is matched against the facts and models given in
728 the parameters.
729 """
730 buffer = StringIO()
731 call_command("stats", command, *self._CMD_ARGS, stdout=buffer)
732 output = buffer.getvalue().splitlines()
733
734 expected_output = "{number} {model} {fact}"
735 for model in models:
736 for fact in facts:
737 self.assertIn(
738 expected_output.format(
739 number=number, model=model.__name__, fact=fact
740 ),
741 output,
742 )
743
744 def test_reset_command_on_empty_db(self):
745 """Test the reset command on an empty database.
746
747 The reset command should yield nothing on an empty database.
748 """
749 self._assert_command_output_matches(
750 "reset", 0, ["deleted"], self._ALL_MODELS
751 )
752
753 def test_update_command_on_empty_db(self):
754 """Test the update command on an empty database.
755
756 The update command should yield nothing on an empty database.
757 """
758 pattern = "{action} for counter {counter}"
759 facts = [
760 pattern.format(action=counter_action, counter=counter_name)
761 for counter_action in self._COUNTER_ACTIONS
762 for counter_name in self._COUNTER_NAMES
763 ]
764 self._assert_command_output_matches(
765 "update", 0, facts, self._STATS_MODELS
766 )
767
768 def test_reset_command_deletion_of_instances(self):
769 """Test the deletion of stats model instances with the reset command.
770
771 This test validates that model instances get deleted when the
772 reset command is called on a database that only contains a single
773 model instance for each class.
774 """
775 # Create dummy version instances
776 version = Dummy.create_dummy_version()
Mitja Nikolaus35a02652018-10-01 15:10:18 +0200777 radio_version = Dummy.create_dummy_version(RadioVersion)
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200778 Dummy.create_dummy_daily_version(version)
779 Dummy.create_dummy_daily_radio_version(radio_version)
780 Dummy.create_dummy_stats_metadata()
781
782 # We expect that the model instances get deleted
783 self._assert_command_output_matches(
784 "reset", 1, ["deleted"], self._ALL_MODELS
785 )