Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add tests for Submissions app #79

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ norecursedirs = ["media", "migrations", "sandboxing"]
testpaths = "tin"
addopts = "--doctest-modules tin --import-mode=importlib -n 8"
doctest_optionflags = "NORMALIZE_WHITESPACE NUMBER"
filterwarnings = [
"error",
'ignore:.*Tin is using the dummy sandboxing module. This is insecure.:',
"ignore::DeprecationWarning:twisted.*:",
]

[tool.coverage.run]
branch = true
Expand Down
55 changes: 0 additions & 55 deletions tin/apps/submissions/tests.py

This file was deleted.

Empty file.
26 changes: 26 additions & 0 deletions tin/apps/submissions/tests/test_models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from __future__ import annotations

from pathlib import Path

from ..models import Submission, upload_submission_file_path


def test_submission_save_file(settings, submission: Submission):
file_path = upload_submission_file_path(submission, "")
submission.save_file("something")
assert submission.file.name == file_path

submission_path = submission.file_path
assert submission_path is not None
submission_path = Path(submission_path)
assert submission_path == Path(settings.MEDIA_ROOT) / file_path
assert submission_path.exists()


def test_make_submission_backup(submission: Submission):
submission.create_backup_copy("HI")
backup_file = submission.backup_file_path
assert backup_file is not None
backup_path = Path(backup_file)
assert backup_path.exists()
assert backup_path.read_text("utf-8") == "HI"
190 changes: 190 additions & 0 deletions tin/apps/submissions/tests/test_submissions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
from __future__ import annotations

import json
import os
from typing import TYPE_CHECKING

import psutil
import pytest
from django.urls import reverse
from django.utils import timezone

from tin.tests import is_redirect, login

if TYPE_CHECKING:
from django.contrib.auth.models import AbstractBaseUser
from django.test import Client

from ...assignments.models import Assignment
from ...courses.models import Course
from ..models import Submission


@login("student")
@pytest.mark.parametrize(
("perm", "hidden", "archived"),
(
# normal
("-", False, False),
("r", False, False),
("w", False, False),
# archived
("-", True, True),
("r", False, True),
("w", False, True),
),
)
def test_see_submission_after_archived(
client: Client, course: Course, submission: Submission, perm: str, hidden: bool, archived: bool
):
course.permission = perm
course.archived = archived
course.save()

response = client.get(reverse("submissions:show", args=[submission.id]))
assert (response.status_code == 404) is hidden


@login("student")
def test_student_requests_kill(client: Client, submission: Submission):
response = client.post(reverse("submissions:kill", args=[submission.id]))
submission.refresh_from_db()
assert is_redirect(response)
assert submission.kill_requested


@login("teacher")
def test_teacher_requests_kill(client: Client, submission: Submission):
response = client.post(reverse("submissions:kill", args=[submission.id]))
submission.refresh_from_db()
assert is_redirect(response)
assert submission.kill_requested


@login("student")
def test_jsonapi_exists(client: Client, submission: Submission):
response = client.get(reverse("submissions:show_json", args=[submission.id]))
data = json.loads(response.content)
assert isinstance(data, dict)

# a nonexistent submission
response = client.get(reverse("submissions:show_json", args=[1000000]))
data = json.loads(response.content)
assert data == {"error": "Submission not found"}


@login("student")
@pytest.mark.parametrize("language", ("P", "J"))
def test_download_submission(
client: Client, assignment: Assignment, student: AbstractBaseUser, language: str
):
extension = "py" if language == "P" else "java"
assignment.filename = f"main.{extension}"
assignment.save()

submission = assignment.submissions.create(student=student)
# Yes this isn't valid Java ;)
code = "print('Hello World!')"
submission.save_file(code)

response = client.get(reverse("submissions:download", args=[submission.id]))

assert (
response["Content-Disposition"] == f'attachment; filename="{student.username}.{extension}"'
)
assert response.content.decode("utf-8") == submission.file_text_with_header


@login("teacher")
def test_comments(client: Client, teacher: AbstractBaseUser, submission: Submission):
submission.complete = True
submission.has_been_graded = True
submission.save()

# create comment
response = client.post(
reverse("submissions:comment", args=[submission.id]),
{"comment": "HiABC", "point_override": "1.0"},
)
assert is_redirect(response)
comments = submission.comments.filter(author=teacher).all()
assert len(comments) == 1
comment = comments[0]
assert comment.text == "HiABC"

# edit the comment
response = client.post(
reverse("submissions:edit_comment", args=[submission.id, comment.id]),
{"text": "Hello", "point_override": "1.0"},
)
assert is_redirect(response)
comment.refresh_from_db()
assert comment.text == "Hello"

# now delete it
response = client.post(reverse("submissions:delete_comment", args=[submission.id, comment.id]))
assert is_redirect(response)
assert not submission.comments.filter(author=teacher).exists()


@login("teacher")
def test_public_comment(client: Client, submission: Submission):
client.post(reverse("submissions:publish", args=[submission.id]))
assert submission.published_submission is not None

client.post(reverse("submissions:unpublish", args=[submission.id]))
assert submission.published_submission is None


@login("admin")
@pytest.mark.skipif(
psutil.pid_exists(2**22 + 1), reason="PID exists, so cannot check if it does not exist"
)
def test_set_aborted_complete_invalid_pid(client: Client, submission: Submission):
submission.complete = False
# on linux x64, 2^22 is the max PID so 2^22+1 should always not exist
submission.grader_pid = 2**22 + 1
submission.save()

client.post(reverse("submissions:set_aborted_complete"))
submission.refresh_from_db()
assert submission.complete, "Should mark submission as complete if process has ended"


def test_set_aborted_complete_valid_pid(client: Client, submission: Submission):
submission.complete = False
submission.grader_pid = os.getpid() # this PID exists
submission.save()

client.post(reverse("submissions:set_aborted_complete"))
assert not submission.complete, "Should not mark submission as complete while running"


@login("admin")
def test_set_past_timeout_complete_view(
client: Client, assignment: Assignment, submission: Submission
):
assignment.enable_grader_timeout = True
assignment.grader_timeout = 0
assignment.save()
submission.complete = False
submission.grader_start_time = 0
submission.save()

client.post(reverse("submissions:set_past_timeout_complete"))
submission.refresh_from_db()

assert submission.complete

submission.complete = False
# the difference between the timestamp between now and when the timeout is called
# should be close to 0, much less than the 1e12 grader timeout set
submission.grader_start_time = timezone.localtime().timestamp()
submission.save()
assignment.grader_timeout = 1_000_000_000_000
assignment.save()

client.post(reverse("submissions:set_past_timeout_complete"))
submission.refresh_from_db()

assert not submission.complete
13 changes: 8 additions & 5 deletions tin/apps/submissions/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,11 @@ def comment_view(request, submission_id):
raise http.Http404

comment = request.POST.get("comment", "")
point_override = request.POST.get("point_override", "")
point_override = request.POST.get("point_override")

if point_override is None:
return http.HttpResponseBadRequest("Missing point_override")

comment = Comment(
submission=submission,
author=request.user,
Expand Down Expand Up @@ -329,11 +333,10 @@ def set_aborted_complete_view(request):
submissions = Submission.objects.filter(complete=False, grader_pid__isnull=False)

for submission in submissions:
try:
psutil.Process(submission.grader_pid)
except psutil.NoSuchProcess:
if not psutil.pid_exists(submission.grader_pid):
submission.complete = True
submission.save(update_fields=["complete"])
submission.grader_pid = None
submission.save(update_fields=["complete", "grader_pid"])

return redirect("auth:index")

Expand Down
Loading