Skip to content

Commit

Permalink
SIO-1790 Global time & memory limits
Browse files Browse the repository at this point in the history
Adding global time and memory limits for tests.
They're enforced in forms and probleminstance creation, not
by checkers.

Change-Id: I77c81dc242c45e80b13965fa3e1528cca944ddce
  • Loading branch information
dolorem committed Apr 20, 2016
1 parent df5b05c commit 905d364
Show file tree
Hide file tree
Showing 7 changed files with 179 additions and 2 deletions.
4 changes: 4 additions & 0 deletions oioioi/default_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,10 @@
# execution (in a sandboxed environment, if USE_UNSAFE_EXEC is set to False).
USE_SINOLPACK_MAKEFILES = True

# Upper bounds for tests' time [ms] and memory [KiB] limits.
MAX_TEST_TIME_LIMIT_PER_PROBLEM = 1000 * 60 * 60 * 30
MAX_MEMORY_LIMIT_FOR_TEST = 256 * 1024

FILETRACKER_SERVER_ENABLED = False
FILETRACKER_LISTEN_ADDR = '127.0.0.1'
FILETRACKER_LISTEN_PORT = 9999
Expand Down
4 changes: 4 additions & 0 deletions oioioi/deployment/settings.py.template
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,10 @@ USE_LOCAL_COMPILERS = True
# execution (in a sandboxed environment, if USE_UNSAFE_EXEC is set to False).
USE_SINOLPACK_MAKEFILES = False

#Upper bounds for tests' time [ms] and memory [KiB] limits.
MAX_TEST_TIME_LIMIT_PER_PROBLEM = 1000 * 60 * 60 * 30
MAX_MEMORY_LIMIT_FOR_TEST = 256 * 1024

# Uncomment and edit this line to limit availability of programming languages.
#SUBMITTABLE_EXTENSIONS = {'C': ['c'], 'C++': ['cpp', 'cc'], 'Pascal': ['pas']}

Expand Down
32 changes: 31 additions & 1 deletion oioioi/programs/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from django.db.models import Q
from django.template.response import TemplateResponse
from django.conf.urls import patterns, url
from django.core.exceptions import PermissionDenied
from django.core.exceptions import ValidationError
from django.utils.translation import ugettext_lazy as _
from django.shortcuts import redirect
from django.core.urlresolvers import reverse
Expand All @@ -22,6 +22,35 @@
LibraryProblemData, ReportActionsConfig
from collections import defaultdict

from django.forms.models import BaseInlineFormSet


class TimeLimitFormset(BaseInlineFormSet):
def get_time_limit_sum(self):
time_limit_sum = 0
for test in self.cleaned_data:
time_limit_sum += test['time_limit']
return time_limit_sum

def validate_time_limit_sum(self):
time_limit_per_problem = settings.MAX_TEST_TIME_LIMIT_PER_PROBLEM

if self.get_time_limit_sum() > time_limit_per_problem:
time_limit_sum_rounded = (self.get_time_limit_sum() + 999) / 1000.0
limit_seconds = time_limit_per_problem / 1000.0

raise ValidationError(_(
"Sum of time limits for all tests is too big. It's %(sum)ds, "
"but it shouldn't exceed %(limit)ds."
) % {'sum': time_limit_sum_rounded, 'limit': limit_seconds})

def clean(self):
try:
self.validate_time_limit_sum()
return self.cleaned_data
except AttributeError:
pass


class TestInline(admin.TabularInline):
model = Test
Expand All @@ -34,6 +63,7 @@ class TestInline(admin.TabularInline):
readonly_fields = ('name', 'kind', 'group', 'input_file_link',
'output_file_link')
ordering = ('kind', 'order', 'name')
formset = TimeLimitFormset

