blob: c31e858ef756edc4eaa53a8495baf205e87e6d7a [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 Nikolaus03e412b2018-09-18 17:50:15 +0200334 def _assert_reports_with_same_timestamp_are_counted(
335 self, report_type, counter_attribute_name, **kwargs
336 ):
337 """Validate that reports with the same timestamp are counted.
338
339 Reports from different devices but the same timestamp should be
340 counted as independent reports.
341 """
342 # Create a report
343 device1 = Dummy.create_dummy_device(user=Dummy.create_dummy_user())
344 report1 = Dummy.create_dummy_report(
345 report_type, device=device1, **kwargs
346 )
347
348 # Create a second report with the same timestamp but from another device
349 device2 = Dummy.create_dummy_device(
350 user=Dummy.create_dummy_user(username=Dummy.USERNAMES[1])
351 )
352 Dummy.create_dummy_report(
353 report_type, device=device2, date=report1.date, **kwargs
354 )
355
356 # Run the command to update the database
357 call_command("stats", "update")
358
359 # Get the corresponding version instance from the database
360 get_params = {
361 self.unique_entry_name: getattr(report1, self.unique_entry_name)
362 }
363 version = self.version_class.objects.get(**get_params)
364
365 # Assert that both reports are counted
366 self.assertEqual(getattr(version, counter_attribute_name), 2)
367
368 @unittest.skip(
369 "Duplicates are dropped based on their timestamp at the moment. This is"
370 "to be adapted so that they are dropped taking into account the device"
371 "UUID as well."
372 )
373 def test_heartbeats_with_same_timestamp_are_counted(self):
374 """Validate that heartbeats with same timestamp are counted."""
375 counter_attribute_name = "heartbeats"
376 self._assert_reports_with_same_timestamp_are_counted(
377 HeartBeat, counter_attribute_name
378 )
379
380 @unittest.skip(
381 "Duplicates are dropped based on their timestamp at the moment. This is"
382 "to be adapted so that they are dropped taking into account the device"
383 "UUID as well."
384 )
385 def test_crash_reports_with_same_timestamp_are_counted(self):
386 """Validate that crash report duplicates are ignored."""
387 counter_attribute_name = "prob_crashes"
388 for unique_entry, boot_reason in zip(
389 self.unique_entries, Crashreport.CRASH_BOOT_REASONS
390 ):
391 params = {
392 "boot_reason": boot_reason,
393 self.unique_entry_name: unique_entry,
394 }
395 self._assert_reports_with_same_timestamp_are_counted(
396 Crashreport, counter_attribute_name, **params
397 )
398
399 @unittest.skip(
400 "Duplicates are dropped based on their timestamp at the moment. This is"
401 "to be adapted so that they are dropped taking into account the device"
402 "UUID as well."
403 )
404 def test_smpl_reports_with_same_timestamp_are_counted(self):
405 """Validate that smpl report duplicates are ignored."""
406 counter_attribute_name = "smpl"
407 for unique_entry, boot_reason in zip(
408 self.unique_entries, Crashreport.SMPL_BOOT_REASONS
409 ):
410 params = {
411 "boot_reason": boot_reason,
412 self.unique_entry_name: unique_entry,
413 }
414 self._assert_reports_with_same_timestamp_are_counted(
415 Crashreport, counter_attribute_name, **params
416 )
417
418 @unittest.skip(
419 "Duplicates are dropped based on their timestamp at the moment. This is"
420 "to be adapted so that they are dropped taking into account the device"
421 "UUID as well."
422 )
423 def test_other_reports_with_same_timestamp_are_counted(self):
424 """Validate that other report duplicates are ignored."""
425 counter_attribute_name = "other"
426 params = {"boot_reason": "random boot reason"}
427 self._assert_reports_with_same_timestamp_are_counted(
428 Crashreport, counter_attribute_name, **params
429 )
430
431 def _assert_duplicates_are_ignored(
432 self, report_type, device, counter_attribute_name, **kwargs
433 ):
434 """Validate that reports with duplicate timestamps are ignored."""
435 # Create a report
436 report = Dummy.create_dummy_report(report_type, device=device, **kwargs)
437
438 # Create a second report with the same timestamp
439 Dummy.create_dummy_report(
440 report_type, device=device, date=report.date, **kwargs
441 )
442
443 # Run the command to update the database
444 call_command("stats", "update")
445
446 # Get the corresponding version instance from the database
447 get_params = {
448 self.unique_entry_name: getattr(report, self.unique_entry_name)
449 }
450 version = self.version_class.objects.get(**get_params)
451
452 # Assert that the report with the duplicate timestamp is not
453 # counted, i.e. only 1 report is counted.
454 self.assertEqual(getattr(version, counter_attribute_name), 1)
455
456 def test_heartbeat_duplicates_are_ignored(self):
457 """Validate that heartbeat duplicates are ignored."""
458 counter_attribute_name = "heartbeats"
459 device = Dummy.create_dummy_device(user=Dummy.create_dummy_user())
460 self._assert_duplicates_are_ignored(
461 HeartBeat, device, counter_attribute_name
462 )
463
464 def test_crash_report_duplicates_are_ignored(self):
465 """Validate that crash report duplicates are ignored."""
466 counter_attribute_name = "prob_crashes"
467 device = Dummy.create_dummy_device(user=Dummy.create_dummy_user())
468 for i, boot_reason in enumerate(Crashreport.CRASH_BOOT_REASONS):
469 params = {
470 "boot_reason": boot_reason,
471 self.unique_entry_name: self.unique_entries[i],
472 }
473 self._assert_duplicates_are_ignored(
474 Crashreport, device, counter_attribute_name, **params
475 )
476
477 def test_smpl_report_duplicates_are_ignored(self):
478 """Validate that smpl report duplicates are ignored."""
479 counter_attribute_name = "smpl"
480 device = Dummy.create_dummy_device(user=Dummy.create_dummy_user())
481 for i, boot_reason in enumerate(Crashreport.SMPL_BOOT_REASONS):
482 params = {
483 "boot_reason": boot_reason,
484 self.unique_entry_name: self.unique_entries[i],
485 }
486 self._assert_duplicates_are_ignored(
487 Crashreport, device, counter_attribute_name, **params
488 )
489
490 def test_other_report_duplicates_are_ignored(self):
491 """Validate that other report duplicates are ignored."""
492 counter_attribute_name = "other"
493 params = {"boot_reason": "random boot reason"}
494 device = Dummy.create_dummy_device(user=Dummy.create_dummy_user())
495 self._assert_duplicates_are_ignored(
496 Crashreport, device, counter_attribute_name, **params
497 )
498
499 def _assert_older_reports_update_released_on_date(
500 self, report_type, **kwargs
501 ):
502 """Test updating of the released_on date.
503
504 Validate that the released_on date is updated once an older report is
505 sent.
506 """
507 # Create a report
508 device = Dummy.create_dummy_device(user=Dummy.create_dummy_user())
509 report = Dummy.create_dummy_report(report_type, device=device, **kwargs)
510
511 # Run the command to update the database
512 call_command("stats", "update")
513
514 # Get the corresponding version instance from the database
515 version = self.version_class.objects.get(
516 **{self.unique_entry_name: getattr(report, self.unique_entry_name)}
517 )
518
519 # Assert that the released_on date matches the first report date
520 self.assertEqual(version.released_on, report.date.date())
521
522 # Create a second report with the a timestamp earlier in time
523 report_2_date = report.date - timedelta(days=1)
524 Dummy.create_dummy_report(
525 report_type, device=device, date=report_2_date, **kwargs
526 )
527
528 # Run the command to update the database
529 call_command("stats", "update")
530
531 # Get the corresponding version instance from the database
532 version = self.version_class.objects.get(
533 **{self.unique_entry_name: getattr(report, self.unique_entry_name)}
534 )
535
536 # Assert that the released_on date matches the older report date
537 self.assertEqual(version.released_on, report_2_date.date())
538
539 def _assert_newer_reports_do_not_update_released_on_date(
540 self, report_type, **kwargs
541 ):
542 """Test updating of the released_on date.
543
544 Validate that the released_on date is not updated once a newer report is
545 sent.
546 """
547 # Create a report
548 device = Dummy.create_dummy_device(user=Dummy.create_dummy_user())
549 report = Dummy.create_dummy_report(report_type, device=device, **kwargs)
550 report_1_date = report.date.date()
551
552 # Run the command to update the database
553 call_command("stats", "update")
554
555 # Get the corresponding version instance from the database
556 version = self.version_class.objects.get(
557 **{self.unique_entry_name: getattr(report, self.unique_entry_name)}
558 )
559
560 # Assert that the released_on date matches the first report date
561 self.assertEqual(version.released_on, report_1_date)
562
563 # Create a second report with the a timestamp later in time
564 report_2_date = report.date + timedelta(days=1)
565 Dummy.create_dummy_report(
566 report_type, device=device, date=report_2_date, **kwargs
567 )
568
569 # Run the command to update the database
570 call_command("stats", "update")
571
572 # Get the corresponding version instance from the database
573 version = self.version_class.objects.get(
574 **{self.unique_entry_name: getattr(report, self.unique_entry_name)}
575 )
576
577 # Assert that the released_on date matches the older report date
578 self.assertEqual(version.released_on, report_1_date)
579
580 def test_older_heartbeat_updates_released_on_date(self):
581 """Validate that older heartbeats update the release date."""
582 self._assert_older_reports_update_released_on_date(HeartBeat)
583
584 def test_older_crash_report_updates_released_on_date(self):
585 """Validate that older crash reports update the release date."""
586 self._assert_older_reports_update_released_on_date(Crashreport)
587
588 def test_newer_heartbeat_does_not_update_released_on_date(self):
589 """Validate that newer heartbeats don't update the release date."""
590 self._assert_newer_reports_do_not_update_released_on_date(HeartBeat)
591
592 def test_newer_crash_report_does_not_update_released_on_date(self):
593 """Validate that newer crash reports don't update the release date."""
594 self._assert_newer_reports_do_not_update_released_on_date(Crashreport)
595
596 def _assert_manually_changed_released_on_date_is_not_updated(
597 self, report_type, **kwargs
598 ):
599 """Test updating of manually changed released_on dates.
600
601 Validate that a manually changed released_on date is not updated when
602 new reports are sent.
603 """
604 # Create a report
605 device = Dummy.create_dummy_device(user=Dummy.create_dummy_user())
606 report = Dummy.create_dummy_report(report_type, device=device, **kwargs)
607
608 # Run the command to update the database
609 call_command("stats", "update")
610
611 # Get the corresponding version instance from the database
612 version = self.version_class.objects.get(
613 **{self.unique_entry_name: getattr(report, self.unique_entry_name)}
614 )
615
616 # Assert that the released_on date matches the first report date
617 self.assertEqual(version.released_on, report.date.date())
618
619 # Create a second report with a timestamp earlier in time
620 report_2_date = report.date - timedelta(days=1)
621 Dummy.create_dummy_report(
622 report_type, device=device, date=report_2_date, **kwargs
623 )
624
625 # Manually change the released_on date
626 version_release_date = report.date + timedelta(days=1)
627 version.released_on = version_release_date
628 version.save()
629
630 # Run the command to update the database
631 call_command("stats", "update")
632
633 # Get the corresponding version instance from the database
634 version = self.version_class.objects.get(
635 **{self.unique_entry_name: getattr(report, self.unique_entry_name)}
636 )
637
638 # Assert that the released_on date still matches the date is was
639 # manually changed to
640 self.assertEqual(version.released_on, version_release_date.date())
641
642 def test_manually_changed_released_on_date_is_not_updated_by_heartbeat(
643 self
644 ):
645 """Test update of manually changed released_on date with heartbeat."""
646 self._assert_manually_changed_released_on_date_is_not_updated(HeartBeat)
647
648 def test_manually_changed_released_on_date_is_not_updated_by_crash_report(
649 self
650 ):
651 """Test update of manually changed released_on date with crashreport."""
652 self._assert_manually_changed_released_on_date_is_not_updated(
653 Crashreport
654 )
655
656
657# pylint: disable=too-many-ancestors
658class StatsCommandRadioVersionsTestCase(StatsCommandVersionsTestCase):
659 """Test the generation of RadioVersion stats with the stats command."""
660
661 version_class = RadioVersion
662 unique_entry_name = "radio_version"
663 unique_entries = Dummy.RADIO_VERSIONS
664
665
666class CommandDebugOutputTestCase(TestCase):
667 """Test the reset and update commands debug output."""
668
669 # Additional positional arguments to pass to the commands
670 _CMD_ARGS = ["--no-color", "-v 2"]
671
672 # The stats models
673 _STATS_MODELS = [Version, VersionDaily, RadioVersion, RadioVersionDaily]
674 # The models that will generate an output
675 _ALL_MODELS = _STATS_MODELS + [StatsMetadata]
676 _COUNTER_NAMES = ["heartbeats", "crashes", "smpl", "other"]
677 _COUNTER_ACTIONS = ["created", "updated"]
678
679 def _assert_command_output_matches(self, command, number, facts, models):
680 """Validate the debug output of a command.
681
682 The debug output is matched against the facts and models given in
683 the parameters.
684 """
685 buffer = StringIO()
686 call_command("stats", command, *self._CMD_ARGS, stdout=buffer)
687 output = buffer.getvalue().splitlines()
688
689 expected_output = "{number} {model} {fact}"
690 for model in models:
691 for fact in facts:
692 self.assertIn(
693 expected_output.format(
694 number=number, model=model.__name__, fact=fact
695 ),
696 output,
697 )
698
699 def test_reset_command_on_empty_db(self):
700 """Test the reset command on an empty database.
701
702 The reset command should yield nothing on an empty database.
703 """
704 self._assert_command_output_matches(
705 "reset", 0, ["deleted"], self._ALL_MODELS
706 )
707
708 def test_update_command_on_empty_db(self):
709 """Test the update command on an empty database.
710
711 The update command should yield nothing on an empty database.
712 """
713 pattern = "{action} for counter {counter}"
714 facts = [
715 pattern.format(action=counter_action, counter=counter_name)
716 for counter_action in self._COUNTER_ACTIONS
717 for counter_name in self._COUNTER_NAMES
718 ]
719 self._assert_command_output_matches(
720 "update", 0, facts, self._STATS_MODELS
721 )
722
723 def test_reset_command_deletion_of_instances(self):
724 """Test the deletion of stats model instances with the reset command.
725
726 This test validates that model instances get deleted when the
727 reset command is called on a database that only contains a single
728 model instance for each class.
729 """
730 # Create dummy version instances
731 version = Dummy.create_dummy_version()
Mitja Nikolaus35a02652018-10-01 15:10:18 +0200732 radio_version = Dummy.create_dummy_version(RadioVersion)
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200733 Dummy.create_dummy_daily_version(version)
734 Dummy.create_dummy_daily_radio_version(radio_version)
735 Dummy.create_dummy_stats_metadata()
736
737 # We expect that the model instances get deleted
738 self._assert_command_output_matches(
739 "reset", 1, ["deleted"], self._ALL_MODELS
740 )