Exemple #1
0
 def _feedback(self):
     case = parse_iospec(self.case)
     answer_key = parse_iospec(self.answer_key)
     return iofeedback.Feedback(
         case, answer_key,
         status=self.status, hint=self.hint, message=self.message
     )
Exemple #2
0
    def clean(self):
        """
        Validate the iospec_source field.
        """

        super().clean()

        # We first should check if the iospec_source has been changed and would
        # require a possibly expensive validation.
        source = self.iospec_source
        iospec_hash = md5hash(source)
        if self.iospec_hash != iospec_hash:
            try:
                self.iospec = iospec = parse_iospec(self.iospec_source)
            except Exception as ex:
                raise ValidationError(
                    {'iospec_source': _('invalid iospec syntax: %s' % ex)})

            # Now we check if the new iospec requires an answer key code and
            # if it has some answer key defined
            self.__iospec_updated = True
            return
            if (not iospec.is_expanded) and not self.answers.has_program():
                raise ValidationError({
                    'iospec_source':
                    _('You iospec definition uses a command or an @input block '
                      'and thus requires an example grading code. Please define '
                      'an "Answer Key" item with source code for at least one '
                      'programming language.')
                })
Exemple #3
0
    def iospec(self, language, force_expanded=True):
        """
        Return the IoSpec object associated with the given language.

        If ``force_expanded=True``, raises an error if it cannot find an
        expanded iospec object. This happens if there is no answer key defined
        for the question and if the main iospec requires command expansions.
        """

        try:
            return self.get(language=language).iospec
        except self.model.DoesNotExist:
            sources = self.values_list('iospec_source', flat=True).distinct()
            if sources:
                source, *_ = sources
                return parse_iospec(source)
            else:
                iospec = self.instance.iospec
                if force_expanded and not iospec.is_simple:
                    source = self.instance.iospec_source
                    source = ('    ' + line for line in source.splitlines())
                    source = '\n'.join(source)
                    raise TypeError('Could not expand iospec source:\n %s' %
                                    source)
                return iospec
Exemple #4
0
    def grade(self, response, error=None):
        """Grade the given response object and return the corresponding
        feedback object."""

        try:
            key = self.answer_keys.get(language=response.language)
            key.assure_is_valid(error)
            iospec_data = key.iospec
        except CodingIoAnswerKey.DoesNotExist:
            self.update_keys()

            # Get all sources
            iospec_sources = self.answer_keys.filter(is_valid=True)\
                .values_list('iospec_source', flat=True)
            iospec_sources = set(iospec_sources)

            # Check if there is only a single distinct source
            if not iospec_sources:
                iospec_data = self.iospec.copy()
                iospec_data.expand_inputs()
                if not all(isinstance(x, SimpleTestCase) for x in iospec_data):
                    raise (
                        error or
                        CodingIoAnswerKey.ValidationError(iospec_data.pformat())
                    )
            elif len(iospec_sources) == 1:
                iospec_data = parse_iospec(next(iter(iospec_sources)))
            else:
                raise error or CodingIoAnswerKey.ValidationError(iospec_sources)

        # Construct ejudge feedback object
        lang = response.language.ref
        source = response.source
        return grade_code(source, iospec_data, lang=lang)
Exemple #5
0
    def get_expand_post_tests(self):
        """
        Return an IoSpec object with the result of post tests expansions.
        """

        state = self.get_current_test_state()
        source = state.post_tests_source_expansion
        return parse_iospec(source)
Exemple #6
0
 def expanded_tree(self):
     if self.iospec_expanded:
         return parse_iospec(self.iospec_expanded)
     else:
         tree = self.expand_iospec()
         self.iospec_expanded = tree.source()
         self.save(update_fields=['iospec_expanded'])
         return tree
Exemple #7
0
    def get_expand_post_tests(self):
        """
        Return an IoSpec object with the result of post tests expansions.
        """

        state = self.get_current_test_state()
        source = state.post_tests_source_expansion
        return parse_iospec(source)
Exemple #8
0
 def post_tests(self):
     try:
         return self._post_tests
     except AttributeError:
         if self.post_tests_source:
             post_tests = parse_iospec(self.post_tests_source)
         else:
             post_tests = IoSpec()
         self._post_tests = ejudge.combine_iospec(self.pre_tests,
                                                  post_tests)
         return self._post_tests
Exemple #9
0
 def post_tests(self):
     try:
         return self._post_tests
     except AttributeError:
         if self.post_tests_source:
             post_tests = parse_iospec(self.post_tests_source)
         else:
             post_tests = IoSpec()
         self._post_tests = ejudge.combine_iospec(
             self.pre_tests, post_tests)
         return self._post_tests