class Media(object):
css = {
Expand Down
12 changes: 11 additions & 1 deletion oioioi/programs/models.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from nose.tools import nottest
from django.conf import settings
from django.core.exceptions import ValidationError
from django.db import models, transaction
from django.utils.translation import ugettext_lazy as _
Expand All @@ -24,6 +25,14 @@ def validate_time_limit(value):
raise ValidationError(_("Time limit must be a positive number."))


def validate_memory_limit(value):
if value is None or value <= 0:
raise ValidationError(_("Memory limit must be a positive number."))
if value > settings.MAX_MEMORY_LIMIT_FOR_TEST:
raise ValidationError(_("Memory limit mustn't be greater than %dKiB."
% settings.MAX_MEMORY_LIMIT_FOR_TEST))


@nottest
class Test(models.Model):
problem_instance = models.ForeignKey(ProblemInstance)
Expand All @@ -37,7 +46,8 @@ class Test(models.Model):
time_limit = models.IntegerField(verbose_name=_("time limit (ms)"),
null=True, blank=False, validators=[validate_time_limit])
memory_limit = models.IntegerField(verbose_name=_("memory limit (KiB)"),
null=True, blank=True)
null=True, blank=True,
validators=[validate_memory_limit])
max_score = models.IntegerField(verbose_name=_("score"),
default=10)
order = models.IntegerField(default=0)
Expand Down
84 changes: 84 additions & 0 deletions oioioi/programs/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@

from datetime import datetime

from django.conf import settings
from django.test import TestCase, RequestFactory
from django.utils.timezone import utc
from django.utils.html import strip_tags, escape
from django.contrib.auth.models import User
from django.core.urlresolvers import reverse
from django.core.files.base import ContentFile
from django.test.utils import override_settings

from oioioi.filetracker.tests import TestStreamingMixin
from oioioi.programs import utils
Expand Down Expand Up @@ -844,3 +846,85 @@ def test_rejudge_new(self):
{},
['0', '1b', '3'],
['1a', '2'])


class TestLimitsLimits(TestCase):
fixtures = ['test_users', 'test_contest', 'test_full_package',
'test_problem_instance', 'test_submission']

form_data = {
'test_set-TOTAL_FORMS': 6,
'test_set-INITIAL_FORMS': 6,
'test_set-MIN_NUM_FORMS': 0,
'test_set-MAX_NUM_FORMS': 0,
'test_set-0-time_limit': 1000,
'test_set-0-memory_limit': 1,
'test_set-0-max_score': 10,
'test_set-0-is_active': 'on',
'test_set-0-problem_instance': 1,
'test_set-0-id': 1,
'test_set-1-time_limit': 1000,
'test_set-1-memory_limit': 10,
'test_set-1-max_score': 10,
'test_set-1-is_active': 'on',
'test_set-1-problem_instance': 1,
'test_set-1-id': 4,
'test_set-2-time_limit': 1000,
'test_set-2-memory_limit': 10,
'test_set-2-max_score': 10,
'test_set-2-is_active': 'on',
'test_set-2-problem_instance': 1,
'test_set-2-id': 2,
'test_set-3-time_limit': 1001,
'test_set-3-memory_limit': 10,
'test_set-3-max_score': 10,
'test_set-3-is_active': 'on',
'test_set-3-problem_instance': 1,
'test_set-3-id': 3,
'test_set-4-time_limit': 1000,
'test_set-4-memory_limit': 101,
'test_set-4-max_score': 10,
'test_set-4-is_active': 'on',
'test_set-4-problem_instance': 1,
'test_set-4-id': 5,
'test_set-5-time_limit': 1000,
'test_set-5-memory_limit': 101,
'test_set-5-max_score': 10,
'test_set-5-is_active': 'on',
'test_set-5-problem_instance': 1,
'test_set-5-id': 6,
'test_set-__prefix__-time-limit': '',
'test_set-__prefix__-memory-limit': '',
'test_set-__prefix__-max_score': 10,
'test_set-__prefix__-is_active': 'on',
'test_set-__prefix__-problem_instance': 3,
'test_set-__prefix__-id': '',
'_continue': 'Zapisz+i+kontynuuj+edycj%C4%99',
'round': 1,
'short_name': 'zad1',
'submissions_limit': 10,
'paprobleminstancedata-TOTAL_FORMS': 1,
'paprobleminstancedata-INITIAL_FORMS': 0,
'paprobleminstancedata-MIN_NUM_FORMS': 0,
'paprobleminstancedata-MAX_NUM_FORMS': 1
}

