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