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

OctaveCodeQuestion redux #932

Open
wants to merge 27 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
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
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,9 @@ jobs:
DEBIAN_FRONTEND: noninteractive
run: |
sudo apt-get install gettext
- name: Install Docker image for Octave; retains as necessary
run: |
docker pull davis68/relate-runcode-octave
- name: Run test suite
env:
RL_CI_TEST: ${{ matrix.suite }}
Expand Down
2 changes: 2 additions & 0 deletions course/page/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
ChoiceQuestion, MultipleChoiceQuestion, SurveyChoiceQuestion)
from course.page.code import (
PythonCodeQuestion, PythonCodeQuestionWithHumanTextFeedback)
from course.page.code import OctaveCodeQuestion
from course.page.upload import FileUploadQuestion

__all__ = (
Expand All @@ -47,6 +48,7 @@

"ChoiceQuestion", "SurveyChoiceQuestion", "MultipleChoiceQuestion",
"PythonCodeQuestion", "PythonCodeQuestionWithHumanTextFeedback",
"OctaveCodeQuestion",
"FileUploadQuestion",
)

Expand Down
154 changes: 150 additions & 4 deletions course/page/code.py
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ def debug_print(s):
docker_cnx.start(container_id)

container_props = docker_cnx.inspect_container(container_id)
(port_info,) = (container_props
(port_info, *_) = (container_props
["NetworkSettings"]["Ports"]["%d/tcp" %
CODE_QUESTION_CONTAINER_PORT])
port_host_ip = port_info.get("HostIp")
Expand Down Expand Up @@ -319,7 +319,7 @@ def check_timeout():
start_time = time()

debug_print("BEFPOST")
connection.request("POST", "/run-python", json_run_req, headers)
connection.request("POST", "/run-code", json_run_req, headers)
debug_print("AFTPOST")

http_response = connection.getresponse()
Expand Down Expand Up @@ -480,6 +480,7 @@ class CodeQuestion(PageBaseWithTitle, PageBaseWithValue):
Optional.
Symbols that the participant's code is expected to define.
These will be made available to the :attr:`test_code`.
Some remapping of types will be made between Octave and Python classes.

.. attribute:: test_code

Expand Down Expand Up @@ -539,7 +540,7 @@ class CodeQuestion(PageBaseWithTitle, PageBaseWithValue):
answer. This overrides the image set in the `local_settings.py`
configuration. The Docker image should provide two files; these are
supplied in RELATE's standard Python Docker image by `course/page/
code_run_backend_python.py` and `course/page/code_feedback.py`, for
code_run_backend_py.py` and `course/page/code_feedback.py`, for
instance. Consult `docker-image-run-py/docker-build.sh` for one
example of a local build. The Docker image should already be loaded
on the system (RELATE does not pull the image automatically).
Expand Down Expand Up @@ -712,7 +713,12 @@ def get_test_code(self):
if correct_code is None:
correct_code = ""

from .code_run_backend import substitute_correct_code_into_test_code
if self.page_desc.type in ["OctaveCodeQuestion"]:
from .code_run_backend_octave \
import substitute_correct_code_into_test_code
else:
from .code_run_backend_py \
import substitute_correct_code_into_test_code
return substitute_correct_code_into_test_code(test_code, correct_code)

@staticmethod
Expand Down Expand Up @@ -1497,4 +1503,144 @@ def grade(self, page_context, page_data, answer_data, grade_data):

# }}}

# {{{ octave code question


class OctaveCodeQuestion(CodeQuestion):
"""
An auto-graded question allowing an answer consisting of Octave code.
All user code as well as all code specified as part of the problem
is in Octave 4.2 or higher.
If you are not including the
:attr:`course.constants.flow_permission.change_answer`
permission for your entire flow, you likely want to
include this snippet in your question definition:
.. code-block:: yaml
access_rules:
add_permissions:
- change_answer
This will allow participants multiple attempts at getting
the right answer.
.. attribute:: id
|id-page-attr|
.. attribute:: type
``OctaveCodeQuestion``
.. attribute:: is_optional_page
|is-optional-page-attr|
.. attribute:: access_rules
|access-rules-page-attr|
.. attribute:: title
|title-page-attr|
.. attribute:: value
|value-page-attr|
.. attribute:: prompt
The page's prompt, written in :ref:`markup`.
.. attribute:: timeout
A number, giving the number of seconds for which setup code,
the given answer code, and the test code (combined) will be
allowed to run.
.. attribute:: setup_code
Optional.
Octave code to prepare the environment for the participants
answer.
.. attribute:: show_setup_code
Optional. ``True`` or ``False``. If true, the :attr:`setup_code`
will be shown to the participant.
.. attribute:: names_for_user
Optional.
Symbols defined at the end of the :attr:`setup_code` that will be
made available to the participant's code.
A deep copy (using the standard library function :func:`copy.deepcopy`)
of these values is made, to prevent the user from modifying trusted
state of the grading code.
.. attribute:: names_from_user
Optional.
Symbols that the participant's code is expected to define.
These will be made available to the :attr:`test_code`.
.. attribute:: test_code
Optional.
Code that will be run to determine the correctness of a
student-provided solution. Will have access to variables in
:attr:`names_from_user` (which will be *None*) if not provided. Should
never raise an exception.
This may contain the marker "###CORRECT_CODE###", which will
be replaced with the contents of :attr:`correct_code`, with
each line indented to the same depth as where the marker
is found. The line with this marker is only allowed to have
white space and the marker on it.
.. attribute:: show_test_code
Optional. ``True`` or ``False``. If true, the :attr:`test_code`
will be shown to the participant.
.. attribute:: correct_code_explanation
Optional.
Code that is revealed when answers are visible
(see :ref:`flow-permissions`). This is shown before
:attr:`correct_code` as an explanation.
.. attribute:: correct_code
Optional.
Code that is revealed when answers are visible
(see :ref:`flow-permissions`).
.. attribute:: initial_code
Optional.
Code present in the code input field when the participant first starts
working on their solution.
.. attribute:: data_files
Optional.
A list of file names in the :ref:`git-repo` whose contents will be made
available to :attr:`setup_code` and :attr:`test_code` through the
``data_files`` dictionary. (see below)
.. attribute:: single_submission
Optional, a Boolean. If the question does not allow multiple submissions
based on its :attr:`access_rules` (not the ones of the flow), a warning
is shown. Setting this attribute to True will silence the warning.
The following symbols are available in :attr:`setup_code` and :attr:`test_code`:
* ``GradingComplete``: An exception class that can be raised to indicated
that the grading code has concluded.
* ``feedback``: A class instance with the following interface::
feedback.set_points(0.5) # 0<=points<=1 (usually)
feedback.add_feedback("This was wrong")
# combines the above two and raises GradingComplete
feedback.finish(0, "This was wrong")
feedback.check_numpy_array_sanity(name, num_axes, data)
feedback.check_numpy_array_features(name, ref, data, report_failure=True)
feedback.check_numpy_array_allclose(name, ref, data,
accuracy_critical=True, rtol=1e-5, atol=1e-8,
report_success=True, report_failure=True)
# If report_failure is True, this function will only return
# if *data* passes the tests. It will return *True* in this
# case.
#
# If report_failure is False, this function will always return,
# and the return value will indicate whether *data* passed the
# accuracy/shape/kind checks.
feedback.check_list(name, ref, data, entry_type=None)
feedback.check_scalar(name, ref, data, accuracy_critical=True,
rtol=1e-5, atol=1e-8, report_success=True, report_failure=True)
# returns True if accurate
feedback.call_user(f, *args, **kwargs)
# Calls a user-supplied function and prints an appropriate
# feedback message in case of failure.
* ``data_files``: A dictionary mapping file names from :attr:`data_files`
to :class:`bytes` instances with that file's contents.
* ``user_code``: The user code being tested, as a string.
"""

@property
def language_mode(self):
return "octave"

@property
def container_image(self):
return settings.RELATE_DOCKER_RUNOC_IMAGE

@property
def suffix(self):
return ".m"

def __init__(self, vctx, location, page_desc, language_mode="octave"):
super(OctaveCodeQuestion, self).__init__(vctx, location, page_desc,
language_mode)

# }}}

# vim: foldmethod=marker
Loading