def edit_settings(self):
self.client.login(username='test_admin')
self.client.get('/c/c/')
return self.client.post(
reverse('oioioiadmin:contests_probleminstance_change',
kwargs={'contest_id': 'c'}, args=[1]),
self.form_data, follow=True)

@override_settings(MAX_TEST_TIME_LIMIT_PER_PROBLEM=6000)
def test_time_limit(self):
response = self.edit_settings()
self.assertIn("Sum of time limits for all tests is too big. It&#39;s "
"7s, but it shouldn&#39;t exceed 6s.", response.content)

@override_settings(MAX_MEMORY_LIMIT_FOR_TEST=100)
def test_memory_limit(self):
response = self.edit_settings()
self.assertIn("Memory limit mustn&#39;t be greater than %dKiB."
% settings.MAX_MEMORY_LIMIT_FOR_TEST, response.content)
12 changes: 12 additions & 0 deletions oioioi/sinolpack/package.py
Original file line number Diff line number Diff line change
Expand Up @@ -562,6 +562,18 @@ def _generate_tests(self, total_score=100):
if instance:
created_tests.append(instance)

time_limit_sum = 0
for test in created_tests:
time_limit_sum += test.time_limit
if time_limit_sum > settings.MAX_TEST_TIME_LIMIT_PER_PROBLEM:
time_limit_sum_rounded = (time_limit_sum + 999) / 1000.0
limit_seconds = settings.MAX_TEST_TIME_LIMIT_PER_PROBLEM / 1000.0

raise ProblemPackageError(_(
"Sum of time limits for all tests is too big. It's %(sum)ds, "
"but it shouldn't exceed %(limit)ds."
) % {'sum': time_limit_sum_rounded, 'limit': limit_seconds})

# Check test inputs
self._verify_ins(created_tests)

Expand Down
33 changes: 33 additions & 0 deletions oioioi/sinolpack/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -486,3 +486,36 @@ def test_judging(self):

urc = UserResultForContest.objects.get()
self.assertEqual(urc.score, IntegerScore(34))


class TestLimits(TestCase):
fixtures = ['test_users', 'test_contest']

def upload_package(self):
ProblemInstance.objects.all().delete()
contest = Contest.objects.get()
filename = get_test_filename('test_simple_package.zip')

self.client.login(username='test_admin')
url = reverse('oioioiadmin:problems_problem_add')
response = self.client.get(url, {'contest_id': contest.id},
follow=True)
url = response.redirect_chain[-1][0]

self.assertEqual(response.status_code, 200)
self.assertIn('problems/add_or_update.html',
[getattr(t, 'name', None) for t in response.templates])
return self.client.post(url,
{'package_file': open(filename, 'rb')}, follow=True)

@override_settings(MAX_TEST_TIME_LIMIT_PER_PROBLEM=2000)
def test_time_limit(self):
response = self.upload_package()
self.assertIn("Sum of time limits for all tests is too big. It&#39;s "
"50s, but it shouldn&#39;t exceed 2s.", response.content)

@override_settings(MAX_MEMORY_LIMIT_FOR_TEST=10)
def test_memory_limit(self):
response = self.upload_package()
self.assertIn("Memory limit mustn&#39;t be greater than %dKiB"
% settings.MAX_MEMORY_LIMIT_FOR_TEST, response.content)

0 comments on commit 905d364

Please sign in to comment.