blob: 6dd5bd76c9978cf21e5f2fb065d5115bbcdf9991 [file] [log] [blame]
Mitja Nikolaus1f7c03d2018-08-09 11:11:28 +02001"""Test crashreport_stats models and the 'stats' command."""
Borjan Tchakaloff0aa1a272018-03-19 17:23:58 +04002from datetime import datetime, date, timedelta
Mitja Nikolaus1f7c03d2018-08-09 11:11:28 +02003import pytz
Dirk Vogt62ff7f22017-05-04 16:07:21 +02004
Borjan Tchakaloff0aa1a272018-03-19 17:23:58 +04005from django.core.management import call_command
6from django.test import TestCase
Mitja Nikolaus1f7c03d2018-08-09 11:11:28 +02007from django.urls import reverse
8from django.utils.http import urlencode
9
10from rest_framework import status
11from rest_framework.test import APITestCase, APIClient
12
13from crashreport_stats.models import (
14 Version, VersionDaily, RadioVersion, RadioVersionDaily
15)
16
Borjan Tchakaloff0aa1a272018-03-19 17:23:58 +040017from crashreports.models import User, Device, Crashreport, HeartBeat
Mitja Nikolaus1f7c03d2018-08-09 11:11:28 +020018
19
20class Dummy():
21 """Class for creating dummy instances for testing."""
22
23 # Valid unique entries
24 BUILD_FINGERPRINTS = [(
25 'Fairphone/FP2/FP2:5.1/FP2/r4275.1_FP2_gms76_1.13.0:user/release-keys'
26 ), (
27 'Fairphone/FP2/FP2:5.1.1/FP2-gms75.1.13.0/FP2-gms75.1.13.0'
28 ':user/release-keys'
29 ), (
30 'Fairphone/FP2/FP2:6.0.1/FP2-gms-18.04.1/FP2-gms-18.04.1'
31 ':user/release-keys'
32 ), (
33 'Fairphone/FP2/FP2:7.1.2/18.07.2/gms-7480c31d'
34 ':user/release-keys'
35 )]
36 RADIO_VERSIONS = ['4437.1-FP2-0-07', '4437.1-FP2-0-08',
37 '4437.1-FP2-0-09', '4437.1-FP2-0-10']
38
39 DATES = [date(2018, 3, 19), date(2018, 3, 26), date(2018, 5, 1)]
40
41 DEFAULT_DUMMY_VERSION_VALUES = {
42 'build_fingerprint': BUILD_FINGERPRINTS[0],
43 'first_seen_on': DATES[1],
44 'released_on': DATES[0]
45 }
46
47 DEFAULT_DUMMY_VERSION_DAILY_VALUES = {
48 'date': DATES[1]
49 }
50
51 DEFAULT_DUMMY_RADIO_VERSION_VALUES = {
52 'radio_version': RADIO_VERSIONS[0],
53 'first_seen_on': DATES[1],
54 'released_on': DATES[0]
55 }
56
57 DEFAULT_DUMMY_RADIO_VERSION_DAILY_VALUES = {
58 'date': DATES[1]
59 }
60
61 DEFAULT_DUMMY_DEVICE_VALUES = {
62 'board_date': datetime(2015, 12, 15, 1, 23, 45, tzinfo=pytz.utc),
63 'chipset': 'Qualcomm MSM8974PRO-AA',
64 'token': '64111c62d521fb4724454ca6dea27e18f93ef56e'
65 }
66
67 DEFAULT_DUMMY_USER_VALUES = {
68 'username': 'testuser'
69 }
70
Borjan Tchakaloff0aa1a272018-03-19 17:23:58 +040071 DEFAULT_DUMMY_HEARTBEAT_VALUES = {
72 'app_version': 10100,
73 'uptime': (
74 'up time: 16 days, 21:49:56, idle time: 5 days, 20:55:04, '
75 'sleep time: 10 days, 20:46:27'),
76 'build_fingerprint': BUILD_FINGERPRINTS[0],
77 'radio_version': RADIO_VERSIONS[0],
78 'date': datetime(2018, 3, 19, tzinfo=pytz.utc),
79 }
80
81 DEFAULT_DUMMY_CRASHREPORT_VALUES = DEFAULT_DUMMY_HEARTBEAT_VALUES.copy()
82 DEFAULT_DUMMY_CRASHREPORT_VALUES.update({
83 'is_fake_report': 0,
84 'boot_reason': Crashreport.BOOT_REASON_UNKOWN,
85 'power_on_reason': 'it was powered on',
86 'power_off_reason': 'something happened and it went off',
87 })
88
Mitja Nikolaus1f7c03d2018-08-09 11:11:28 +020089 @staticmethod
90 def update_copy(original, update):
91 """Merge fields of update into a copy of original."""
92 data = original.copy()
93 data.update(update)
94 return data
95
96 @staticmethod
97 def create_dummy_user(**kwargs):
98 """Create a dummy user instance.
99
100 The dummy instance is created and saved to the database.
101 Args:
102 **kwargs:
103 Optional arguments to extend/overwrite the default values.
104
105 Returns: The created user instance.
106
107 """
108 entity = User(**Dummy.update_copy(
109 Dummy.DEFAULT_DUMMY_USER_VALUES, kwargs))
110 entity.save()
111 return entity
112
113 @staticmethod
114 def create_dummy_device(user, **kwargs):
115 """Create a dummy device instance.
116
117 The dummy instance is created and saved to the database.
118 Args:
119 user: The user instance that the device should relate to
120 **kwargs:
121 Optional arguments to extend/overwrite the default values.
122
123 Returns: The created device instance.
124
125 """
126 entity = Device(user=user, **Dummy.update_copy(
127 Dummy.DEFAULT_DUMMY_DEVICE_VALUES, kwargs))
128 entity.save()
129 return entity
130
131 @staticmethod
Borjan Tchakaloff0aa1a272018-03-19 17:23:58 +0400132 def create_dummy_report(report_type, device, **kwargs):
133 """Create a dummy report instance of the given report class type.
134
135 The dummy instance is created and saved to the database.
136 Args:
137 report_type: The class of the report type to be created.
138 user: The device instance that the heartbeat should relate to
139 **kwargs:
140 Optional arguments to extend/overwrite the default values.
141
142 Returns: The created report instance.
143
144 """
145 if report_type == HeartBeat:
146 entity = HeartBeat(device=device, **Dummy.update_copy(
147 Dummy.DEFAULT_DUMMY_HEARTBEAT_VALUES, kwargs))
148 elif report_type == Crashreport:
149 entity = Crashreport(device=device, **Dummy.update_copy(
150 Dummy.DEFAULT_DUMMY_CRASHREPORT_VALUES, kwargs))
151 else:
152 raise RuntimeError(
153 'No dummy report instance can be created for {}'.format(
154 report_type.__name__))
155 entity.save()
156 return entity
157
158 @staticmethod
Mitja Nikolaus1f7c03d2018-08-09 11:11:28 +0200159 def create_dummy_version(**kwargs):
160 """Create a dummy version instance.
161
162 The dummy instance is created and saved to the database.
163 Args:
164 **kwargs:
165 Optional arguments to extend/overwrite the default values.
166
167 Returns: The created version instance.
168
169 """
170 entity = Version(**Dummy.update_copy(
171 Dummy.DEFAULT_DUMMY_VERSION_VALUES, kwargs))
172 entity.save()
173 return entity
174
175 @staticmethod
176 def create_dummy_radio_version(**kwargs):
177 """Create a dummy radio version instance.
178
179 The dummy instance is created and saved to the database.
180 Args:
181 **kwargs:
182 Optional arguments to extend/overwrite the default values.
183
184 Returns: The created radio version instance.
185
186 """
187 entity = RadioVersion(**Dummy.update_copy(
188 Dummy.DEFAULT_DUMMY_RADIO_VERSION_VALUES, kwargs))
189 entity.save()
190 return entity
191
192 @staticmethod
193 def create_dummy_daily_version(version, **kwargs):
194 """Create a dummy daily version instance.
195
196 The dummy instance is created and saved to the database.
197 Args:
198 **kwargs:
199 Optional arguments to extend/overwrite the default values.
200
201 Returns: The created daily version instance.
202
203 """
204 entity = VersionDaily(version=version, **Dummy.update_copy(
205 Dummy.DEFAULT_DUMMY_VERSION_DAILY_VALUES, kwargs))
206 entity.save()
207 return entity
208
209 @staticmethod
210 def create_dummy_daily_radio_version(version, **kwargs):
211 """Create a dummy daily radio version instance.
212
213 The dummy instance is created and saved to the database.
214 Args:
215 **kwargs:
216 Optional arguments to extend/overwrite the default values.
217
218 Returns: The created daily radio version instance.
219
220 """
221 entity = RadioVersionDaily(version=version, **Dummy.update_copy(
222 Dummy.DEFAULT_DUMMY_RADIO_VERSION_DAILY_VALUES, kwargs))
223 entity.save()
224 return entity
225
226
227class _VersionTestCase(APITestCase):
228 """Abstract class for version-related test cases to inherit from."""
229
230 # The attribute name characterising the unicity of a stats entry (the
231 # named identifier)
232 unique_entry_name = 'build_fingerprint'
233 # The collection of unique entries to post
234 unique_entries = Dummy.BUILD_FINGERPRINTS
235 # The URL to retrieve the stats entries from
236 endpoint_url = reverse('hiccup_stats_api_v1_versions')
237
238 @classmethod
239 def setUpTestData(cls): # noqa: N802
240 """Create an admin user for accessing the API.
241
242 The APIClient that can be used to make authenticated requests to the
243 server is stored in self.admin.
244 """
245 admin_user = User.objects.create_superuser(
246 'somebody', 'somebody@example.com', 'thepassword')
247 cls.admin = APIClient()
248 cls.admin.force_authenticate(admin_user)
249
250 @staticmethod
251 def _create_dummy_version(**kwargs):
252 return Dummy.create_dummy_version(**kwargs)
253
254 def _get_with_params(self, url, params):
255 return self.admin.get('{}?{}'.format(url, urlencode(params)))
256
257 def _assert_result_length_is(self, response, count):
258 self.assertEqual(response.status_code, status.HTTP_200_OK)
259 self.assertIn('results', response.data)
260 self.assertIn('count', response.data)
261 self.assertEqual(response.data['count'], count)
262 self.assertEqual(len(response.data['results']), count)
263
264 def _assert_device_owner_has_no_get_access(self, entries_url):
265 # Create a user and device
266 user = Dummy.create_dummy_user()
267 device = Dummy.create_dummy_device(user=user)
268
269 # Create authenticated client
270 user = APIClient()
271 user.credentials(HTTP_AUTHORIZATION='Token ' + device.token)
272
273 # Try getting entries using the client
274 response = user.get(entries_url)
275 self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
276
277 def _assert_filter_result_matches(self, filter_params, expected_result):
278 # List entities with filter
279 response = self._get_with_params(self.endpoint_url, filter_params)
280
281 # Expect only the single matching result to be returned
282 self._assert_result_length_is(response, 1)
283 self.assertEqual(response.data['results'][0][self.unique_entry_name],
284 getattr(expected_result, self.unique_entry_name))
285
286
287class VersionTestCase(_VersionTestCase):
288 """Test the Version and REST endpoint."""
289
290 def _create_version_entities(self):
291 versions = [
292 self._create_dummy_version(
293 **{self.unique_entry_name: unique_entry}
294 )
295 for unique_entry in self.unique_entries
296 ]
297 return versions
298
299 def test_list_versions_without_authentication(self):
300 """Test listing of versions without authentication."""
301 response = self.client.get(self.endpoint_url)
302 self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
303
304 def test_list_versions_as_device_owner(self):
305 """Test listing of versions as device owner."""
306 self._assert_device_owner_has_no_get_access(self.endpoint_url)
307
308 def test_list_versions_empty_database(self):
309 """Test listing of versions on an empty database."""
310 response = self.admin.get(self.endpoint_url)
311 self._assert_result_length_is(response, 0)
312
313 def test_list_versions(self):
314 """Test listing versions."""
315 versions = self._create_version_entities()
316 response = self.admin.get(self.endpoint_url)
317 self._assert_result_length_is(response, len(versions))
318
319 def test_filter_versions_by_unique_entry_name(self):
320 """Test filtering versions by their unique entry name."""
321 versions = self._create_version_entities()
322 response = self.admin.get(self.endpoint_url)
323
324 # Listing all entities should return the correct result length
325 self._assert_result_length_is(response, len(versions))
326
327 # List entities with filter
328 filter_params = {
329 self.unique_entry_name: getattr(versions[0],
330 self.unique_entry_name)
331 }
332 self._assert_filter_result_matches(filter_params,
333 expected_result=versions[0])
334
335 def test_filter_versions_by_release_type(self):
336 """Test filtering versions by release type."""
337 # Create versions for all combinations of release types
338 versions = []
339 i = 0
340 for is_official_release in True, False:
341 for is_beta_release in True, False:
342 versions.append(self._create_dummy_version(**{
343 'is_official_release': is_official_release,
344 'is_beta_release': is_beta_release,
345 self.unique_entry_name: self.unique_entries[i]
346 }))
347 i += 1
348
349 # # Listing all entities should return the correct result length
350 response = self.admin.get(self.endpoint_url)
351 self._assert_result_length_is(response, len(versions))
352
353 # List each of the entities with the matching filter params
354 for version in versions:
355 filter_params = {
356 'is_official_release': version.is_official_release,
357 'is_beta_release': version.is_beta_release
358 }
359 self._assert_filter_result_matches(filter_params,
360 expected_result=version)
361
362 def test_filter_versions_by_first_seen_date(self):
363 """Test filtering versions by first seen date."""
364 versions = self._create_version_entities()
365
366 # Set the first seen date of an entity
367 versions[0].first_seen_on = Dummy.DATES[2]
368 versions[0].save()
369
370 # Listing all entities should return the correct result length
371 response = self.admin.get(self.endpoint_url)
372 self._assert_result_length_is(response, len(versions))
373
374 # Expect the single matching result to be returned
375 filter_params = {'first_seen_after': Dummy.DATES[2]}
376 self._assert_filter_result_matches(filter_params,
377 expected_result=versions[0])
378
379
380# pylint: disable=too-many-ancestors
381class RadioVersionTestCase(VersionTestCase):
382 """Test the RadioVersion REST endpoint."""
383
384 unique_entry_name = 'radio_version'
385 unique_entries = Dummy.RADIO_VERSIONS
386 endpoint_url = reverse('hiccup_stats_api_v1_radio_versions')
387
388 @staticmethod
389 def _create_dummy_version(**kwargs):
390 return Dummy.create_dummy_radio_version(**kwargs)
391
392
393class VersionDailyTestCase(_VersionTestCase):
394 """Test the VersionDaily REST endpoint."""
395
396 endpoint_url = reverse('hiccup_stats_api_v1_version_daily')
397
398 @staticmethod
399 def _create_dummy_daily_version(version, **kwargs):
400 return Dummy.create_dummy_daily_version(version, **kwargs)
401
402 def _create_version_entities(self):
403 versions = [
404 self._create_dummy_version(
405 **{self.unique_entry_name: unique_entry}
406 )
407 for unique_entry in self.unique_entries
408 ]
409 versions_daily = [
410 self._create_dummy_daily_version(version=version)
411 for version in versions
412 ]
413 return versions_daily
414
415 def test_list_daily_versions_without_authentication(self):
416 """Test listing of daily versions without authentication."""
417 response = self.client.get(self.endpoint_url)
418 self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
419
420 def test_list_daily_versions_as_device_owner(self):
421 """Test listing of daily versions as device owner."""
422 self._assert_device_owner_has_no_get_access(self.endpoint_url)
423
424 def test_list_daily_versions_empty_database(self):
425 """Test listing of daily versions on an empty database."""
426 response = self.admin.get(self.endpoint_url)
427 self._assert_result_length_is(response, 0)
428
429 def test_list_daily_versions(self):
430 """Test listing daily versions."""
431 versions_daily = self._create_version_entities()
432 response = self.admin.get(self.endpoint_url)
433 self._assert_result_length_is(response, len(versions_daily))
434
435 def test_filter_daily_versions_by_version(self):
436 """Test filtering versions by the version they relate to."""
437 # Create VersionDaily entities
438 versions = self._create_version_entities()
439
440 # Listing all entities should return the correct result length
441 response = self.admin.get(self.endpoint_url)
442 self._assert_result_length_is(response, len(versions))
443
444 # List entities with filter
445 param_name = 'version__' + self.unique_entry_name
446 filter_params = {
447 param_name: getattr(versions[0].version, self.unique_entry_name)
448 }
449 self._assert_filter_result_matches(filter_params,
450 expected_result=versions[0].version)
451
452 def test_filter_daily_versions_by_date(self):
453 """Test filtering daily versions by date."""
454 # Create Version and VersionDaily entities
455 versions = self._create_version_entities()
456
457 # Update the date
458 versions[0].date = Dummy.DATES[2]
459 versions[0].save()
460
461 # Listing all entities should return the correct result length
462 response = self.admin.get(self.endpoint_url)
463 self._assert_result_length_is(response, len(versions))
464
465 # Expect the single matching result to be returned
466 filter_params = {'date': versions[0].date}
467 self._assert_filter_result_matches(filter_params,
468 expected_result=versions[0].version)
469
470
471class RadioVersionDailyTestCase(VersionDailyTestCase):
472 """Test the RadioVersionDaily REST endpoint."""
473
474 unique_entry_name = 'radio_version'
475 unique_entries = Dummy.RADIO_VERSIONS
476 endpoint_url = reverse('hiccup_stats_api_v1_radio_version_daily')
477
478 @staticmethod
479 def _create_dummy_version(**kwargs):
480 entity = RadioVersion(**Dummy.update_copy(
481 Dummy.DEFAULT_DUMMY_RADIO_VERSION_VALUES, kwargs))
482 entity.save()
483 return entity
484
485 @staticmethod
486 def _create_dummy_daily_version(version, **kwargs):
487 return Dummy.create_dummy_daily_radio_version(version, **kwargs)
Borjan Tchakaloff0aa1a272018-03-19 17:23:58 +0400488
489
490class StatsCommandVersionsTestCase(TestCase):
491 """Test the generation of Version stats with the stats command."""
492
Borjan Tchakaloff0aa1a272018-03-19 17:23:58 +0400493 # FIXME: Test for false duplicates: same timestamps but different UUIDs
494 # FIXME: Test that the 'released_on' field changes or not once an older
495 # report has been sent depending on whether the field has been manually
496 # changed
497 # FIXME: Test that tests the daily version stats
498 # FIXME: Test creating stats from reports of different devices/users.
499
500 # The class of the version type to be tested
501 version_class = Version
502 # The attribute name characterising the unicity of a stats entry (the
503 # named identifier)
504 unique_entry_name = 'build_fingerprint'
505 # The collection of unique entries to post
506 unique_entries = Dummy.BUILD_FINGERPRINTS
507
508 def _create_reports(self, report_type, unique_entry_name, device,
509 number, **kwargs):
510 # Create reports with distinct timestamps
511 now = datetime.now(pytz.utc)
512 for i in range(number):
513 report_date = now - timedelta(milliseconds=i)
514 report_attributes = {
515 self.unique_entry_name: unique_entry_name,
516 'device': device,
517 'date': report_date
518 }
519 report_attributes.update(**kwargs)
520 Dummy.create_dummy_report(report_type, **report_attributes)
521
522 def test_stats_calculation(self):
523 """Test generation of a Version instance."""
524 user = Dummy.create_dummy_user()
525 device = Dummy.create_dummy_device(user=user)
526 heartbeat = Dummy.create_dummy_report(HeartBeat, device=device)
527
528 # Expect that we do not have the Version before updating the stats
529 get_params = {
530 self.unique_entry_name: getattr(heartbeat, self.unique_entry_name)
531 }
532 self.assertRaises(self.version_class.DoesNotExist,
533 self.version_class.objects.get, **get_params)
534
535 # Run the command to update the database
536 call_command('stats', 'update')
537
538 # Assume that a corresponding Version instance has been created
539 version = self.version_class.objects.get(**get_params)
540 self.assertIsNotNone(version)
541
542 def _assert_older_report_updates_version_date(self, report_type):
543 """Validate that older reports sent later affect the version date."""
544 user = Dummy.create_dummy_user()
545 device = Dummy.create_dummy_device(user=user)
546 report = Dummy.create_dummy_report(report_type, device=device)
547
548 # Run the command to update the database
549 call_command('stats', 'update')
550
551 get_params = {
552 self.unique_entry_name: getattr(report, self.unique_entry_name)
553 }
554 version = self.version_class.objects.get(**get_params)
555
556 self.assertEqual(report.date.date(), version.first_seen_on)
557
558 # Create a new report from an earlier point in time
559 report_time_2 = report.date - timedelta(weeks=1)
560 Dummy.create_dummy_report(report_type, device=device,
561 date=report_time_2)
562
563 # Run the command to update the database
564 call_command('stats', 'update')
565
566 # Get the same version object from before
567 version = self.version_class.objects.get(**get_params)
568
569 # Validate that the date matches the report recently sent
570 self.assertEqual(report_time_2.date(), version.first_seen_on)
571
572 def test_older_heartbeat_updates_version_date(self):
573 """Validate updating version date with older heartbeats."""
574 self._assert_older_report_updates_version_date(HeartBeat)
575
576 def test_older_crash_report_updates_version_date(self):
577 """Validate updating version date with older crash reports."""
578 self._assert_older_report_updates_version_date(Crashreport)
579
580 def test_entries_are_unique(self):
581 """Validate the entries' unicity and value."""
582 # Create some reports
583 user = Dummy.create_dummy_user()
584 device = Dummy.create_dummy_device(user=user)
585 for unique_entry in self.unique_entries:
586 self._create_reports(HeartBeat, unique_entry, device, 10)
587
588 # Run the command to update the database
589 call_command('stats', 'update')
590
591 # Check whether the correct amount of distinct versions have been
592 # created
593 versions = self.version_class.objects.all()
594 for version in versions:
595 self.assertIn(getattr(version, self.unique_entry_name),
596 self.unique_entries)
597 self.assertEqual(len(versions), len(self.unique_entries))
598
599 def _assert_counter_distribution_is_correct(self, report_type, numbers,
600 counter_attribute_name,
601 **kwargs):
602 """Validate a counter distribution in the database."""
603 if len(numbers) != len(self.unique_entries):
604 raise ValueError('The length of the numbers list must match the '
605 'length of self.unique_entries in the test class'
606 '({} != {})'.format(len(numbers),
607 len(self.unique_entries)))
608 # Create some reports
609 user = Dummy.create_dummy_user()
610 device = Dummy.create_dummy_device(user=user)
611 for unique_entry, num in zip(self.unique_entries, numbers):
612 self._create_reports(report_type, unique_entry, device, num,
613 **kwargs)
614
615 # Run the command to update the database
616 call_command('stats', 'update')
617
618 # Check whether the numbers of reports match
619 for version in self.version_class.objects.all():
620 unique_entry_name = getattr(version, self.unique_entry_name)
621 num = numbers[self.unique_entries.index(unique_entry_name)]
622 self.assertEqual(num, getattr(version, counter_attribute_name))
623
624 def test_heartbeats_counter(self):
625 """Test the calculation of the heartbeats counter."""
626 numbers = [10, 7, 8, 5]
627 counter_attribute_name = 'heartbeats'
628 self._assert_counter_distribution_is_correct(HeartBeat, numbers,
629 counter_attribute_name)
630
631 def test_crash_reports_counter(self):
632 """Test the calculation of the crashreports counter."""
633 numbers = [2, 5, 0, 3]
634 counter_attribute_name = 'prob_crashes'
635 boot_reason_param = {'boot_reason': Crashreport.BOOT_REASON_UNKOWN}
636 self._assert_counter_distribution_is_correct(Crashreport, numbers,
637 counter_attribute_name,
638 **boot_reason_param)
639
640 def test_smpl_reports_counter(self):
641 """Test the calculation of the smpl reports counter."""
642 numbers = [1, 3, 4, 0]
643 counter_attribute_name = 'smpl'
644 boot_reason_param = {'boot_reason': Crashreport.BOOT_REASON_RTC_ALARM}
645 self._assert_counter_distribution_is_correct(Crashreport, numbers,
646 counter_attribute_name,
647 **boot_reason_param)
648
649 def test_other_reports_counter(self):
650 """Test the calculation of the other reports counter."""
651 numbers = [0, 2, 1, 2]
652 counter_attribute_name = 'other'
653 boot_reason_param = {'boot_reason': "random boot reason"}
654 self._assert_counter_distribution_is_correct(Crashreport, numbers,
655 counter_attribute_name,
656 **boot_reason_param)
657
Borjan Tchakaloffd803b632018-03-20 17:32:37 +0400658 def _assert_duplicates_are_ignored(self, report_type, device,
659 counter_attribute_name, **kwargs):
660 """Validate that reports with duplicate timestamps are ignored."""
661 # Create a report
662 report = Dummy.create_dummy_report(report_type, device=device,
663 **kwargs)
664
665 # Create a second report with the same timestamp
666 Dummy.create_dummy_report(report_type, device=device,
667 date=report.date, **kwargs)
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 get_params = {
674 self.unique_entry_name: getattr(report, self.unique_entry_name)
675 }
676 version = self.version_class.objects.get(**get_params)
677
678 # Assert that the report with the duplicate timestamp is not
679 # counted, i.e. only 1 report is counted.
680 self.assertEqual(getattr(version, counter_attribute_name), 1)
681
682 def test_heartbeat_duplicates_are_ignored(self):
683 """Validate that heartbeat duplicates are ignored."""
684 counter_attribute_name = 'heartbeats'
685 device = Dummy.create_dummy_device(user=Dummy.create_dummy_user())
686 self._assert_duplicates_are_ignored(HeartBeat, device,
687 counter_attribute_name)
688
689 def test_crash_report_duplicates_are_ignored(self):
690 """Validate that crash report duplicates are ignored."""
691 counter_attribute_name = 'prob_crashes'
692 device = Dummy.create_dummy_device(user=Dummy.create_dummy_user())
693 for i, boot_reason in enumerate(Crashreport.CRASH_BOOT_REASONS):
694 params = {'boot_reason': boot_reason,
695 self.unique_entry_name: self.unique_entries[i]}
696 self._assert_duplicates_are_ignored(Crashreport, device,
697 counter_attribute_name,
698 **params)
699
700 def test_smpl_report_duplicates_are_ignored(self):
701 """Validate that smpl report duplicates are ignored."""
702 counter_attribute_name = 'smpl'
703 device = Dummy.create_dummy_device(user=Dummy.create_dummy_user())
704 for i, boot_reason in enumerate(Crashreport.SMPL_BOOT_REASONS):
705 params = {'boot_reason': boot_reason,
706 self.unique_entry_name: self.unique_entries[i]}
707 self._assert_duplicates_are_ignored(Crashreport, device,
708 counter_attribute_name,
709 **params)
710
711 def test_other_report_duplicates_are_ignored(self):
712 """Validate that other report duplicates are ignored."""
713 counter_attribute_name = 'other'
714 params = {'boot_reason': 'random boot reason'}
715 device = Dummy.create_dummy_device(user=Dummy.create_dummy_user())
716 self._assert_duplicates_are_ignored(Crashreport, device,
717 counter_attribute_name,
718 **params)
719
Borjan Tchakaloff0aa1a272018-03-19 17:23:58 +0400720
721# pylint: disable=too-many-ancestors
722class StatsCommandRadioVersionsTestCase(StatsCommandVersionsTestCase):
723 """Test the generation of RadioVersion stats with the stats command."""
724
725 version_class = RadioVersion
726 unique_entry_name = 'radio_version'
727 unique_entries = Dummy.RADIO_VERSIONS