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 )
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.') })
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
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)
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)
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
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
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
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
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
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
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
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())
def iospec_source_validator(iospec): if iospec: try: parse_iospec(iospec) except IoSpecSyntaxError as ex: raise ValidationError(_('Invalid IoSpec: %(msg)s') % {'msg': ex})
def iospec_tree(self): return parse_iospec(self.iospec)
def pre_tests(self): try: return self._pre_tests except AttributeError: self._pre_tests = parse_iospec(self.pre_tests_source) return self._pre_tests
def iospec(self): return parse_iospec(self.iospec_source)
def iospec(self): """ The IoSpec structure corresponding to the iospec_source. """ return parse_iospec(self.iospec_source)
def iospec(self): """The IoSpec structure corresponding to the iospec_source.""" return parse_iospec(self.iospec_source)