Miscellanea

Celery usages

This section briefly describes currently used applications of Celery across the infrastructure.

class oioioi.celery.apps.CeleryAppConfig(app_name, app_module)[source]

Getting current time

The main source of the current time in the request processing should be the request.timestamp variable. This variable contains the time when the request was initiated and, when used consistently, allows the admins to time travel.

Usage of timezone.now() is highly discouraged.

Current contest mechanism

oioioi.contests.current_contest.reverse(target, *args, **kwargs)[source]

A modified URL reverser that takes into account the current contest and generates URLs that are appropriately prefixed. With it we substitute the original urlresolvers.reverse function.

The choice of prefixing the URL with a particular contest ID (or not prefixing at all) by the function is made as follows:

  • If a contest_id kwarg is given which is not None then the URL, if succesfully reversed, is prefixed with it.
  • If a contest_id kwarg equal to None is given then the URL, if succesfully reversed, will not be prefixed.
  • If the kwarg isn’t given but a contest is active when calling the function then that contest is used for the generated URL.
  • If the above fails or there is no active contest then no contest will be used.

Our reverser uses the special structure of each app’s urls.py file:

  • Urls pointing to views that require a contest are defined in the contest_patterns pattern list. Those only have a contest-prefixed version.
  • Urls pointing to views that require no contest being active are defined in the noncontest_patterns pattern list. Those only have a non contest-prefixed version.
  • Urls pointing to views that can run both with and without current contest are defined in the urlpatterns pattern list. Those have both versions.

These files are preprocessed to be used by the reverser. Urls defined in oioioi.urls are not preprocessed, so they only have a non-prefixed version, even though they could exist within a contest.

Note that there is no point defining patterns that receive a contest_id kwarg. That particular kwarg is interpreted differently and will never be actually matched in the url pattern when reversing.

You need to take into account the behavior of reverse when defining your own custom urlconf (that means patterns lying outside an app’s urls.py file, e.g. for testing purposes), because it won’t be preprocessed. For that we created the make_patterns() function.

Exclusive contests

Checking for instance-level permissions in templates

To check for model-level permissions, one may use the standard Django mechanism. To check for instance-level permissions, use {% check_perm %} template tag.

oioioi.base.templatetags.check_perm.check_perm(parser, token)[source]

A template tag to look up object permissions.

The current user is tested agains the given permission on the given object. Current user is taken from the template context, so the django.contrib.auth.context_processors.auth template context processor must be present in settings.TEMPLATE_CONTEXT_PROCESSORS.

Usage:

{% load check_perm %}

{% check_perm "some_permission" for some_object as variable %}
{% if variable %}
<p>This is shown if the user has some_permission on some_object.</p>
{% endif %}

Conditions

To assign a condition to a view use the enforce_condition decorator:

Additionally, the enforce_condition decorator adds a condition attribute to the view, which can be later used by oioioi.base.menu.MenuRegistry.register_decorator().

Mixing it all together in a simple example:

@make_request_condition
def is_superuser(request):
    return request.user.is_superuser

@enforce_condition(is_superuser & ~is_superuser)
def not_accessible_view(request):
    pass

Switching users (su)

The SU app is used to change the current logged in user on-the-fly.

In order to achieve this goal, the module introduces concept of effective and real user privileges known from Unix-like systems. The effective user is stored in request.user field, while the real in request.real_user.

On-the-fly means that current session variables are preserved while changing effective user, which may be also a pitfall if some code stores there data directly connected with current user scope.

Zeus integration (zeus)

The zeus app is used for integration between oioioi and zeus, a system for grading distributed programing problems.

Zeus instances are configured in settings.ZEUS_INSTANCES, which is a dict mapping zeus_id - unique identifier of a zeus instance - to (zeus_url, zeus_login, zeus_secret) - base URL for zeus api (ZBU) and credentials. It is also possible to use a mock instance (ZeusTestServer) which allows manual testing for development purposes.

API specification

Communication with zeus is done over HTTPS protocol, in a REST-like style. Data is encoded using JSON. OIOIOI authorizes itself to zeus using HTTP Basic Authentication, with login and secret fixed for a zeus instance.

Prefix ? means optional attribute.

Sending submissions

