import re
from datetime import datetime, timezone # pylint: disable=E0611
import urllib.parse
from django.contrib.admin.utils import quote
from django.contrib.auth.models import User
from django.core.files.base import ContentFile
from django.test import RequestFactory
from django.test.utils import override_settings
from django.urls import reverse
from oioioi.base.tests import TestCase, fake_time, fake_timezone_now
from oioioi.contests.models import (
Contest,
ProblemInstance,
Submission,
UserResultForProblem,
)
from oioioi.contests.scores import IntegerScore
from oioioi.pa import utils
from oioioi.pa.controllers import A_PLUS_B_RANKING_KEY, B_RANKING_KEY
from oioioi.pa.models import PAProblemInstanceData, PARegistration
from oioioi.pa.score import PAScore, ScoreDistribution
from oioioi.participants.models import Participant, TermsAcceptedPhrase
from oioioi.problems.models import Problem
[docs]class TestPAScore(TestCase):
[docs] def test_score_distribution(self):
dist1 = ScoreDistribution([1] + [0] * 9)
dist2 = ScoreDistribution([0] + [10] * 9)
dist_null = ScoreDistribution([0] * 10)
self.assertLess(dist2, dist1)
self.assertLess(dist1, dist1 + dist2)
self.assertLess(dist2 + dist2, dist1)
self.assertLess(dist_null, dist1)
self.assertLess(dist_null, dist2)
self.assertEqual(dist_null, ScoreDistribution())
self.assertEqual(dist_null + dist_null, dist_null)
self.assertEqual(dist1 + dist_null, dist1)
self.assertEqual(
dist1._to_repr(),
'00001:00000:00000:00000:00000:00000:00000:00000:00000:00000',
)
self.assertEqual(
dist2._to_repr(),
'00000:00010:00010:00010:00010:00010:00010:00010:00010:00010',
)
self.assertEqual(
(dist1 + dist2)._to_repr(),
'00001:00010:00010:00010:00010:00010:00010:00010:00010:00010',
)
self.assertEqual(dist1, ScoreDistribution._from_repr(dist1._to_repr()))
self.assertEqual(dist2, ScoreDistribution._from_repr(dist2._to_repr()))
self.assertEqual(
repr(dist1),
'ScoreDistribution(10: 1, 9: 0, 8: 0, 7: 0, 6: 0, 5: 0, 4: 0, '
'3: 0, 2: 0, 1: 0)',
)
[docs] def test_pa_score(self):
score = [PAScore(IntegerScore(x)) for x in range(0, 11)]
self.assertLess(score[0], score[5])
self.assertLess(score[5], score[10])
self.assertLess(score[5] + score[5], score[10])
self.assertLess(score[5] + score[5], score[2] + score[2] + score[6])
self.assertLess(score[10], score[2] + score[4] + score[5])
self.assertLess(score[2] + score[2] + score[6], score[1] + score[3] + score[6])
dist1 = ScoreDistribution([0] * 8 + [2, 4])
dist2 = ScoreDistribution([0] * 8 + [1, 6])
score1 = PAScore(IntegerScore(8), dist1)
score2 = PAScore(IntegerScore(8), dist2)
self.assertLess(score2, score1)
score3 = (
score[10] + score[10] + score[10] + score[4] + score[2] + score1 + score2
)
self.assertEqual(score3, (3 * 10 + 4 + 2 + 2 * 8))
self.assertEqual(
repr(score3),
'PAScore(IntegerScore(52), ScoreDistribution(10: 3, 9: 0, 8: '
'0, 7: 0, 6: 0, 5: 0, 4: 1, 3: 0, 2: 4, 1: 10))',
)
self.assertEqual(
score3._to_repr(),
'0000000000000000052;00003:00000:'
'00000:00000:00000:00000:00001:00000:00004:00010',
)
self.assertEqual(score3, PAScore._from_repr(score3._to_repr()))
[docs]class TestPARoundTimes(TestCase):
[docs] fixtures = ['test_users', 'test_pa_contest']
[docs] def test_round_states(self):
contest = Contest.objects.get()
controller = contest.controller
not_last_submission = Submission.objects.get(id=6)
# user's last submission
not_my_submission = Submission.objects.get(id=10)
user = User.objects.get(username='test_user')
def check_round_state(date, expected):
request = RequestFactory().request()
request.contest = contest
request.user = user
request.timestamp = date
self.assertTrue(self.client.login(username='test_user'))
with fake_timezone_now(date):
url = reverse(
'ranking', kwargs={'contest_id': 'c', 'key': A_PLUS_B_RANKING_KEY}
)
response = self.client.get(url)
if expected[0]:
self.assertContains(response, 'taskA1')
else:
self.assertNotContains(response, 'taskA1')
self.assertEqual(
expected[1], controller.can_see_source(request, not_my_submission)
)
self.assertEqual(
False, controller.can_see_source(request, not_last_submission)
)
dates = [
datetime(2012, 6, 1, 0, 0, tzinfo=timezone.utc),
datetime(2012, 8, 1, 0, 0, tzinfo=timezone.utc),
datetime(2012, 10, 1, 0, 0, tzinfo=timezone.utc),
]
# 1) results date of round 1
# 2) public results date of round 1
# 3) public results date of all rounds
#
# ============== ==============
# can: | see ranking | see solutions of other participants
# ============== ==============
# 1 -> | | |
# | False | False |
# 2 -> | | |
# | True | False |
# 3 -> | | |
# | True | True |
# | | |
# ============== ==============
expected = [[False, False], [True, False], [True, True]]
for date, exp in zip(dates, expected):
check_round_state(date, exp)
[docs]class TestPARanking(TestCase):
[docs] fixtures = ['test_users', 'test_pa_contest']
[docs] def _ranking_url(self, key):
contest = Contest.objects.get()
return reverse('ranking', kwargs={'contest_id': contest.id, 'key': key})
[docs] def test_divisions(self):
def check_visibility(good_keys, response):
division_for_pi = {1: 'A', 2: 'A', 3: 'B', 4: 'B', 5: 'NONE'}
for key, div in division_for_pi.items():
p = ProblemInstance.objects.get(pk=key)
if div in good_keys:
self.assertContains(response, p.short_name)
else:
self.assertNotContains(response, p.short_name)
self.assertTrue(self.client.login(username='test_user'))
with fake_timezone_now(datetime(2013, 1, 1, 0, 0, tzinfo=timezone.utc)):
response = self.client.get(self._ranking_url(B_RANKING_KEY))
check_visibility(['B'], response)
response = self.client.get(self._ranking_url(A_PLUS_B_RANKING_KEY))
check_visibility(['A', 'B'], response)
# Round 3 is trial
response = self.client.get(self._ranking_url(3))
check_visibility(['NONE'], response)
[docs] def test_no_zero_scores_in_ranking(self):
self.assertTrue(self.client.login(username='test_user'))
with fake_time(datetime(2013, 1, 1, 0, 0, tzinfo=timezone.utc)):
response = self.client.get(self._ranking_url(3))
# Test User should be present in the ranking.
self.assertTrue(re.search(b'<td[^>]*>Test User</td>', response.content))
# Test User 2 scored 0 points for the only task in the round.
self.assertFalse(re.search(b'<td[^>]*>Test User 2</td>', response.content))
[docs] def test_ranking_ordering(self):
def check_order(response, expected):
prev_pos = 0
for user in expected:
pattern = b'<td[^>]*>%s</td>' % (user,)
pattern_match = re.search(pattern, response.content)
self.assertTrue(pattern_match)
self.assertContains(response, user)
pos = pattern_match.start()
self.assertGreater(
pos, prev_pos, msg=('User %s has incorrect ' 'position' % (user,))
)
prev_pos = pos
self.assertTrue(self.client.login(username='test_user'))
with fake_timezone_now(datetime(2013, 1, 1, 0, 0, tzinfo=timezone.utc)):
# 28 (10, 8, 6, 4), 28 (9, 9, 7, 3), 10 (10)
response = self.client.get(self._ranking_url(A_PLUS_B_RANKING_KEY))
check_order(response, [b'Test User', b'Test User 2', b'Test User 3'])
self.assertContains(response, b'28</td>')
# 10 (10), 10 (7, 3), 10 (6, 4)
response = self.client.get(self._ranking_url(B_RANKING_KEY))
check_order(response, [b'Test User 3', b'Test User 2', b'Test User'])
self.assertNotContains(response, b'28</td>')
[docs]class TestPARegistration(TestCase):
[docs] fixtures = ['test_users', 'test_contest', 'test_terms_accepted_phrase']
[docs] def setUp(self):
contest = Contest.objects.get()
contest.controller_name = 'oioioi.pa.controllers.PAContestController'
contest.save()
self.reg_data = {
'address': 'The Castle',
'postal_code': '31-337',
'city': 'Camelot',
't_shirt_size': 'L',
'job': 'AS',
'job_name': 'WSRH',
'terms_accepted': 't',
}
[docs] def test_default_terms_accepted_phrase(self):
TermsAcceptedPhrase.objects.get().delete()
contest = Contest.objects.get()
url = reverse('participants_register', kwargs={'contest_id': contest.id})
self.assertTrue(self.client.login(username='test_user'))
response = self.client.get(url)
self.assertContains(
response,
'I declare that I have read the contest rules and '
'the technical arrangements. I fully understand '
'them and accept them unconditionally.',
)
[docs] def test_participants_registration(self):
contest = Contest.objects.get()
user = User.objects.get(username='test_user')
url = reverse('participants_register', kwargs={'contest_id': contest.id})
self.assertTrue(self.client.login(username='test_user'))
response = self.client.get(url)
self.assertContains(response, 'Postal code')
self.assertContains(response, 'Test terms accepted')
user.first_name = 'Sir Lancelot'
user.last_name = 'du Lac'
user.save()
response = self.client.post(url, self.reg_data)
self.assertEqual(302, response.status_code)
registration = PARegistration.objects.get(participant__user=user)
self.assertEqual(registration.address, self.reg_data['address'])
[docs] def test_contest_info(self):
contest = Contest.objects.get()
user = User.objects.get(username='test_user')
p = Participant(contest=contest, user=user)
p.save()
PARegistration(participant_id=p.id, **self.reg_data).save()
url = reverse('contest_info', kwargs={'contest_id': contest.id})
data = self.client.get(url).json()
self.assertEqual(data['users_count'], 1)
[docs]class TestPAScorer(TestCase):
[docs] t_results_ok = (
(
{'exec_time_limit': 100, 'max_score': 100},
{'result_code': 'OK', 'time_used': 0},
),
(
{'exec_time_limit': 100, 'max_score': 10},
{'result_code': 'OK', 'time_used': 99},
),
(
{'exec_time_limit': 1000, 'max_score': 0},
{'result_code': 'OK', 'time_used': 123},
),
)
[docs] t_expected_ok = [
(IntegerScore(1), IntegerScore(1), 'OK'),
(IntegerScore(1), IntegerScore(1), 'OK'),
(IntegerScore(0), IntegerScore(0), 'OK'),
]
[docs] t_results_wrong = [
(
{'exec_time_limit': 100, 'max_score': 100},
{'result_code': 'WA', 'time_used': 75},
),
(
{'exec_time_limit': 100, 'max_score': 0},
{'result_code': 'RV', 'time_used': 75},
),
]
[docs] t_expected_wrong = [
(IntegerScore(0), IntegerScore(1), 'WA'),
(IntegerScore(0), IntegerScore(0), 'RV'),
]
[docs] def test_pa_test_scorer(self):
results = list(map(utils.pa_test_scorer, *list(zip(*self.t_results_ok))))
self.assertEqual(self.t_expected_ok, results)
results = list(map(utils.pa_test_scorer, *list(zip(*self.t_results_wrong))))
self.assertEqual(self.t_expected_wrong, results)
[docs]class TestPAResults(TestCase):
[docs] fixtures = ['test_users', 'test_pa_contest']
[docs] def test_pa_user_results(self):
contest = Contest.objects.get()
user = User.objects.get(username='test_user')
old_results = sorted(
[result.score for result in UserResultForProblem.objects.filter(user=user)]
)
for pi in ProblemInstance.objects.all():
contest.controller.update_user_results(user, pi)
new_results = sorted(
[result.score for result in UserResultForProblem.objects.filter(user=user)]
)
self.assertEqual(old_results, new_results)
@override_settings(
PROBLEM_PACKAGE_BACKENDS=('oioioi.problems.tests.DummyPackageBackend',)
)
[docs]class TestPADivisions(TestCase):
[docs] fixtures = ['test_users', 'test_contest']
[docs] def test_prolem_upload(self):
contest = Contest.objects.get()
contest.controller_name = 'oioioi.pa.controllers.PAContestController'
contest.save()
self.assertTrue(self.client.login(username='test_admin'))
url = (
reverse('add_or_update_problem', kwargs={'contest_id': contest.id})
+ '?'
+ urllib.parse.urlencode({'key': 'upload'})
)
response = self.client.get(url)
# "NONE" is the default division
self.assertContains(response, '<option value="NONE" selected>None</option>')
data = {
'package_file': ContentFile('eloziom', name='foo'),
'visibility': Problem.VISIBILITY_FRIENDS,
'division': 'A',
}
response = self.client.post(url, data, follow=True)
self.assertEqual(response.status_code, 200)
pid = PAProblemInstanceData.objects.get()
problem = Problem.objects.get()
self.assertEqual(pid.division, 'A')
self.assertEqual(pid.problem_instance.problem, problem)
url = (
reverse('add_or_update_problem', kwargs={'contest_id': contest.id})
+ '?'
+ urllib.parse.urlencode({'problem': problem.id, 'key': 'upload'})
)
response = self.client.get(url)
self.assertContains(response, '<option value="A" selected>A</option>')
[docs]class TestPAContestInfo(TestCase):
[docs] fixtures = ['test_users', 'test_pa_contest']
[docs] def test_contest_info_anonymous(self):
c = Contest.objects.get()
url = reverse('contest_info', kwargs={'contest_id': c.id})
self.client.logout()
response = self.client.get(url).json()
self.assertEqual(response['users_count'], 2)
[docs] def test_cross_origin(self):
c = Contest.objects.get()
url = reverse('contest_info', kwargs={'contest_id': c.id})
response = self.client.get(url)
self.assertEqual(response['Access-Control-Allow-Origin'], '*')
[docs]class TestPASafeExecModes(TestCase):
[docs] fixtures = ['test_pa_contests_safe_exec_mode']
[docs] def test_pa_quals_controller_safe_exec_mode(self):
c = Contest.objects.get(pk="quals")
self.assertEqual(c.controller.get_safe_exec_mode(), 'cpu')
[docs] def test_pa_finals_controller_safe_exec_mode(self):
c = Contest.objects.get(pk="finals")
self.assertEqual(c.controller.get_safe_exec_mode(), 'cpu')
[docs]class TestPAAdmin(TestCase):
[docs] fixtures = [
'test_users',
'test_contest',
'test_pa_registration',
'test_permissions',
]
[docs] def setUp(self):
contest = Contest.objects.get()
contest.controller_name = 'oioioi.pa.controllers.PAContestController'
contest.save()
[docs] def test_terms_accepted_phrase_inline_admin_permissions(self):
PARegistration.objects.all().delete()
# Logging as superuser.
self.assertTrue(self.client.login(username='test_admin'))
self.client.get('/c/c/') # 'c' becomes the current contest
url = reverse('oioioiadmin:contests_contest_change', args=(quote('c'),))
response = self.client.get(url)
self.assertContains(response, 'Text asking participant to accept contest terms')
# Checks if the field is editable.
self.assertContains(response, 'id_terms_accepted_phrase-0-text')
# Logging as contest admin.
self.assertTrue(self.client.login(username='test_contest_admin'))
self.client.get('/c/c/') # 'c' becomes the current contest
url = reverse('oioioiadmin:contests_contest_change', args=(quote('c'),))
response = self.client.get(url)
self.assertContains(response, 'Text asking participant to accept contest terms')
# Checks if the field is editable.
self.assertContains(response, 'id_terms_accepted_phrase-0-text')
[docs] def test_terms_accepted_phrase_inline_edit_restrictions(self):
self.assertTrue(self.client.login(username='test_admin'))
self.client.get('/c/c/') # 'c' becomes the current contest
url = reverse('oioioiadmin:contests_contest_change', args=(quote('c'),))
response = self.client.get(url)
self.assertContains(response, 'Text asking participant to accept contest terms')
# Checks if the field is not editable.
self.assertNotContains(response, 'id_terms_accepted_phrase-0-text')