Exemple #10
0
    def validate_tests(self):
        """
        Triggered when (pre|post)_test_source changes or on the first time the
        .clean() method is called.
        """

        # Check if new source is valid
        for attr in ['pre_tests_source', 'post_tests_source']:
            try:
                source = getattr(self, attr)
                if source:
                    iospec = parse_iospec(source)
                else:
                    iospec = None
                setattr(self, attr[:-7], iospec)
            except Exception as ex:
                self.clear_tests()
                raise ValidationError(
                    {attr: _('invalid iospec syntax: %s' % ex)}
                )

        # Computes temporary expansions for all sources. A second step may be
        # required in which we use the reference source in answer key to further
        # expand iospec data structures
        iospec = self.pre_tests.copy()
        iospec.expand_inputs(self.number_of_pre_expansions)
        self.pre_tests_expanded = iospec

        if self.pre_tests_source and self.post_tests_source:
            iospec = ejudge.combine_iospecs(self.pre_tests, self.post_tests)
        elif self.post_tests_source:
            iospec = self.post_tests.copy()
        elif self.pre_tests_source:
            iospec = self.pre_tests.copy()
        else:
            raise ValidationError(_(
                'either pre_tests_source or post_tests_source must be given!'
            ))
        iospec.expand_inputs(self.number_of_post_expansions)
        # assert len(iospec) >= self.number_of_expansions, iospec
        self.post_tests_expanded = iospec

        if self.pre_tests_expanded.is_expanded and \
                self.post_tests_expanded.is_expanded:
            self.pre_tests_expanded_source = self.pre_tests_expanded.source()
            self.post_tests_expanded_source = self.post_tests_expanded.source()

        else:
            self._expand_from_answer_keys()

        # Iospec is valid: save the hash
        self.tests_state_hash = self.current_tests_hash
Exemple #11
0
    def validate_tests(self):
        """
        Triggered when (pre|post)_test_source changes or on the first time the
        .clean() method is called.
        """

        # Check if new source is valid
        for attr in ['pre_tests_source', 'post_tests_source']:
            try:
                source = getattr(self, attr)
                if source:
                    iospec = parse_iospec(source)
                else:
                    iospec = None
                setattr(self, attr[:-7], iospec)
            except Exception as ex:
                self.clear_tests()
                raise ValidationError(
                    {attr: _('invalid iospec syntax: %s' % ex)}
                )

        # Computes temporary expansions for all sources. A second step may be
        # required in which we use the reference source in answer key to further
        # expand iospec data structures
        iospec = self.pre_tests.copy()
        iospec.expand_inputs(self.number_of_pre_expansions)
        self.pre_tests_expanded = iospec

        if self.pre_tests_source and self.post_tests_source:
            iospec = ejudge.combine_iospecs(self.pre_tests, self.post_tests)
        elif self.post_tests_source:
            iospec = self.post_tests.copy()
        elif self.pre_tests_source:
            iospec = self.pre_tests.copy()
        else:
            raise ValidationError(_(
                'either pre_tests_source or post_tests_source must be given!'
            ))
        iospec.expand_inputs(self.number_of_post_expansions)
        # assert len(iospec) >= self.number_of_expansions, iospec
        self.post_tests_expanded = iospec

        if self.pre_tests_expanded.is_expanded and \
                self.post_tests_expanded.is_expanded:
            self.pre_tests_expanded_source = self.pre_tests_expanded.source()
            self.post_tests_expanded_source = self.post_tests_expanded.source()

        else:
            self._expand_from_answer_keys()

        # Iospec is valid: save the hash
        self.tests_state_hash = self.current_tests_hash
Exemple #12
0
def iospec_expand(iospec: str, source: str, language: str):
    """
    Expand iospec template using a program.

    Args:
        iospec:
            A string of iospec template
        source:
            Source code for the expansion program.
        language:
            Language used in the expansion program.

    Returns:
        An expanded iospec data.
    """

    iospec_ = parse_iospec(iospec)
    results = ejudge.run(source, iospec_, lang=language, sandbox=SANDBOX)
    return results.source()
    def get_validation_errors(self, lang=None, test_iospec=True):
        """Raise ValueError if some answer key is invalid or produce
         invalid iospec expansions.

         Return a valid iospec tree expansion or None if no expansion was
         possible (e.g., by the lack of source code in the answer key)."""

        # It cannot be valid if the iospec source does not not parse
        if test_iospec:
            try:
                tree = parse_iospec(self.iospec)
            except SyntaxError as ex:
                raise ValueError('invalid iospec syntax: %s' % ex)

        # Expand to all langs if lang is not given
        if lang is None:
            keys = self.answer_keys.exclude(source='')
            langs = keys.values_list('language', flat=True)
            expansions = [
                self.is_valid(lang, test_iospec=False) for lang in langs
            ]
            if not expansions:
                return None
            if iospec.ioequal(expansions):
                return expansions[0]

        # Test an specific language
        if isinstance(lang, str):
            lang = ProgrammingLanguage.get(ref=lang)
        try:
            key = self.answer_keys.get(language=lang)
        except self.DoesNotExist:
            return None

        if key.source:
            result = run_code(key.source, key, lang=lang.ref)
            if result.has_errors():
                raise result.get_error()
            return result
        else:
            return None