Request:
POST ZBU/dcj_problem/zeus_problem_id/submissions
Data sent:
{
“submission_type”: submission_type :: Base64String(SMALL|LARGE),
“return_url”: return_url :: Base64String,
“username”: username :: Base64String,
“metadata”: metadata :: Base64String,
“source_code”: source_code :: Base64String,
“language”: source_language :: Base64String(CPP|...),
}
Result:

Code 200 and data:

{ “submission_id”: unique_job_id :: Uint }

or code 4xx|5xx and data:

{ ? “error”: error_description :: Base64String }

username and metadata fields are not used by Zeus and sent for debugging purposes only.

Receiving results

Zeus hits the “return_url” from submission data once it is graded.

Data received:
{
“compilation_output”: output :: Base64String,
}

in case of compilation failure or

{
“tests_info”: list_of_results :: [TestInfo],
}

in case of compilation success, where

TestInfo = {
“time_limit_ms”: execution_time_limit :: Int,
“memory_limit_byte”: memory_limit :: Int,
“verdict”: test_status :: Base64String(OK|WA|TLE|RE|RV|OLE|MSE|MCE),
“runtime”: max_of_times_at_all_machines :: Int,
“metadata”: more_data_about_test :: Base64String,
? “nof_nodes”: number_of_nodes :: Int,
}
Our response:

Code 200 and HttpResponse("Recorded!") or code 4xx|5xx and a lot of HTML (for example the one which normally displays a message Internal Server Error in a browser).

MSE and MCE are statuses meaning that size or count of outgoing messages sent by submitted program has exceeded the limit.

Metadata is subject to coordination between judges and contest admin. It may be passed through zeus, but in the most recent workflow we sent meaningless metadata to zeus and received meaningful metadata (zeus admins were provided with a file containing metadata for each test). It is designed to contain additional data about grouping, scoring etc. Currently we expect it to be in format:

test name,group name,max score

Test name will be shown to users. All tests with the same, non-empty group name will be grouped together. All tests in group shall have the same max score. Example tests are expected to be in the group 0.

Selenium tests

This module allows us to run selenium tests for OIOIOI. As opposed to regular django tests these are integration tests run under production-like environment, with external filetracker and database.

Test creation process

Tests may be created in two ways:
  • Simply write test in python, basing on existing one,
  • Use Selenium IDE plugin for Firefox.

Writing tests with Selenium IDE

With Selenium IDE it’s possible to record mouse and keyboard events in order to create a test.

Before you start look at Predefined selenium actions, there are some helpful methods that simplify common tasks like login or form submit.

Let’s assume you have recorded a test. What’s next?

  1. Export the test to proper python file.
    File -> Export Test Case As... -> Python 2 / unittest / WebDriver
  2. Remove setUp and tearDown functions and set OIOIOISeleniumTestCase as a superclass.

  3. Important! Even if test is doing what it’s supposed to do, there’s still some work left. Selenium recorder will recognize elements in the easiest possible way. That’s not what we want, things may change and test will broke because of insufficient description. Simplified html access section may be helpful now. The goal is to find elements that are poorly accessed by Selenium (for ex. by a class that is not guaranteed to be unique). Usually it applies to tables, buttons. The best solution is to access elements by ID’s, if there’s no corresponding ID, just do the best you can. It’s recommended to find elements by xpath, that way you can nicely access nested elements with specific classes, ids or tags.

Running Selenium tests

Selenium tests are excluded from default Django tests because they need different environment. In OIOIOI’s root directory there’s script test_selenium.sh which runs all Selenium tests. Any arguments passed to it are forwarded to nose. This script is responsible for launching docker containers with fresh OIOIOI and stuff needed by Selenium. By default it take some time to perform whole process so you may customize script to just wipe data between launches etc.

What to do when tests are not working

If there were errors in testing, the first place to go is test_screenshots.tar.gz archive in project root directory. Test engine makes screenshot on every failure so you can determine what’s gone wrong. When screen looks like page is not loaded properly, consider adding wait_for_load() to wait for it.

Predefined selenium actions

Methods to perform common actions.

If you write some code that fits this section, don’t hesitate to add it there!

Simplified html access

If you write some code that fits this section, don’t hesitate to add it there!