blob: 6409b558dfb17163e6466e99361fd5a7d439523b [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)
Mitja Nikolaus77dd5652018-12-06 11:27:01 +010049 Dummy.create_report(report_type, **report_attributes)
Mitja Nikolaus03e412b2018-09-18 17:50:15 +020050
51 def test_stats_calculation(self):
52 """Test generation of a Version instance."""
Mitja Nikolaus77dd5652018-12-06 11:27:01 +010053 user = Dummy.create_user()
54 device = Dummy.create_device(user=user)
55 heartbeat = Dummy.create_report(HeartBeat, device=device)
Mitja Nikolaus03e412b2018-09-18 17:50:15 +020056
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."""
Mitja Nikolaus77dd5652018-12-06 11:27:01 +010076 user = Dummy.create_user()
77 device = Dummy.create_device(user=user)
78 report = Dummy.create_report(report_type, device=device)
Mitja Nikolaus03e412b2018-09-18 17:50:15 +020079
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 Nikolaus77dd5652018-12-06 11:27:01 +010095 Dummy.create_report(report_type, device=device, date=report_date_2)
Mitja Nikolaus03e412b2018-09-18 17:50:15 +020096
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
Mitja Nikolausfd452f82018-11-07 11:53:59 +0100104 if report_type == Crashreport:
105 report_date_2 = report_date_2.date()
106 self.assertEqual(report_date_2, version.first_seen_on)
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200107
108 def test_older_heartbeat_updates_version_date(self):
109 """Validate updating version date with older heartbeats."""
110 self._assert_older_report_updates_version_date(HeartBeat)
111
112 def test_older_crash_report_updates_version_date(self):
113 """Validate updating version date with older crash reports."""
114 self._assert_older_report_updates_version_date(Crashreport)
115
116 def test_entries_are_unique(self):
117 """Validate the entries' unicity and value."""
118 # Create some reports
Mitja Nikolausfd452f82018-11-07 11:53:59 +0100119 for unique_entry, username in zip(self.unique_entries, Dummy.USERNAMES):
Mitja Nikolaus77dd5652018-12-06 11:27:01 +0100120 user = Dummy.create_user(username=username)
121 device = Dummy.create_device(user=user)
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200122 self._create_reports(HeartBeat, unique_entry, device, 10)
123
124 # Run the command to update the database
125 call_command("stats", "update")
126
127 # Check whether the correct amount of distinct versions have been
128 # created
129 versions = self.version_class.objects.all()
130 for version in versions:
131 self.assertIn(
132 getattr(version, self.unique_entry_name), self.unique_entries
133 )
134 self.assertEqual(len(versions), len(self.unique_entries))
135
136 def _assert_counter_distribution_is_correct(
137 self, report_type, numbers, counter_attribute_name, **kwargs
138 ):
139 """Validate a counter distribution in the database."""
140 if len(numbers) != len(self.unique_entries):
141 raise ValueError(
142 "The length of the numbers list must match the "
143 "length of self.unique_entries in the test class"
144 "({} != {})".format(len(numbers), len(self.unique_entries))
145 )
146 # Create some reports
Mitja Nikolausfd452f82018-11-07 11:53:59 +0100147 for unique_entry, num, username in zip(
148 self.unique_entries, numbers, Dummy.USERNAMES
149 ):
Mitja Nikolaus77dd5652018-12-06 11:27:01 +0100150 user = Dummy.create_user(username=username)
151 device = Dummy.create_device(user=user)
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200152 self._create_reports(
153 report_type, unique_entry, device, num, **kwargs
154 )
155
156 # Run the command to update the database
157 call_command("stats", "update")
158
159 # Check whether the numbers of reports match
160 for version in self.version_class.objects.all():
161 unique_entry_name = getattr(version, self.unique_entry_name)
162 num = numbers[self.unique_entries.index(unique_entry_name)]
163 self.assertEqual(num, getattr(version, counter_attribute_name))
164
165 def test_heartbeats_counter(self):
166 """Test the calculation of the heartbeats counter."""
167 numbers = [10, 7, 8, 5]
168 counter_attribute_name = "heartbeats"
169 self._assert_counter_distribution_is_correct(
170 HeartBeat, numbers, counter_attribute_name
171 )
172
173 def test_crash_reports_counter(self):
174 """Test the calculation of the crashreports counter."""
175 numbers = [2, 5, 0, 3]
176 counter_attribute_name = "prob_crashes"
177 boot_reason_param = {"boot_reason": Crashreport.BOOT_REASON_UNKOWN}
178 self._assert_counter_distribution_is_correct(
179 Crashreport, numbers, counter_attribute_name, **boot_reason_param
180 )
181
182 def test_smpl_reports_counter(self):
183 """Test the calculation of the smpl reports counter."""
184 numbers = [1, 3, 4, 0]
185 counter_attribute_name = "smpl"
186 boot_reason_param = {"boot_reason": Crashreport.BOOT_REASON_RTC_ALARM}
187 self._assert_counter_distribution_is_correct(
188 Crashreport, numbers, counter_attribute_name, **boot_reason_param
189 )
190
191 def test_other_reports_counter(self):
192 """Test the calculation of the other reports counter."""
193 numbers = [0, 2, 1, 2]
194 counter_attribute_name = "other"
195 boot_reason_param = {"boot_reason": "random boot reason"}
196 self._assert_counter_distribution_is_correct(
197 Crashreport, numbers, counter_attribute_name, **boot_reason_param
198 )
199
Mitja Nikolause0df5a32018-09-21 15:59:54 +0200200 def _assert_accumulated_counters_are_correct(
201 self, report_type, counter_attribute_name, **kwargs
202 ):
203 """Validate a counter distribution with reports of different devices."""
204 # Create some devices and corresponding reports
205 devices = [
Mitja Nikolaus77dd5652018-12-06 11:27:01 +0100206 Dummy.create_device(Dummy.create_user(username=name))
Mitja Nikolause0df5a32018-09-21 15:59:54 +0200207 for name in Dummy.USERNAMES
208 ]
209 num_reports = 5
210 for device in devices:
211 self._create_reports(
212 report_type,
213 self.unique_entries[0],
214 device,
215 num_reports,
216 **kwargs
217 )
218
219 # Run the command to update the database
220 call_command("stats", "update")
221
222 # Check whether the numbers of reports match
223 version = self.version_class.objects.get(
224 **{self.unique_entry_name: self.unique_entries[0]}
225 )
226 self.assertEqual(
227 len(Dummy.USERNAMES) * num_reports,
228 getattr(version, counter_attribute_name),
229 )
230
231 def test_accumulated_heartbeats_counter(self):
232 """Test heartbeats counter with reports from different devices."""
233 report_type = HeartBeat
234 counter_attribute_name = "heartbeats"
235 self._assert_accumulated_counters_are_correct(
236 report_type, counter_attribute_name
237 )
238
239 def test_accumulated_crash_reports_counter(self):
240 """Test crash reports counter with reports from different devices."""
241 report_type = Crashreport
242 counter_attribute_name = "prob_crashes"
243 boot_reason_param = {"boot_reason": Crashreport.CRASH_BOOT_REASONS[0]}
244 self._assert_accumulated_counters_are_correct(
245 report_type, counter_attribute_name, **boot_reason_param
246 )
247
248 def test_accumulated_smpl_reports_counter(self):
249 """Test smpl reports counter with reports from different devices."""
250 report_type = Crashreport
251 counter_attribute_name = "smpl"
252 boot_reason_param = {"boot_reason": Crashreport.SMPL_BOOT_REASONS[0]}
253 self._assert_accumulated_counters_are_correct(
254 report_type, counter_attribute_name, **boot_reason_param
255 )
256
257 def test_accumulated_other_reports_counter(self):
258 """Test other reports counter with reports from different devices."""
259 report_type = Crashreport
260 counter_attribute_name = "other"
261 boot_reason_param = {"boot_reason": "random boot reason"}
262 self._assert_accumulated_counters_are_correct(
263 report_type, counter_attribute_name, **boot_reason_param
264 )
265
Mitja Nikolaus42e400f2018-10-01 15:15:04 +0200266 def test_reset_deletion_of_unrelated_version(self):
267 """Test delete functionality of the reset command."""
268 # Create a version instance that is not related to any reports
Mitja Nikolaus77dd5652018-12-06 11:27:01 +0100269 Dummy.create_version(
Mitja Nikolaus42e400f2018-10-01 15:15:04 +0200270 self.version_class,
271 **{self.unique_entry_name: self.unique_entries[0]}
272 )
273
274 # Run the command to reset the database
275 call_command("stats", "reset")
276
277 # Check whether the unrelated version instance has been deleted
278 self.assertFalse(
279 self.version_class.objects.filter(
280 **{self.unique_entry_name: self.unique_entries[0]}
281 ).exists()
282 )
283
284 def _assert_reset_updates_counter(
285 self, report_type, counter_attribute_name, **kwargs
286 ):
287 # Create a device and corresponding reports
Mitja Nikolaus77dd5652018-12-06 11:27:01 +0100288 device = Dummy.create_device(Dummy.create_user())
Mitja Nikolaus42e400f2018-10-01 15:15:04 +0200289 num_reports = 5
290 self._create_reports(
291 report_type, self.unique_entries[0], device, num_reports, **kwargs
292 )
293
294 # Create a version instance with wrong numbers
295 wrong_num_of_reports = 4
Mitja Nikolaus77dd5652018-12-06 11:27:01 +0100296 Dummy.create_version(
Mitja Nikolaus42e400f2018-10-01 15:15:04 +0200297 self.version_class,
298 **{
299 self.unique_entry_name: self.unique_entries[0],
300 counter_attribute_name: wrong_num_of_reports,
301 }
302 )
303
304 # Run the command to reset the database
305 call_command("stats", "reset")
306
307 # Check whether the numbers of reports do match
308 version = self.version_class.objects.get(
309 **{self.unique_entry_name: self.unique_entries[0]}
310 )
311 self.assertEqual(num_reports, getattr(version, counter_attribute_name))
312
313 def test_reset_update_heartbeat_counter(self):
314 """Test update of the heartbeat counter using the reset command."""
315 self._assert_reset_updates_counter(HeartBeat, "heartbeats")
316
317 def test_reset_update_crash_report_counter(self):
318 """Test update of the crash report counter using the reset command."""
319 boot_reason_param = {"boot_reason": Crashreport.CRASH_BOOT_REASONS[0]}
320 self._assert_reset_updates_counter(
321 Crashreport, "prob_crashes", **boot_reason_param
322 )
323
324 def test_reset_update_smpl_report_counter(self):
325 """Test update of the smpl report counter using the reset command."""
326 boot_reason_param = {"boot_reason": Crashreport.SMPL_BOOT_REASONS[0]}
327 self._assert_reset_updates_counter(
328 Crashreport, "smpl", **boot_reason_param
329 )
330
331 def test_reset_update_other_report_counter(self):
332 """Test update of the other report counter using the reset command."""
333 boot_reason_param = {"boot_reason": "random boot reason"}
334 self._assert_reset_updates_counter(
335 Crashreport, "other", **boot_reason_param
336 )
337
Mitja Nikolaus0ff39472018-09-24 15:00:04 +0200338 def _assert_updating_twice_gives_correct_counters(
339 self, report_type, counter_attribute_name, **boot_reason_param
340 ):
Mitja Nikolausfd452f82018-11-07 11:53:59 +0100341 # Create a two devices and a corresponding reports for 2 different
342 # versions
Mitja Nikolaus77dd5652018-12-06 11:27:01 +0100343 device_1 = Dummy.create_device(Dummy.create_user())
Mitja Nikolaus0ff39472018-09-24 15:00:04 +0200344 num_reports = 5
345 self._create_reports(
346 report_type,
347 self.unique_entries[0],
Mitja Nikolausfd452f82018-11-07 11:53:59 +0100348 device_1,
Mitja Nikolaus0ff39472018-09-24 15:00:04 +0200349 num_reports,
350 **boot_reason_param
351 )
Mitja Nikolaus77dd5652018-12-06 11:27:01 +0100352 device_2 = Dummy.create_device(
353 Dummy.create_user(username=Dummy.USERNAMES[1])
Mitja Nikolausfd452f82018-11-07 11:53:59 +0100354 )
Mitja Nikolaus0ff39472018-09-24 15:00:04 +0200355 self._create_reports(
356 report_type,
357 self.unique_entries[1],
Mitja Nikolausfd452f82018-11-07 11:53:59 +0100358 device_2,
Mitja Nikolaus0ff39472018-09-24 15:00:04 +0200359 num_reports,
360 **boot_reason_param
361 )
362
363 # Run the command to update the database
364 call_command("stats", "update")
365
366 # Check whether the numbers of reports match for both versions
367 version_1 = self.version_class.objects.get(
368 **{self.unique_entry_name: self.unique_entries[0]}
369 )
370 self.assertEqual(
371 num_reports, getattr(version_1, counter_attribute_name)
372 )
373 version_2 = self.version_class.objects.get(
374 **{self.unique_entry_name: self.unique_entries[1]}
375 )
376 self.assertEqual(
377 num_reports, getattr(version_2, counter_attribute_name)
378 )
379
380 # Create another report for the first version
381 report_new_attributes = {
382 self.unique_entry_name: self.unique_entries[0],
Mitja Nikolausfd452f82018-11-07 11:53:59 +0100383 "device": device_1,
Mitja Nikolaus0ff39472018-09-24 15:00:04 +0200384 **boot_reason_param,
385 }
Mitja Nikolaus77dd5652018-12-06 11:27:01 +0100386 Dummy.create_report(report_type, **report_new_attributes)
Mitja Nikolaus0ff39472018-09-24 15:00:04 +0200387
388 # Run the command to update the database again
389 call_command("stats", "update")
390
391 # Check whether the numbers of reports match:
392 # Assert that the first version's counter is increased by one
393 version_1 = self.version_class.objects.get(
394 **{self.unique_entry_name: self.unique_entries[0]}
395 )
396 self.assertEqual(
397 num_reports + 1, getattr(version_1, counter_attribute_name)
398 )
399 # Assert that the second versions counter is unchanged
400 version_2 = self.version_class.objects.get(
401 **{self.unique_entry_name: self.unique_entries[1]}
402 )
403 self.assertEqual(
404 num_reports, getattr(version_2, counter_attribute_name)
405 )
406
407 def test_updating_heartbeats_counter_twice(self):
408 """Test updating the heartbeats counter twice."""
409 self._assert_updating_twice_gives_correct_counters(
410 HeartBeat, "heartbeats"
411 )
412
413 def test_updating_crash_reports_counter_twice(self):
414 """Test updating the crash reports counter twice."""
415 boot_reason_param = {"boot_reason": Crashreport.CRASH_BOOT_REASONS[0]}
416 self._assert_updating_twice_gives_correct_counters(
417 Crashreport, "prob_crashes", **boot_reason_param
418 )
419
420 def test_updating_smpl_reports_counter_twice(self):
421 """Test updating the smpl reports counter twice."""
422 boot_reason_param = {"boot_reason": Crashreport.SMPL_BOOT_REASONS[0]}
423 self._assert_updating_twice_gives_correct_counters(
424 Crashreport, "smpl", **boot_reason_param
425 )
426
427 def test_updating_other_reports_counter_twice(self):
428 """Test updating the other reports counter twice."""
429 boot_reason_param = {"boot_reason": "random boot reason"}
430 self._assert_updating_twice_gives_correct_counters(
431 Crashreport, "other", **boot_reason_param
432 )
433
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200434 def _assert_reports_with_same_timestamp_are_counted(
Mitja Nikolausb4ba8002018-11-07 11:57:02 +0100435 self,
436 report_type,
437 counter_attribute_name,
438 username_1=Dummy.USERNAMES[0],
439 username_2=Dummy.USERNAMES[1],
440 **kwargs
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200441 ):
442 """Validate that reports with the same timestamp are counted.
443
444 Reports from different devices but the same timestamp should be
445 counted as independent reports.
446 """
447 # Create a report
Mitja Nikolaus77dd5652018-12-06 11:27:01 +0100448 device1 = Dummy.create_device(
449 user=Dummy.create_user(username=username_1)
Mitja Nikolausb4ba8002018-11-07 11:57:02 +0100450 )
Mitja Nikolaus77dd5652018-12-06 11:27:01 +0100451 report1 = Dummy.create_report(report_type, device=device1, **kwargs)
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200452
453 # Create a second report with the same timestamp but from another device
Mitja Nikolaus77dd5652018-12-06 11:27:01 +0100454 device2 = Dummy.create_device(
455 user=Dummy.create_user(username=username_2)
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200456 )
Mitja Nikolaus77dd5652018-12-06 11:27:01 +0100457 Dummy.create_report(
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200458 report_type, device=device2, date=report1.date, **kwargs
459 )
460
461 # Run the command to update the database
462 call_command("stats", "update")
463
464 # Get the corresponding version instance from the database
465 get_params = {
466 self.unique_entry_name: getattr(report1, self.unique_entry_name)
467 }
468 version = self.version_class.objects.get(**get_params)
469
470 # Assert that both reports are counted
471 self.assertEqual(getattr(version, counter_attribute_name), 2)
472
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200473 def test_heartbeats_with_same_timestamp_are_counted(self):
474 """Validate that heartbeats with same timestamp are counted."""
475 counter_attribute_name = "heartbeats"
476 self._assert_reports_with_same_timestamp_are_counted(
477 HeartBeat, counter_attribute_name
478 )
479
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200480 def test_crash_reports_with_same_timestamp_are_counted(self):
Mitja Nikolausb4ba8002018-11-07 11:57:02 +0100481 """Validate that crash reports with same timestamp are counted."""
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200482 counter_attribute_name = "prob_crashes"
Mitja Nikolausb4ba8002018-11-07 11:57:02 +0100483 for i, (unique_entry, boot_reason) in enumerate(
484 zip(self.unique_entries, Crashreport.CRASH_BOOT_REASONS)
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200485 ):
486 params = {
487 "boot_reason": boot_reason,
488 self.unique_entry_name: unique_entry,
489 }
490 self._assert_reports_with_same_timestamp_are_counted(
Mitja Nikolausb4ba8002018-11-07 11:57:02 +0100491 Crashreport,
492 counter_attribute_name,
493 Dummy.USERNAMES[2 * i],
494 Dummy.USERNAMES[2 * i + 1],
495 **params
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200496 )
497
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200498 def test_smpl_reports_with_same_timestamp_are_counted(self):
Mitja Nikolausb4ba8002018-11-07 11:57:02 +0100499 """Validate that smpl reports with same timestamp are counted."""
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200500 counter_attribute_name = "smpl"
Mitja Nikolausb4ba8002018-11-07 11:57:02 +0100501 for i, (unique_entry, boot_reason) in enumerate(
502 zip(self.unique_entries, Crashreport.SMPL_BOOT_REASONS)
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200503 ):
504 params = {
505 "boot_reason": boot_reason,
506 self.unique_entry_name: unique_entry,
507 }
508 self._assert_reports_with_same_timestamp_are_counted(
Mitja Nikolausb4ba8002018-11-07 11:57:02 +0100509 Crashreport,
510 counter_attribute_name,
511 Dummy.USERNAMES[2 * i],
512 Dummy.USERNAMES[2 * i + 1],
513 **params
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200514 )
515
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200516 def test_other_reports_with_same_timestamp_are_counted(self):
Mitja Nikolausb4ba8002018-11-07 11:57:02 +0100517 """Validate that other reports with same timestamp are counted."""
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200518 counter_attribute_name = "other"
519 params = {"boot_reason": "random boot reason"}
520 self._assert_reports_with_same_timestamp_are_counted(
521 Crashreport, counter_attribute_name, **params
522 )
523
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200524 def _assert_older_reports_update_released_on_date(
525 self, report_type, **kwargs
526 ):
527 """Test updating of the released_on date.
528
529 Validate that the released_on date is updated once an older report is
530 sent.
531 """
532 # Create a report
Mitja Nikolaus77dd5652018-12-06 11:27:01 +0100533 device = Dummy.create_device(user=Dummy.create_user())
534 report = Dummy.create_report(report_type, device=device, **kwargs)
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200535
536 # Run the command to update the database
537 call_command("stats", "update")
538
539 # Get the corresponding version instance from the database
540 version = self.version_class.objects.get(
541 **{self.unique_entry_name: getattr(report, self.unique_entry_name)}
542 )
543
544 # Assert that the released_on date matches the first report date
Mitja Nikolausfd452f82018-11-07 11:53:59 +0100545 report_date = (
546 report.date.date() if report_type == Crashreport else report.date
547 )
548 self.assertEqual(version.released_on, report_date)
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200549
550 # Create a second report with the a timestamp earlier in time
551 report_2_date = report.date - timedelta(days=1)
Mitja Nikolaus77dd5652018-12-06 11:27:01 +0100552 Dummy.create_report(
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200553 report_type, device=device, date=report_2_date, **kwargs
554 )
555
556 # Run the command to update the database
557 call_command("stats", "update")
558
559 # Get the corresponding version instance from the database
560 version = self.version_class.objects.get(
561 **{self.unique_entry_name: getattr(report, self.unique_entry_name)}
562 )
563
564 # Assert that the released_on date matches the older report date
Mitja Nikolausfd452f82018-11-07 11:53:59 +0100565 if report_type == Crashreport:
566 report_2_date = report_2_date.date()
567 self.assertEqual(version.released_on, report_2_date)
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200568
569 def _assert_newer_reports_do_not_update_released_on_date(
570 self, report_type, **kwargs
571 ):
572 """Test updating of the released_on date.
573
574 Validate that the released_on date is not updated once a newer report is
575 sent.
576 """
577 # Create a report
Mitja Nikolaus77dd5652018-12-06 11:27:01 +0100578 device = Dummy.create_device(user=Dummy.create_user())
579 report = Dummy.create_report(report_type, device=device, **kwargs)
Mitja Nikolausfd452f82018-11-07 11:53:59 +0100580 report_1_date = (
581 report.date.date() if report_type == Crashreport else report.date
582 )
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200583
584 # Run the command to update the database
585 call_command("stats", "update")
586
587 # Get the corresponding version instance from the database
588 version = self.version_class.objects.get(
589 **{self.unique_entry_name: getattr(report, self.unique_entry_name)}
590 )
591
592 # Assert that the released_on date matches the first report date
593 self.assertEqual(version.released_on, report_1_date)
594
595 # Create a second report with the a timestamp later in time
596 report_2_date = report.date + timedelta(days=1)
Mitja Nikolaus77dd5652018-12-06 11:27:01 +0100597 Dummy.create_report(
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200598 report_type, device=device, date=report_2_date, **kwargs
599 )
600
601 # Run the command to update the database
602 call_command("stats", "update")
603
604 # Get the corresponding version instance from the database
605 version = self.version_class.objects.get(
606 **{self.unique_entry_name: getattr(report, self.unique_entry_name)}
607 )
608
609 # Assert that the released_on date matches the older report date
610 self.assertEqual(version.released_on, report_1_date)
611
612 def test_older_heartbeat_updates_released_on_date(self):
613 """Validate that older heartbeats update the release date."""
614 self._assert_older_reports_update_released_on_date(HeartBeat)
615
616 def test_older_crash_report_updates_released_on_date(self):
617 """Validate that older crash reports update the release date."""
618 self._assert_older_reports_update_released_on_date(Crashreport)
619
620 def test_newer_heartbeat_does_not_update_released_on_date(self):
621 """Validate that newer heartbeats don't update the release date."""
622 self._assert_newer_reports_do_not_update_released_on_date(HeartBeat)
623
624 def test_newer_crash_report_does_not_update_released_on_date(self):
625 """Validate that newer crash reports don't update the release date."""
626 self._assert_newer_reports_do_not_update_released_on_date(Crashreport)
627
628 def _assert_manually_changed_released_on_date_is_not_updated(
629 self, report_type, **kwargs
630 ):
631 """Test updating of manually changed released_on dates.
632
633 Validate that a manually changed released_on date is not updated when
634 new reports are sent.
635 """
636 # Create a report
Mitja Nikolaus77dd5652018-12-06 11:27:01 +0100637 device = Dummy.create_device(user=Dummy.create_user())
638 report = Dummy.create_report(report_type, device=device, **kwargs)
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200639
640 # Run the command to update the database
641 call_command("stats", "update")
642
643 # Get the corresponding version instance from the database
644 version = self.version_class.objects.get(
645 **{self.unique_entry_name: getattr(report, self.unique_entry_name)}
646 )
647
648 # Assert that the released_on date matches the first report date
Mitja Nikolausfd452f82018-11-07 11:53:59 +0100649 report_date = (
650 report.date.date() if report_type == Crashreport else report.date
651 )
652 self.assertEqual(version.released_on, report_date)
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200653
654 # Create a second report with a timestamp earlier in time
655 report_2_date = report.date - timedelta(days=1)
Mitja Nikolaus77dd5652018-12-06 11:27:01 +0100656 Dummy.create_report(
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200657 report_type, device=device, date=report_2_date, **kwargs
658 )
659
660 # Manually change the released_on date
Mitja Nikolausfd452f82018-11-07 11:53:59 +0100661 version_release_date = report_date + timedelta(days=1)
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200662 version.released_on = version_release_date
663 version.save()
664
665 # Run the command to update the database
666 call_command("stats", "update")
667
668 # Get the corresponding version instance from the database
669 version = self.version_class.objects.get(
670 **{self.unique_entry_name: getattr(report, self.unique_entry_name)}
671 )
672
673 # Assert that the released_on date still matches the date is was
674 # manually changed to
Mitja Nikolausfd452f82018-11-07 11:53:59 +0100675 self.assertEqual(version.released_on, version_release_date)
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200676
677 def test_manually_changed_released_on_date_is_not_updated_by_heartbeat(
678 self
679 ):
680 """Test update of manually changed released_on date with heartbeat."""
681 self._assert_manually_changed_released_on_date_is_not_updated(HeartBeat)
682
683 def test_manually_changed_released_on_date_is_not_updated_by_crash_report(
684 self
685 ):
686 """Test update of manually changed released_on date with crashreport."""
687 self._assert_manually_changed_released_on_date_is_not_updated(
688 Crashreport
689 )
690
691
692# pylint: disable=too-many-ancestors
693class StatsCommandRadioVersionsTestCase(StatsCommandVersionsTestCase):
694 """Test the generation of RadioVersion stats with the stats command."""
695
696 version_class = RadioVersion
697 unique_entry_name = "radio_version"
698 unique_entries = Dummy.RADIO_VERSIONS
699
700
701class CommandDebugOutputTestCase(TestCase):
702 """Test the reset and update commands debug output."""
703
704 # Additional positional arguments to pass to the commands
705 _CMD_ARGS = ["--no-color", "-v 2"]
706
707 # The stats models
708 _STATS_MODELS = [Version, VersionDaily, RadioVersion, RadioVersionDaily]
709 # The models that will generate an output
710 _ALL_MODELS = _STATS_MODELS + [StatsMetadata]
711 _COUNTER_NAMES = ["heartbeats", "crashes", "smpl", "other"]
712 _COUNTER_ACTIONS = ["created", "updated"]
713
714 def _assert_command_output_matches(self, command, number, facts, models):
715 """Validate the debug output of a command.
716
717 The debug output is matched against the facts and models given in
718 the parameters.
719 """
720 buffer = StringIO()
721 call_command("stats", command, *self._CMD_ARGS, stdout=buffer)
722 output = buffer.getvalue().splitlines()
723
724 expected_output = "{number} {model} {fact}"
725 for model in models:
726 for fact in facts:
727 self.assertIn(
728 expected_output.format(
729 number=number, model=model.__name__, fact=fact
730 ),
731 output,
732 )
733
734 def test_reset_command_on_empty_db(self):
735 """Test the reset command on an empty database.
736
737 The reset command should yield nothing on an empty database.
738 """
739 self._assert_command_output_matches(
740 "reset", 0, ["deleted"], self._ALL_MODELS
741 )
742
743 def test_update_command_on_empty_db(self):
744 """Test the update command on an empty database.
745
746 The update command should yield nothing on an empty database.
747 """
748 pattern = "{action} for counter {counter}"
749 facts = [
750 pattern.format(action=counter_action, counter=counter_name)
751 for counter_action in self._COUNTER_ACTIONS
752 for counter_name in self._COUNTER_NAMES
753 ]
754 self._assert_command_output_matches(
755 "update", 0, facts, self._STATS_MODELS
756 )
757
758 def test_reset_command_deletion_of_instances(self):
759 """Test the deletion of stats model instances with the reset command.
760
761 This test validates that model instances get deleted when the
762 reset command is called on a database that only contains a single
763 model instance for each class.
764 """
765 # Create dummy version instances
Mitja Nikolaus77dd5652018-12-06 11:27:01 +0100766 version = Dummy.create_version()
767 radio_version = Dummy.create_version(RadioVersion)
768 Dummy.create_daily_version(version)
769 Dummy.create_daily_radio_version(radio_version)
770 Dummy.create_stats_metadata()
Mitja Nikolaus03e412b2018-09-18 17:50:15 +0200771
772 # We expect that the model instances get deleted
773 self._assert_command_output_matches(
774 "reset", 1, ["deleted"], self._ALL_MODELS
775 )