Exemple #14
0
    def get_validation_errors(self, lang=None, test_iospec=True):
        """Raise ValueError if some answer key is invalid or produce
         invalid iospec expansions.

         Return a valid iospec tree expansion or None if no expansion was
         possible (e.g., by the lack of source code in the answer key)."""

        # It cannot be valid if the iospec source does not not parse
        if test_iospec:
            try:
                tree = parse_iospec(self.iospec)
            except SyntaxError as ex:
                raise ValueError('invalid iospec syntax: %s' % ex)

        # Expand to all langs if lang is not given
        if lang is None:
            keys = self.answer_keys.exclude(source='')
            langs = keys.values_list('language', flat=True)
            expansions = [self.is_valid(lang, test_iospec=False)
                          for lang in langs]
            if not expansions:
                return None
            if iospec.ioequal(expansions):
                return expansions[0]

        # Test an specific language
        if isinstance(lang, str):
            lang = ProgrammingLanguage.get(ref=lang)
        try:
            key = self.answer_keys.get(language=lang)
        except self.DoesNotExist:
            return None

        if key.source:
            result = run_code(key.source, key, lang=lang.ref)
            if result.has_errors():
                raise result.get_exception()
            return result
        else:
            return None
Exemple #15
0
    def grade(self, response, error=None):
        """Grade the given response object and return the corresponding
        CodingIoFeedback."""

        try:
            key = self.answer_keys.get(language=response.language)
            key.assure_is_valid(error)
            iospec = key.iospec
        except CodingIoAnswerKey.DoesNotExist:
            self.update_keys()

            # Get all sources
            iospec_sources = self.answer_keys.filter(is_valid=True)\
                .values_list('iospec_source', flat=True)
            iospec_sources = set(iospec_sources)

            # Check if there is only a single distinct source
            if not iospec_sources:
                iospec = self.iospec.copy()
                iospec.expand_inputs()
                if not all(isinstance(x, IoTestCase) for x in iospec):
                    raise error or CodingIoAnswerKey.ValidationError(iospec.pformat())
            elif len(iospec_sources) == 1:
                iospec = parse_iospec(next(iter(iospec_sources)))
            else:
                raise error or CodingIoAnswerKey.ValidationError(iospec_sources)

        # Construct ejudge feedback object
        lang = response.language.ref
        source = response.source
        feedback = grade_code(source, iospec, lang=lang)

        # Create a codeschool feedback and save it to the database
        response.status = 'graded'
        response.feedback = feedback
        response.grade = feedback.grade * 100
        response.save()
        return feedback
Exemple #16
0
def grade_submission(iospec: str, source: str, language: str):
    """
    Grade source code from submission using the given iospec data.

    Args:
        iospec:
            A string with iospec examples used to grade the submission.
        source:
            The source code for the submission program.
        language:
            The programming language used in the submission.

    Returns:
        grade (float):
            A numeric grade between 0-100.
        feedback:
            A JSON representation with the complete feedback.
    """

    iospec_ = parse_iospec(iospec)
    results = ejudge.grade(source, iospec_, lang=language, sandbox=SANDBOX)
    grade = results.grade * 100
    return (grade, results.to_json())
Exemple #17
0
def iospec_source_validator(iospec):
    if iospec:
        try:
            parse_iospec(iospec)
        except IoSpecSyntaxError as ex:
            raise ValidationError(_('Invalid IoSpec: %(msg)s') % {'msg': ex})
Exemple #18
0
 def iospec_tree(self):
     return parse_iospec(self.iospec)
Exemple #19
0
 def pre_tests(self):
     try:
         return self._pre_tests
     except AttributeError:
         self._pre_tests = parse_iospec(self.pre_tests_source)
         return self._pre_tests
Exemple #20
0
 def pre_tests(self):
     try:
         return self._pre_tests
     except AttributeError:
         self._pre_tests = parse_iospec(self.pre_tests_source)
         return self._pre_tests
Exemple #21
0
 def iospec(self):
     return parse_iospec(self.iospec_source)
Exemple #22
0
    def iospec(self):
        """
        The IoSpec structure corresponding to the iospec_source.
        """

        return parse_iospec(self.iospec_source)
Exemple #23
0
def iospec_source_validator(iospec):
    if iospec:
        try:
            parse_iospec(iospec)
        except IoSpecSyntaxError as ex:
            raise ValidationError(_('Invalid IoSpec: %(msg)s') % {'msg': ex})
Exemple #24
0
    def iospec(self):
        """The IoSpec structure corresponding to the iospec_source."""

        return parse_iospec(self.iospec_source)
Exemple #25
0
 def iospec(self):
     return parse_iospec(self.iospec_source)