blob: ddea798e07c356bb6a40a51728795f71fe310139 [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
Mitja Nikolaus03e412b2018-09-18 17:50:15 +02004
5import pytz
6
7from django.core.management import call_command
8from django.test import TestCase
9
10from crashreport_stats.models import (
11 Version,
12 VersionDaily,
13 RadioVersion,
14 RadioVersionDaily,
15 StatsMetadata,
16)
17from crashreport_stats.tests.utils import Dummy
18
19from crashreports.models import Crashreport, HeartBeat
20
21# pylint: disable=too-many-public-methods
22
23
24class StatsCommandVersionsTestCase(TestCase):
25 """Test the generation of Version stats with the stats command."""
26
27 # The class of the version type to be tested
28 version_class = Version
29 # The attribute name characterising the unicity of a stats entry (the
30 # named identifier)
31 unique_entry_name = "build_fingerprint"
32 # The collection of unique entries to post
33 unique_entries = Dummy.BUILD_FINGERPRINTS
34
35 def _create_reports(
36 self, report_type, unique_entry_name, device, number, **kwargs
37 ):
38 # Create reports with distinct timestamps
Mitja Nikolausfd452f82018-11-07 11:53:59 +010039 report_date = datetime.now(pytz.utc)
40 if report_type == HeartBeat:
41 report_date = report_date.date()
Mitja Nikolaus03e412b2018-09-18 17:50:15 +020042 for i in range(number):
Mitja Nikolaus03e412b2018-09-18 17:50:15 +020043 report_attributes = {
44 self.unique_entry_name: unique_entry_name,
45 "device": device,
Mitja Nikolausfd452f82018-11-07 11:53:59 +010046 "date": report_date - timedelta(days=i),
Mitja Nikolaus03e412b2018-09-18 17:50:15 +020047 }
48 report_attributes.update(**kwargs)
49 Dummy.create_dummy_report(report_type, **report_attributes)
50
51 def test_stats_calculation(self):
52 """Test generation of a Version instance."""
53 user = Dummy.create_dummy_user()
54 device = Dummy.create_dummy_device(user=user)
55 heartbeat = Dummy.create_dummy_report(HeartBeat, device=device)
56
57 # Expect that we do not have the Version before updating the stats
58 get_params = {
59 self.unique_entry_name: getattr(heartbeat, self.unique_entry_name)
60 }
61 self.assertRaises(
62 self.version_class.DoesNotExist,
63 self.version_class.objects.get,
64 **get_params
65 )
66
67 # Run the command to update the database
68 call_command("stats", "update")
69
70 # Assume that a corresponding Version instance has been created
71 version = self.version_class.objects.get(**get_params)
72 self.assertIsNotNone(version)
73
74 def _assert_older_report_updates_version_date(self, report_type):
75 """Validate that older reports sent later affect the version date."""
76 user = Dummy.create_dummy_user()
77 device = Dummy.create_dummy_device(user=user)
78 report = Dummy.create_dummy_report(report_type, device=device)
79
80 # Run the command to update the database
81 call_command("stats", "update")
82
83 get_params = {
84 self.unique_entry_name: getattr(report, self.unique_entry_name)
85 }
86 version = self.version_class.objects.get(**get_params)
87
Mitja Nikolausfd452f82018-11-07 11:53:59 +010088 report_date = (
89 report.date.date() if report_type == Crashreport else report.date
90 )
91 self.assertEqual(report_date, version.first_seen_on)
Mitja Nikolaus03e412b2018-09-18 17:50:15 +020092
93 # Create a new report from an earlier point in time
Mitja Nikolausfd452f82018-11-07 11:53:59 +010094 report_date_2 = report.date - timedelta(weeks=1)
Mitja Nikolaus03e412b2018-09-18 17:50:15 +020095 Dummy.create_dummy_report(
Mitja Nikolausfd452f82018-11-07 11:53:59 +010096 report_type, device=device, date=report_date_2
Mitja Nikolaus03e412b2018-09-18 17:50:15 +020097 )
98
99 # Run the command to update the database
100 call_command("stats", "update")
101
102 # Get the same version object from before
103 version = self.version_class.objects.get(**get_params)
104
105 # Validate that the date matches the report recently sent
Mitja Nikolausfd452f82018-11-07 11:53:59 +0100106 if report_type == Crashreport:
107 report_date_2 = report_date_2.date()
108 self.assertEqual(report_date_2, version.first_seen_on)
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200109
110 def test_older_heartbeat_updates_version_date(self):
111 """Validate updating version date with older heartbeats."""
112 self._assert_older_report_updates_version_date(HeartBeat)
113
114 def test_older_crash_report_updates_version_date(self):
115 """Validate updating version date with older crash reports."""
116 self._assert_older_report_updates_version_date(Crashreport)
117
118 def test_entries_are_unique(self):
119 """Validate the entries' unicity and value."""
120 # Create some reports
Mitja Nikolausfd452f82018-11-07 11:53:59 +0100121 for unique_entry, username in zip(self.unique_entries, Dummy.USERNAMES):
122 user = Dummy.create_dummy_user(username=username)
123 device = Dummy.create_dummy_device(user=user)
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200124 self._create_reports(HeartBeat, unique_entry, device, 10)
125
126 # Run the command to update the database
127 call_command("stats", "update")
128
129 # Check whether the correct amount of distinct versions have been
130 # created
131 versions = self.version_class.objects.all()
132 for version in versions:
133 self.assertIn(
134 getattr(version, self.unique_entry_name), self.unique_entries
135 )
136 self.assertEqual(len(versions), len(self.unique_entries))
137
138 def _assert_counter_distribution_is_correct(
139 self, report_type, numbers, counter_attribute_name, **kwargs
140 ):
141 """Validate a counter distribution in the database."""
142 if len(numbers) != len(self.unique_entries):
143 raise ValueError(
144 "The length of the numbers list must match the "
145 "length of self.unique_entries in the test class"
146 "({} != {})".format(len(numbers), len(self.unique_entries))
147 )
148 # Create some reports
Mitja Nikolausfd452f82018-11-07 11:53:59 +0100149 for unique_entry, num, username in zip(
150 self.unique_entries, numbers, Dummy.USERNAMES
151 ):
152 user = Dummy.create_dummy_user(username=username)
153 device = Dummy.create_dummy_device(user=user)
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200154 self._create_reports(
155 report_type, unique_entry, device, num, **kwargs
156 )
157
158 # Run the command to update the database
159 call_command("stats", "update")
160
161 # Check whether the numbers of reports match
162 for version in self.version_class.objects.all():
163 unique_entry_name = getattr(version, self.unique_entry_name)
164 num = numbers[self.unique_entries.index(unique_entry_name)]
165 self.assertEqual(num, getattr(version, counter_attribute_name))
166
167 def test_heartbeats_counter(self):
168 """Test the calculation of the heartbeats counter."""
169 numbers = [10, 7, 8, 5]
170 counter_attribute_name = "heartbeats"
171 self._assert_counter_distribution_is_correct(
172 HeartBeat, numbers, counter_attribute_name
173 )
174
175 def test_crash_reports_counter(self):
176 """Test the calculation of the crashreports counter."""
177 numbers = [2, 5, 0, 3]
178 counter_attribute_name = "prob_crashes"
179 boot_reason_param = {"boot_reason": Crashreport.BOOT_REASON_UNKOWN}
180 self._assert_counter_distribution_is_correct(
181 Crashreport, numbers, counter_attribute_name, **boot_reason_param
182 )
183
184 def test_smpl_reports_counter(self):
185 """Test the calculation of the smpl reports counter."""
186 numbers = [1, 3, 4, 0]
187 counter_attribute_name = "smpl"
188 boot_reason_param = {"boot_reason": Crashreport.BOOT_REASON_RTC_ALARM}
189 self._assert_counter_distribution_is_correct(
190 Crashreport, numbers, counter_attribute_name, **boot_reason_param
191 )
192
193 def test_other_reports_counter(self):
194 """Test the calculation of the other reports counter."""
195 numbers = [0, 2, 1, 2]
196 counter_attribute_name = "other"
197 boot_reason_param = {"boot_reason": "random boot reason"}
198 self._assert_counter_distribution_is_correct(
199 Crashreport, numbers, counter_attribute_name, **boot_reason_param
200 )
201
Mitja Nikolause0df5a32018-09-21 15:59:54 +0200202 def _assert_accumulated_counters_are_correct(
203 self, report_type, counter_attribute_name, **kwargs
204 ):
205 """Validate a counter distribution with reports of different devices."""
206 # Create some devices and corresponding reports
207 devices = [
208 Dummy.create_dummy_device(Dummy.create_dummy_user(username=name))
209 for name in Dummy.USERNAMES
210 ]
211 num_reports = 5
212 for device in devices:
213 self._create_reports(
214 report_type,
215 self.unique_entries[0],
216 device,
217 num_reports,
218 **kwargs
219 )
220
221 # Run the command to update the database
222 call_command("stats", "update")
223
224 # Check whether the numbers of reports match
225 version = self.version_class.objects.get(
226 **{self.unique_entry_name: self.unique_entries[0]}
227 )
228 self.assertEqual(
229 len(Dummy.USERNAMES) * num_reports,
230 getattr(version, counter_attribute_name),
231 )
232
233 def test_accumulated_heartbeats_counter(self):
234 """Test heartbeats counter with reports from different devices."""
235 report_type = HeartBeat
236 counter_attribute_name = "heartbeats"
237 self._assert_accumulated_counters_are_correct(
238 report_type, counter_attribute_name
239 )
240
241 def test_accumulated_crash_reports_counter(self):
242 """Test crash reports counter with reports from different devices."""
243 report_type = Crashreport
244 counter_attribute_name = "prob_crashes"
245 boot_reason_param = {"boot_reason": Crashreport.CRASH_BOOT_REASONS[0]}
246 self._assert_accumulated_counters_are_correct(
247 report_type, counter_attribute_name, **boot_reason_param
248 )
249
250 def test_accumulated_smpl_reports_counter(self):
251 """Test smpl reports counter with reports from different devices."""
252 report_type = Crashreport
253 counter_attribute_name = "smpl"
254 boot_reason_param = {"boot_reason": Crashreport.SMPL_BOOT_REASONS[0]}
255 self._assert_accumulated_counters_are_correct(
256 report_type, counter_attribute_name, **boot_reason_param
257 )
258
259 def test_accumulated_other_reports_counter(self):
260 """Test other reports counter with reports from different devices."""
261 report_type = Crashreport
262 counter_attribute_name = "other"
263 boot_reason_param = {"boot_reason": "random boot reason"}
264 self._assert_accumulated_counters_are_correct(
265 report_type, counter_attribute_name, **boot_reason_param
266 )
267
Mitja Nikolaus42e400f2018-10-01 15:15:04 +0200268 def test_reset_deletion_of_unrelated_version(self):
269 """Test delete functionality of the reset command."""
270 # Create a version instance that is not related to any reports
271 Dummy.create_dummy_version(
272 self.version_class,
273 **{self.unique_entry_name: self.unique_entries[0]}
274 )
275
276 # Run the command to reset the database
277 call_command("stats", "reset")
278
279 # Check whether the unrelated version instance has been deleted
280 self.assertFalse(
281 self.version_class.objects.filter(
282 **{self.unique_entry_name: self.unique_entries[0]}
283 ).exists()
284 )
285
286 def _assert_reset_updates_counter(
287 self, report_type, counter_attribute_name, **kwargs
288 ):
289 # Create a device and corresponding reports
290 device = Dummy.create_dummy_device(Dummy.create_dummy_user())
291 num_reports = 5
292 self._create_reports(
293 report_type, self.unique_entries[0], device, num_reports, **kwargs
294 )
295
296 # Create a version instance with wrong numbers
297 wrong_num_of_reports = 4
298 Dummy.create_dummy_version(
299 self.version_class,
300 **{
301 self.unique_entry_name: self.unique_entries[0],
302 counter_attribute_name: wrong_num_of_reports,
303 }
304 )
305
306 # Run the command to reset the database
307 call_command("stats", "reset")
308
309 # Check whether the numbers of reports do match
310 version = self.version_class.objects.get(
311 **{self.unique_entry_name: self.unique_entries[0]}
312 )
313 self.assertEqual(num_reports, getattr(version, counter_attribute_name))
314
315 def test_reset_update_heartbeat_counter(self):
316 """Test update of the heartbeat counter using the reset command."""
317 self._assert_reset_updates_counter(HeartBeat, "heartbeats")
318
319 def test_reset_update_crash_report_counter(self):
320 """Test update of the crash report counter using the reset command."""
321 boot_reason_param = {"boot_reason": Crashreport.CRASH_BOOT_REASONS[0]}
322 self._assert_reset_updates_counter(
323 Crashreport, "prob_crashes", **boot_reason_param
324 )
325
326 def test_reset_update_smpl_report_counter(self):
327 """Test update of the smpl report counter using the reset command."""
328 boot_reason_param = {"boot_reason": Crashreport.SMPL_BOOT_REASONS[0]}
329 self._assert_reset_updates_counter(
330 Crashreport, "smpl", **boot_reason_param
331 )
332
333 def test_reset_update_other_report_counter(self):
334 """Test update of the other report counter using the reset command."""
335 boot_reason_param = {"boot_reason": "random boot reason"}
336 self._assert_reset_updates_counter(
337 Crashreport, "other", **boot_reason_param
338 )
339
Mitja Nikolaus0ff39472018-09-24 15:00:04 +0200340 def _assert_updating_twice_gives_correct_counters(
341 self, report_type, counter_attribute_name, **boot_reason_param
342 ):
Mitja Nikolausfd452f82018-11-07 11:53:59 +0100343 # Create a two devices and a corresponding reports for 2 different
344 # versions
345 device_1 = Dummy.create_dummy_device(Dummy.create_dummy_user())
Mitja Nikolaus0ff39472018-09-24 15:00:04 +0200346 num_reports = 5
347 self._create_reports(
348 report_type,
349 self.unique_entries[0],
Mitja Nikolausfd452f82018-11-07 11:53:59 +0100350 device_1,
Mitja Nikolaus0ff39472018-09-24 15:00:04 +0200351 num_reports,
352 **boot_reason_param
353 )
Mitja Nikolausfd452f82018-11-07 11:53:59 +0100354 device_2 = Dummy.create_dummy_device(
355 Dummy.create_dummy_user(username=Dummy.USERNAMES[1])
356 )
Mitja Nikolaus0ff39472018-09-24 15:00:04 +0200357 self._create_reports(
358 report_type,
359 self.unique_entries[1],
Mitja Nikolausfd452f82018-11-07 11:53:59 +0100360 device_2,
Mitja Nikolaus0ff39472018-09-24 15:00:04 +0200361 num_reports,
362 **boot_reason_param
363 )
364
365 # Run the command to update the database
366 call_command("stats", "update")
367
368 # Check whether the numbers of reports match for both versions
369 version_1 = self.version_class.objects.get(
370 **{self.unique_entry_name: self.unique_entries[0]}
371 )
372 self.assertEqual(
373 num_reports, getattr(version_1, counter_attribute_name)
374 )
375 version_2 = self.version_class.objects.get(
376 **{self.unique_entry_name: self.unique_entries[1]}
377 )
378 self.assertEqual(
379 num_reports, getattr(version_2, counter_attribute_name)
380 )
381
382 # Create another report for the first version
383 report_new_attributes = {
384 self.unique_entry_name: self.unique_entries[0],
Mitja Nikolausfd452f82018-11-07 11:53:59 +0100385 "device": device_1,
Mitja Nikolaus0ff39472018-09-24 15:00:04 +0200386 **boot_reason_param,
387 }
388 Dummy.create_dummy_report(report_type, **report_new_attributes)
389
390 # Run the command to update the database again
391 call_command("stats", "update")
392
393 # Check whether the numbers of reports match:
394 # Assert that the first version's counter is increased by one
395 version_1 = self.version_class.objects.get(
396 **{self.unique_entry_name: self.unique_entries[0]}
397 )
398 self.assertEqual(
399 num_reports + 1, getattr(version_1, counter_attribute_name)
400 )
401 # Assert that the second versions counter is unchanged
402 version_2 = self.version_class.objects.get(
403 **{self.unique_entry_name: self.unique_entries[1]}
404 )
405 self.assertEqual(
406 num_reports, getattr(version_2, counter_attribute_name)
407 )
408
409 def test_updating_heartbeats_counter_twice(self):
410 """Test updating the heartbeats counter twice."""
411 self._assert_updating_twice_gives_correct_counters(
412 HeartBeat, "heartbeats"
413 )
414
415 def test_updating_crash_reports_counter_twice(self):
416 """Test updating the crash reports counter twice."""
417 boot_reason_param = {"boot_reason": Crashreport.CRASH_BOOT_REASONS[0]}
418 self._assert_updating_twice_gives_correct_counters(
419 Crashreport, "prob_crashes", **boot_reason_param
420 )
421
422 def test_updating_smpl_reports_counter_twice(self):
423 """Test updating the smpl reports counter twice."""
424 boot_reason_param = {"boot_reason": Crashreport.SMPL_BOOT_REASONS[0]}
425 self._assert_updating_twice_gives_correct_counters(
426 Crashreport, "smpl", **boot_reason_param
427 )
428
429 def test_updating_other_reports_counter_twice(self):
430 """Test updating the other reports counter twice."""
431 boot_reason_param = {"boot_reason": "random boot reason"}
432 self._assert_updating_twice_gives_correct_counters(
433 Crashreport, "other", **boot_reason_param
434 )
435
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200436 def _assert_reports_with_same_timestamp_are_counted(
Mitja Nikolausb4ba8002018-11-07 11:57:02 +0100437 self,
438 report_type,
439 counter_attribute_name,
440 username_1=Dummy.USERNAMES[0],
441 username_2=Dummy.USERNAMES[1],
442 **kwargs
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200443 ):
444 """Validate that reports with the same timestamp are counted.
445
446 Reports from different devices but the same timestamp should be
447 counted as independent reports.
448 """
449 # Create a report
Mitja Nikolausb4ba8002018-11-07 11:57:02 +0100450 device1 = Dummy.create_dummy_device(
451 user=Dummy.create_dummy_user(username=username_1)
452 )
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200453 report1 = Dummy.create_dummy_report(
454 report_type, device=device1, **kwargs
455 )
456
457 # Create a second report with the same timestamp but from another device
458 device2 = Dummy.create_dummy_device(
Mitja Nikolausb4ba8002018-11-07 11:57:02 +0100459 user=Dummy.create_dummy_user(username=username_2)
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200460 )
461 Dummy.create_dummy_report(
462 report_type, device=device2, date=report1.date, **kwargs
463 )
464
465 # Run the command to update the database
466 call_command("stats", "update")
467
468 # Get the corresponding version instance from the database
469 get_params = {
470 self.unique_entry_name: getattr(report1, self.unique_entry_name)
471 }
472 version = self.version_class.objects.get(**get_params)
473
474 # Assert that both reports are counted
475 self.assertEqual(getattr(version, counter_attribute_name), 2)
476
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200477 def test_heartbeats_with_same_timestamp_are_counted(self):
478 """Validate that heartbeats with same timestamp are counted."""
479 counter_attribute_name = "heartbeats"
480 self._assert_reports_with_same_timestamp_are_counted(
481 HeartBeat, counter_attribute_name
482 )
483
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200484 def test_crash_reports_with_same_timestamp_are_counted(self):
Mitja Nikolausb4ba8002018-11-07 11:57:02 +0100485 """Validate that crash reports with same timestamp are counted."""
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200486 counter_attribute_name = "prob_crashes"
Mitja Nikolausb4ba8002018-11-07 11:57:02 +0100487 for i, (unique_entry, boot_reason) in enumerate(
488 zip(self.unique_entries, Crashreport.CRASH_BOOT_REASONS)
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200489 ):
490 params = {
491 "boot_reason": boot_reason,
492 self.unique_entry_name: unique_entry,
493 }
494 self._assert_reports_with_same_timestamp_are_counted(
Mitja Nikolausb4ba8002018-11-07 11:57:02 +0100495 Crashreport,
496 counter_attribute_name,
497 Dummy.USERNAMES[2 * i],
498 Dummy.USERNAMES[2 * i + 1],
499 **params
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200500 )
501
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200502 def test_smpl_reports_with_same_timestamp_are_counted(self):
Mitja Nikolausb4ba8002018-11-07 11:57:02 +0100503 """Validate that smpl reports with same timestamp are counted."""
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200504 counter_attribute_name = "smpl"
Mitja Nikolausb4ba8002018-11-07 11:57:02 +0100505 for i, (unique_entry, boot_reason) in enumerate(
506 zip(self.unique_entries, Crashreport.SMPL_BOOT_REASONS)
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200507 ):
508 params = {
509 "boot_reason": boot_reason,
510 self.unique_entry_name: unique_entry,
511 }
512 self._assert_reports_with_same_timestamp_are_counted(
Mitja Nikolausb4ba8002018-11-07 11:57:02 +0100513 Crashreport,
514 counter_attribute_name,
515 Dummy.USERNAMES[2 * i],
516 Dummy.USERNAMES[2 * i + 1],
517 **params
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200518 )
519
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200520 def test_other_reports_with_same_timestamp_are_counted(self):
Mitja Nikolausb4ba8002018-11-07 11:57:02 +0100521 """Validate that other reports with same timestamp are counted."""
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200522 counter_attribute_name = "other"
523 params = {"boot_reason": "random boot reason"}
524 self._assert_reports_with_same_timestamp_are_counted(
525 Crashreport, counter_attribute_name, **params
526 )
527
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200528 def _assert_older_reports_update_released_on_date(
529 self, report_type, **kwargs
530 ):
531 """Test updating of the released_on date.
532
533 Validate that the released_on date is updated once an older report is
534 sent.
535 """
536 # Create a report
537 device = Dummy.create_dummy_device(user=Dummy.create_dummy_user())
538 report = Dummy.create_dummy_report(report_type, device=device, **kwargs)
539
540 # Run the command to update the database
541 call_command("stats", "update")
542
543 # Get the corresponding version instance from the database
544 version = self.version_class.objects.get(
545 **{self.unique_entry_name: getattr(report, self.unique_entry_name)}
546 )
547
548 # Assert that the released_on date matches the first report date
Mitja Nikolausfd452f82018-11-07 11:53:59 +0100549 report_date = (
550 report.date.date() if report_type == Crashreport else report.date
551 )
552 self.assertEqual(version.released_on, report_date)
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200553
554 # Create a second report with the a timestamp earlier in time
555 report_2_date = report.date - timedelta(days=1)
556 Dummy.create_dummy_report(
557 report_type, device=device, date=report_2_date, **kwargs
558 )
559
560 # Run the command to update the database
561 call_command("stats", "update")
562
563 # Get the corresponding version instance from the database
564 version = self.version_class.objects.get(
565 **{self.unique_entry_name: getattr(report, self.unique_entry_name)}
566 )
567
568 # Assert that the released_on date matches the older report date
Mitja Nikolausfd452f82018-11-07 11:53:59 +0100569 if report_type == Crashreport:
570 report_2_date = report_2_date.date()
571 self.assertEqual(version.released_on, report_2_date)
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200572
573 def _assert_newer_reports_do_not_update_released_on_date(
574 self, report_type, **kwargs
575 ):
576 """Test updating of the released_on date.
577
578 Validate that the released_on date is not updated once a newer report is
579 sent.
580 """
581 # Create a report
582 device = Dummy.create_dummy_device(user=Dummy.create_dummy_user())
583 report = Dummy.create_dummy_report(report_type, device=device, **kwargs)
Mitja Nikolausfd452f82018-11-07 11:53:59 +0100584 report_1_date = (
585 report.date.date() if report_type == Crashreport else report.date
586 )
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200587
588 # Run the command to update the database
589 call_command("stats", "update")
590
591 # Get the corresponding version instance from the database
592 version = self.version_class.objects.get(
593 **{self.unique_entry_name: getattr(report, self.unique_entry_name)}
594 )
595
596 # Assert that the released_on date matches the first report date
597 self.assertEqual(version.released_on, report_1_date)
598
599 # Create a second report with the a timestamp later in time
600 report_2_date = report.date + timedelta(days=1)
601 Dummy.create_dummy_report(
602 report_type, device=device, date=report_2_date, **kwargs
603 )
604
605 # Run the command to update the database
606 call_command("stats", "update")
607
608 # Get the corresponding version instance from the database
609 version = self.version_class.objects.get(
610 **{self.unique_entry_name: getattr(report, self.unique_entry_name)}
611 )
612
613 # Assert that the released_on date matches the older report date
614 self.assertEqual(version.released_on, report_1_date)
615
616 def test_older_heartbeat_updates_released_on_date(self):
617 """Validate that older heartbeats update the release date."""
618 self._assert_older_reports_update_released_on_date(HeartBeat)
619
620 def test_older_crash_report_updates_released_on_date(self):
621 """Validate that older crash reports update the release date."""
622 self._assert_older_reports_update_released_on_date(Crashreport)
623
624 def test_newer_heartbeat_does_not_update_released_on_date(self):
625 """Validate that newer heartbeats don't update the release date."""
626 self._assert_newer_reports_do_not_update_released_on_date(HeartBeat)
627
628 def test_newer_crash_report_does_not_update_released_on_date(self):
629 """Validate that newer crash reports don't update the release date."""
630 self._assert_newer_reports_do_not_update_released_on_date(Crashreport)
631
632 def _assert_manually_changed_released_on_date_is_not_updated(
633 self, report_type, **kwargs
634 ):
635 """Test updating of manually changed released_on dates.
636
637 Validate that a manually changed released_on date is not updated when
638 new reports are sent.
639 """
640 # Create a report
641 device = Dummy.create_dummy_device(user=Dummy.create_dummy_user())
642 report = Dummy.create_dummy_report(report_type, device=device, **kwargs)
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
Mitja Nikolausfd452f82018-11-07 11:53:59 +0100653 report_date = (
654 report.date.date() if report_type == Crashreport else report.date
655 )
656 self.assertEqual(version.released_on, report_date)
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200657
658 # Create a second report with a timestamp earlier in time
659 report_2_date = report.date - timedelta(days=1)
660 Dummy.create_dummy_report(
661 report_type, device=device, date=report_2_date, **kwargs
662 )
663
664 # Manually change the released_on date
Mitja Nikolausfd452f82018-11-07 11:53:59 +0100665 version_release_date = report_date + timedelta(days=1)
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200666 version.released_on = version_release_date
667 version.save()
668
669 # Run the command to update the database
670 call_command("stats", "update")
671
672 # Get the corresponding version instance from the database
673 version = self.version_class.objects.get(
674 **{self.unique_entry_name: getattr(report, self.unique_entry_name)}
675 )
676
677 # Assert that the released_on date still matches the date is was
678 # manually changed to
Mitja Nikolausfd452f82018-11-07 11:53:59 +0100679 self.assertEqual(version.released_on, version_release_date)
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200680
681 def test_manually_changed_released_on_date_is_not_updated_by_heartbeat(
682 self
683 ):
684 """Test update of manually changed released_on date with heartbeat."""
685 self._assert_manually_changed_released_on_date_is_not_updated(HeartBeat)
686
687 def test_manually_changed_released_on_date_is_not_updated_by_crash_report(
688 self
689 ):
690 """Test update of manually changed released_on date with crashreport."""
691 self._assert_manually_changed_released_on_date_is_not_updated(
692 Crashreport
693 )
694
695
696# pylint: disable=too-many-ancestors
697class StatsCommandRadioVersionsTestCase(StatsCommandVersionsTestCase):
698 """Test the generation of RadioVersion stats with the stats command."""
699
700 version_class = RadioVersion
701 unique_entry_name = "radio_version"
702 unique_entries = Dummy.RADIO_VERSIONS
703
704
705class CommandDebugOutputTestCase(TestCase):
706 """Test the reset and update commands debug output."""
707
708 # Additional positional arguments to pass to the commands
709 _CMD_ARGS = ["--no-color", "-v 2"]
710
711 # The stats models
712 _STATS_MODELS = [Version, VersionDaily, RadioVersion, RadioVersionDaily]
713 # The models that will generate an output
714 _ALL_MODELS = _STATS_MODELS + [StatsMetadata]
715 _COUNTER_NAMES = ["heartbeats", "crashes", "smpl", "other"]
716 _COUNTER_ACTIONS = ["created", "updated"]
717
718 def _assert_command_output_matches(self, command, number, facts, models):
719 """Validate the debug output of a command.
720
721 The debug output is matched against the facts and models given in
722 the parameters.
723 """
724 buffer = StringIO()
725 call_command("stats", command, *self._CMD_ARGS, stdout=buffer)
726 output = buffer.getvalue().splitlines()
727
728 expected_output = "{number} {model} {fact}"
729 for model in models:
730 for fact in facts:
731 self.assertIn(
732 expected_output.format(
733 number=number, model=model.__name__, fact=fact
734 ),
735 output,
736 )
737
738 def test_reset_command_on_empty_db(self):
739 """Test the reset command on an empty database.
740
741 The reset command should yield nothing on an empty database.
742 """
743 self._assert_command_output_matches(
744 "reset", 0, ["deleted"], self._ALL_MODELS
745 )
746
747 def test_update_command_on_empty_db(self):
748 """Test the update command on an empty database.
749
750 The update command should yield nothing on an empty database.
751 """
752 pattern = "{action} for counter {counter}"
753 facts = [
754 pattern.format(action=counter_action, counter=counter_name)
755 for counter_action in self._COUNTER_ACTIONS
756 for counter_name in self._COUNTER_NAMES
757 ]
758 self._assert_command_output_matches(
759 "update", 0, facts, self._STATS_MODELS
760 )
761
762 def test_reset_command_deletion_of_instances(self):
763 """Test the deletion of stats model instances with the reset command.
764
765 This test validates that model instances get deleted when the
766 reset command is called on a database that only contains a single
767 model instance for each class.
768 """
769 # Create dummy version instances
770 version = Dummy.create_dummy_version()
Mitja Nikolaus35a02652018-10-01 15:10:18 +0200771 radio_version = Dummy.create_dummy_version(RadioVersion)
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200772 Dummy.create_dummy_daily_version(version)
773 Dummy.create_dummy_daily_radio_version(radio_version)
774 Dummy.create_dummy_stats_metadata()
775
776 # We expect that the model instances get deleted
777 self._assert_command_output_matches(
778 "reset", 1, ["deleted"], self._ALL_MODELS
779 )