Source code for oioioi.problems.tests.test_problem_statistics

from functools import cmp_to_key

from django.contrib.auth.models import User
from django.test.utils import override_settings
from django.urls import reverse
from django.utils.html import strip_tags

from oioioi.base.tests import TestCase
from oioioi.contests.handlers import update_problem_statistics
from oioioi.contests.models import Submission
from oioioi.problems.management.commands import recalculate_statistics
from oioioi.problems.models import Problem, ProblemStatistics, UserStatistics


@override_settings(PROBLEM_STATISTICS_AVAILABLE=True)
[docs]class TestProblemStatistics(TestCase):
[docs] fixtures = [ 'test_users', 'test_full_package', 'test_contest', 'test_problem_instance', 'test_extra_contests', 'test_extra_problem_instance', 'test_submissions_for_statistics', 'test_extra_submissions_for_statistics', ]
[docs] def test_statistics_updating(self): Submission.objects.select_for_update().filter(id__gt=4).update(kind='IGNORED') problem = Problem.objects.get(id=1) ps, created = ProblemStatistics.objects.get_or_create(problem=problem) self.assertTrue(ps.submitted == 0) self.assertTrue(ps.solved == 0) self.assertTrue(ps.avg_best_score == 0) # Count submissions for single user in single problem instance # compilation error update_problem_statistics({'submission_id': 1}) ps.refresh_from_db() self.assertTrue(ps.submitted == 0) self.assertTrue(ps.solved == 0) self.assertTrue(ps.avg_best_score == 0) # 0 pts update_problem_statistics({'submission_id': 2}) ps.refresh_from_db() self.assertTrue(ps.submitted == 1) self.assertTrue(ps.solved == 0) self.assertTrue(ps.avg_best_score == 0) # 42 pts update_problem_statistics({'submission_id': 3}) ps.refresh_from_db() self.assertTrue(ps.submitted == 1) self.assertTrue(ps.solved == 0) self.assertTrue(ps.avg_best_score == 42) # 100 pts update_problem_statistics({'submission_id': 4}) ps.refresh_from_db() self.assertTrue(ps.submitted == 1) self.assertTrue(ps.solved == 1) self.assertTrue(ps.avg_best_score == 100) # ignore 100 pts submission = Submission.objects.select_for_update().get(id=4) submission.kind = 'IGNORED' submission.save() submission.problem_instance.problem.controller.recalculate_statistics_for_user( submission.user ) ps.refresh_from_db() self.assertTrue(ps.submitted == 1) self.assertTrue(ps.solved == 0) self.assertTrue(ps.avg_best_score == 42) # unignore 100 pts submission = Submission.objects.select_for_update().get(id=4) submission.kind = 'NORMAL' submission.save() submission.problem_instance.problem.controller.recalculate_statistics_for_user( submission.user ) ps.refresh_from_db() self.assertTrue(ps.submitted == 1) self.assertTrue(ps.solved == 1) self.assertTrue(ps.avg_best_score == 100) # delete 100 pts submission = Submission.objects.select_for_update().get(id=4).delete() ps.refresh_from_db() self.assertTrue(ps.submitted == 1) self.assertTrue(ps.solved == 0) self.assertTrue(ps.avg_best_score == 42)
[docs] def test_statistics_probleminstances(self): Submission.objects.select_for_update().filter(id__gt=8).update(kind='IGNORED') problem = Problem.objects.get(id=1) ps, created = ProblemStatistics.objects.get_or_create(problem=problem) self.assertTrue(ps.submitted == 0) self.assertTrue(ps.solved == 0) self.assertTrue(ps.avg_best_score == 0) # Count submissions for two users in two problem instances # user1 to pinstance1 100 pts update_problem_statistics({'submission_id': 4}) ps.refresh_from_db() self.assertTrue(ps.submitted == 1) self.assertTrue(ps.solved == 1) self.assertTrue(ps.avg_best_score == 100) # user1 to pinstance2 100 pts update_problem_statistics({'submission_id': 5}) ps.refresh_from_db() self.assertTrue(ps.submitted == 1) self.assertTrue(ps.solved == 1) self.assertTrue(ps.avg_best_score == 100) # user2 to pinstance1 0 pts update_problem_statistics({'submission_id': 6}) ps.refresh_from_db() self.assertTrue(ps.submitted == 2) self.assertTrue(ps.solved == 1) self.assertTrue(ps.avg_best_score == 50) # user2 to pinstance2 50 pts update_problem_statistics({'submission_id': 7}) ps.refresh_from_db() self.assertTrue(ps.submitted == 2) self.assertTrue(ps.solved == 1) self.assertTrue(ps.avg_best_score == 75) # user2 to pinstance1 100 pts update_problem_statistics({'submission_id': 8}) ps.refresh_from_db() self.assertTrue(ps.submitted == 2) self.assertTrue(ps.solved == 2) self.assertTrue(ps.avg_best_score == 100)
[docs] def test_recalculate_statistics(self): problem = Problem.objects.get(id=1) ps, created = ProblemStatistics.objects.get_or_create(problem=problem) self.assertTrue(ps.submitted == 0) self.assertTrue(ps.solved == 0) self.assertTrue(ps.avg_best_score == 0) # Best scores for user1: 100, user2: 100, user3: 0, user4: None (CE) manager = recalculate_statistics.Command() manager.run_from_argv(['manage.py', 'recalculate_statistics']) # refresh_from_db() won't work because statistics were deleted problem = Problem.objects.get(id=1) ps = problem.statistics self.assertTrue(ps.submitted == 3) self.assertTrue(ps.solved == 2) self.assertTrue(ps.avg_best_score == 66)
@override_settings(PROBLEM_STATISTICS_AVAILABLE=True)
[docs]class TestProblemStatisticsSpecialCases(TestCase):
[docs] fixtures = [ 'test_users', 'test_full_package', 'test_contest', 'test_problem_instance', 'test_statistics_special_cases', ]
[docs] def test_statistics_null_score(self): problem = Problem.objects.get(id=1) ps, created = ProblemStatistics.objects.get_or_create(problem=problem) self.assertTrue(ps.submitted == 0) self.assertTrue(ps.solved == 0) self.assertTrue(ps.avg_best_score == 0) update_problem_statistics({'submission_id': 10000}) ps.refresh_from_db() self.assertTrue(ps.submitted == 1) self.assertTrue(ps.solved == 0) self.assertTrue(ps.avg_best_score == 0)
[docs] def test_statistics_zero_max_score(self): problem = Problem.objects.get(id=1) ps, created = ProblemStatistics.objects.get_or_create(problem=problem) self.assertTrue(ps.submitted == 0) self.assertTrue(ps.solved == 0) self.assertTrue(ps.avg_best_score == 0) update_problem_statistics({'submission_id': 10004}) ps.refresh_from_db() self.assertTrue(ps.submitted == 1) self.assertTrue(ps.solved == 0) self.assertTrue(ps.avg_best_score == 0)
[docs] def test_statistics_weird_scores(self): problem = Problem.objects.get(id=1) ps, created = ProblemStatistics.objects.get_or_create(problem=problem) self.assertTrue(ps.submitted == 0) self.assertTrue(ps.solved == 0) self.assertTrue(ps.avg_best_score == 0) update_problem_statistics({'submission_id': 10002}) ps.refresh_from_db() self.assertTrue(ps.submitted == 1) self.assertTrue(ps.solved == 0) self.assertTrue(ps.avg_best_score == 50) update_problem_statistics({'submission_id': 10003}) ps.refresh_from_db() self.assertTrue(ps.submitted == 1) self.assertTrue(ps.solved == 1) self.assertTrue(ps.avg_best_score == 100)
# Check if imported submissions lacking score_report.score and # score_report.max_score are handled correctly.
[docs] def test_statistics_imported(self): problem = Problem.objects.get(id=1) ps, created = ProblemStatistics.objects.get_or_create(problem=problem) self.assertTrue(ps.submitted == 0) self.assertTrue(ps.solved == 0) self.assertTrue(ps.avg_best_score == 0) update_problem_statistics({'submission_id': 10001}) ps.refresh_from_db() self.assertTrue(ps.submitted == 1) self.assertTrue(ps.solved == 1) self.assertTrue(ps.avg_best_score == 100)
@override_settings(PROBLEM_STATISTICS_AVAILABLE=True)
[docs]class TestProblemStatisticsDisplay(TestCase):
[docs] fixtures = ['test_users', 'test_statistics_display']
[docs] problem_columns = [ 'short_name', 'name', 'submitted', 'solved_pc', 'avg_best_score', 'user_score', ]
[docs] problem_data = [ [u'aaa', u'Aaaa', u'7', u'14%', u'50', None], [u'bbb', u'Bbbb', u'8', u'25%', u'45', u'0'], [u'ccc', u'Cccc', u'5', u'60%', u'90', u'50'], [u'ddd', u'Dddd', u'6', u'66%', u'80', u'90'], ]
[docs] def _get_table_contents(self, html): col_n = html.count('<th') - html.count('<thead>') row_n = html.count('<tr') - 1 # Skip first `<tr>` pos = html.find('<tr') + 1 self.assertNotEqual(pos, -1) rows = [] for _ in range(row_n): pos = html.find('<tr', pos) self.assertNotEqual(pos, -1) rows.append([]) for _ in range(col_n): pos = html.find('<td', pos) self.assertNotEqual(pos, -1) none_pos = html.find('<td/>', pos) if none_pos == pos: rows[-1].append(None) pos += len('<td/>') else: pos2 = html.find('</td>', pos) self.assertNotEqual(pos2, -1) rows[-1].append(strip_tags(html[pos:pos2]).strip()) pos = pos2 + len('</td>') return rows
@staticmethod
[docs] def _cmp_str_with_none(key_fn): def _cmp(a, b): key_a = key_fn(a) key_b = key_fn(b) if key_a is None: return True if key_b is None: return False return key_a < key_b return _cmp
[docs] def _assert_rows_sorted(self, rows, order_by=0, desc=False): # Nones should be treated as if they were less than zeroes # (i.e. listed last when desc=True and listed first otherwise). self.assertEqual( rows, sorted( rows, key=cmp_to_key(self._cmp_str_with_none(lambda x: x[order_by])), reverse=desc, ), )
[docs] def test_statistics_problem_list(self): self.assertTrue(self.client.login(username='test_user')) url_main = reverse('problemset_main') response = self.client.get(url_main) self.assertEqual(response.status_code, 200) rows = self._get_table_contents(response.content.decode('utf-8')) self.assertEqual(rows, self.problem_data) # There are exactly four problems, one for each score class. for result in ['result--OK', 'result--TRIED', 'result--FAILED']: self.assertContains(response, result, count=1)
[docs] def test_statistics_sorting(self): self.assertTrue(self.client.login(username='test_user')) for i, column in enumerate(self.problem_columns): url_main = reverse('problemset_main') response = self.client.get(url_main, {'order_by': column}) self.assertEqual(response.status_code, 200) rows = self._get_table_contents(response.content.decode('utf-8')) self._assert_rows_sorted(rows, order_by=i) response = self.client.get(url_main, {'order_by': column, 'desc': ''}) self.assertEqual(response.status_code, 200) rows = self._get_table_contents(response.content.decode('utf-8')) self._assert_rows_sorted(rows, order_by=i, desc=True)
[docs] def test_statistics_nulls(self): # Make ccc have null stats ProblemStatistics.objects.get(problem__short_name='ccc').delete() # Supply user_score for a aaa_statistics = UserStatistics( problem_statistics=ProblemStatistics.objects.get(problem__short_name='aaa'), user=User.objects.get(username='test_user'), ) aaa_statistics.best_score = 0 aaa_statistics.has_submitted = True aaa_statistics.save() self.assertTrue(self.client.login(username='test_user')) for column in self.problem_columns[2:]: url_main = reverse('problemset_main') response = self.client.get(url_main, {'order_by': column}) self.assertEqual(response.status_code, 200) rows = self._get_table_contents(response.content.decode('utf-8')) self.assertEqual(rows[0], [u'ccc', u'Cccc', '0', None, None, None]) response = self.client.get(url_main, {'order_by': column, 'desc': ''}) self.assertEqual(response.status_code, 200) rows = self._get_table_contents(response.content.decode('utf-8')) self.assertEqual(rows[-1], [u'ccc', u'Cccc', '0', None, None, None])
[docs] def test_statistics_sort_nulls(self): ProblemStatistics.objects.get(problem__short_name='ccc').delete() self.assertTrue(self.client.login(username='test_user')) for i, column in enumerate(self.problem_columns): url_main = reverse('problemset_main') response = self.client.get(url_main, {'order_by': column}) self.assertEqual(response.status_code, 200) rows = self._get_table_contents(response.content.decode('utf-8')) self._assert_rows_sorted(rows, order_by=i) response = self.client.get(url_main, {'order_by': column, 'desc': ''}) self.assertEqual(response.status_code, 200) rows = self._get_table_contents(response.content.decode('utf-8')) self._assert_rows_sorted(rows, order_by=i, desc=True)
# Check that the query and the ordering are correctly preserved in links
[docs] def test_statistics_sorting_with_query(self): self.assertTrue(self.client.login(username='test_user')) col_no = 3 q = 'Bbbb' order = self.problem_columns[col_no - 1] url_main = reverse('problemset_main') response = self.client.get( url_main, {'q': q, 'foo': 'bar', 'order_by': order, 'desc': ''} ) self.assertEqual(response.status_code, 200) rows = self._get_table_contents(response.content.decode('utf-8')) self.assertEqual(len(rows), 1) html = response.content.decode('utf-8') pos = html.find('<tr>') for _ in range(col_no): pos = html.find('<th', pos) + 1 self.assertNotEqual(pos, -1) pos2 = html.find('</th>', pos) self.assertNotEqual(pos2, -1) th = html[pos:pos2] self.assertIn('q=' + q, th) self.assertIn('foo=bar', th) # The current column link should be to reverse ordering self.assertNotIn('desc', th) pos = html.find('<th', pos) + 1 self.assertNotEqual(pos, -1) pos2 = html.find('</th>', pos) self.assertNotEqual(pos2, -1) th = html[pos:pos2] self.assertIn('q=' + q, th) self.assertIn('foo=bar', th) # Any other column links should be to (default) descending ordering self.assertIn('desc', th)