def _check_scores_from_config(self, scored_groups, config_scores): """Called if ``config.yml`` specifies scores for any tests. Makes sure that all scored tests are present in ``config.yml`` and that nothing else is there. """ for group in scored_groups: if int(group) not in config_scores: errormsg = _("Score for group '%(group_name)s' not found. " "You must either provide scores for all groups " "or not provide them at all " "(to have them assigned automatically). " "(Scored groups: %(scored_groups)s, " "groups from config: %(config_groups)s)") % { "group_name": group, "scored_groups": list(scored_groups), "config_groups": config_scores, } raise ProblemPackageError(errormsg) for group in config_scores: if str(group) not in scored_groups: errormsg = _("Score for group '%(group_name)s' " "found in config, " "but no such test group exists in scored groups. " "You must either provide scores for all groups " "or not provide them at all " "(to have them assigned automatically). " "(Scored groups: %(scored_groups)s, " "groups from config: %(config_groups)s)") % { "group_name": group, "scored_groups": list(scored_groups), "config_groups": config_scores, } raise ProblemPackageError(errormsg)
def _validate_tests(self, created_tests): """Check if all tests have output files and that test instances are correct. :raises: :class:`~oioioi.problems.package.ProblemPackageError` """ for instance in created_tests: if not instance.output_file: raise ProblemPackageError( _("Missing out file for test %s") % instance.name) try: instance.full_clean() except ValidationError as e: raise ProblemPackageError(e.messages[0])
def _verify_inputs(self, tests): """Check if correct solution exits with code 0 on all tests. :raises: :class:`~oioioi.problems.package.ProblemPackageError` otherwise. """ env = self._find_and_compile('inwer') if env and not self.use_make: jobs = {} for test in tests: job = env.copy() job['job_type'] = 'inwer' job['task_priority'] = TASK_PRIORITY job['exe_file'] = env['compiled_file'] job['in_file'] = django_to_filetracker_path(test.input_file) job['use_sandboxes'] = self.use_sandboxes jobs[test.name] = job jobs = run_sioworkers_jobs(jobs) get_client().delete_file(env['compiled_file']) for test_name, job in jobs.iteritems(): if job['result_code'] != 'OK': raise ProblemPackageError( _("Inwer failed on test " "%(test)s. Inwer output %(output)s") % { 'test': test_name, 'output': '\n'.join(job['stdout']) }) logger.info("%s: inwer success", self.filename)
def _force_index_encoding(self, htmlzipfile): """Ensures index.html file is utf-8 encoded, if cannot apply this encoding raise :class:`~oioioi.problems.package.ProblemPackageError`. """ with zipfile.ZipFile(htmlzipfile, 'r') as archive, \ archive.open('index.html') as index: data = index.read() # First, we check if index.html is utf-8 encoded. # If it is - nothing to do. try: data.decode('utf8') # We accept iso-8859-2 encoded files, but django doesn't # so index.html has to be translated to utf-8. except UnicodeDecodeError: try: data = data.decode('iso-8859-2').encode('utf8') except (UnicodeDecodeError, UnicodeEncodeError): raise ProblemPackageError( _("index.html has to be utf8 or iso8859-2 encoded")) # We have to remove index.html from the archive and # then add the translated file to archive because # zipfile module doesn't implement editing files # inside archive. _remove_from_zip(htmlzipfile, 'index.html') with zipfile.ZipFile(htmlzipfile, 'a') as new_archive: new_archive.writestr('index.html', data)
def _process_extra_files(self): ExtraFile.objects.filter(problem=self.problem).delete() files = list(self.config.get('extra_compilation_files', ())) not_found = self._find_and_save_files(files) if not_found: raise ProblemPackageError( _("Expected extra files %r not found in prog/") % (not_found))
def _ensure_short_name_equality_with(self, existing_problem): if existing_problem.short_name != self.short_name: raise ProblemPackageError(_("Tried to replace problem " "'%(oldname)s' with '%(newname)s'. For safety, changing " "problem short name is not possible.") % dict(oldname=existing_problem.short_name, newname=self.short_name))
def _process_extra_files(self): ExtraFile.objects.filter(problem=self.problem).delete() for filename in self.config.get('extra_compilation_files', ()): fn = os.path.join(self.rootdir, 'prog', filename) if not os.path.exists(fn): raise ProblemPackageError( _("Expected extra file '%s' not " "found in prog/") % (filename, )) instance = ExtraFile(problem=self.problem, name=filename) instance.file.save(filename, File(open(fn, 'rb')))
def _ensure_compilation_success(self, filename, new_env): compilation_message = new_env.get('compiler_output', '') compilation_result = new_env.get('result_code', 'CE') if compilation_result != 'OK': logger.warning("%s: compilation of file %s failed with code %s", self.filename, filename, compilation_result) logger.warning("%s: compiler output: %r", self.filename, compilation_message) raise ProblemPackageError(_("Compilation of file %(filename)s " "failed. Compiler output: " "%(output)s") % { 'filename': filename, 'output': compilation_message})
def _verify_time_limits(self, tests): """:raises: :class:`~oioioi.problems.package.ProblemPackageError` if sum of tests time limits exceeds """ time_limit_sum = 0 for test in tests: time_limit_sum += test.time_limit if time_limit_sum > settings.MAX_TEST_TIME_LIMIT_PER_PROBLEM: time_limit_sum_rounded = (time_limit_sum + 999) / 1000.0 limit_seconds = settings.MAX_TEST_TIME_LIMIT_PER_PROBLEM / 1000.0 raise ProblemPackageError(_( "Sum of time limits for all tests is too big. It's %(sum)ds, " "but it shouldn't exceed %(limit)ds." ) % {'sum': time_limit_sum_rounded, 'limit': limit_seconds})
def _process_statements(self): """Creates a problem statement from html or pdf source. If `USE_SINOLPACK_MAKEFILES` is set to True in the OIOIOI settings, the pdf file will be compiled from a LaTeX source. """ docdir = os.path.join(self.rootdir, 'doc') if not os.path.isdir(docdir): logger.warning("%s: no docdir", self.filename) return # pylint: disable=maybe-no-member self.problem.statements.all().delete() lang_prefs = [''] + ['-' + l[0] for l in settings.STATEMENT_LANGUAGES] if self.use_make: self._compile_latex_docs(docdir) for lang in lang_prefs: htmlzipfile = os.path.join( docdir, self.short_name + 'zad' + lang + '.html.zip') if os.path.isfile(htmlzipfile): if self._html_disallowed(): raise ProblemPackageError( _("You cannot upload package with " "problem statement in HTML. " "Try again using PDF format.")) self._force_index_encoding(htmlzipfile) statement = ProblemStatement(problem=self.problem, language=lang[1:]) statement.content.save(self.short_name + lang + '.html.zip', File(open(htmlzipfile, 'rb'))) pdffile = os.path.join(docdir, self.short_name + 'zad' + lang + '.pdf') if os.path.isfile(pdffile): statement = ProblemStatement(problem=self.problem, language=lang[1:]) statement.content.save(self.short_name + lang + '.pdf', File(open(pdffile, 'rb'))) if not self.problem.statements.exists(): logger.warning("%s: no problem statement", self.filename)
def unpack(self, existing_problem=None): self.short_name = self._find_main_folder() if existing_problem: self.problem = existing_problem if existing_problem.short_name != self.short_name: raise ProblemPackageError( _("Tried to replace problem " "'%(oldname)s' with '%(newname)s'. For safety, changing " "problem short name is not possible.") % dict(oldname=existing_problem.short_name, newname=self.short_name)) else: self.problem = Problem( name=self.short_name, short_name=self.short_name, controller_name= 'oioioi.sinolpack.controllers.SinolProblemController') self.problem.package_backend_name = \ 'oioioi.sinolpack.package.SinolPackageBackend' self.problem.save() tmpdir = tempfile.mkdtemp() logger.info('%s: tmpdir is %s', self.filename, tmpdir) try: self.archive.extract(to_path=tmpdir) self.rootdir = os.path.join(tmpdir, self.short_name) self._process_config_yml() self._detect_full_name() self._extract_makefiles() self._process_statements() self._generate_tests() self._process_tests() self._process_checkers() self._process_extra_files() self._process_model_solutions() self._save_original_package() return self.problem finally: shutil.rmtree(tmpdir)
def _process_test(self, test, order, names_re, indir, outdir, collected_ins, scored_groups, outs_to_make): """Responsible for saving test in and out files, setting test limits, assigning test kind and group. :param test: Test name. :param order: Test number. :param names_re: Compiled regex to match test details from name. Should extract basename, test name, group number and test type. :param indir: Directory with tests inputs. :param outdir: Directory with tests outputs. :param collected_ins: List of inputs that were generated, not taken from archive as a file. :param scored_groups: Accumulator for score groups. :param outs_to_make: Accumulator for name of output files to be generated by model solution. :return: Test instance or None if name couldn't be matched. """ match = names_re.match(test) if not match: if test.endswith('.in'): raise ProblemPackageError(_("Unrecognized test: %s") % (test)) return None # Examples for odl0ocen.in: basename = match.group(1) # odl0ocen name = match.group(2) # 0ocen group = match.group(3) # 0 suffix = match.group(4) # ocen instance, created = Test.objects.get_or_create( problem_instance=self.main_problem_instance, name=name) inname_base = basename + '.in' inname = os.path.join(indir, inname_base) outname_base = basename + '.out' outname = os.path.join(outdir, outname_base) if test in collected_ins: self._save_to_field(instance.input_file, collected_ins[test]) else: instance.input_file.save(inname_base, File(open(inname, 'rb'))) if os.path.isfile(outname): instance.output_file.save(outname_base, File(open(outname), 'rb')) outs_to_make.append( (_make_filename_in_job_dir(self.env, 'out/%s' % (outname_base)), instance)) if group == '0' or 'ocen' in suffix: # Example tests instance.kind = 'EXAMPLE' instance.group = name else: instance.kind = 'NORMAL' instance.group = group scored_groups.add(group) if created: instance.time_limit = self.time_limits.get(name, DEFAULT_TIME_LIMIT) memory_limit = self._get_memory_limit(created, name) if memory_limit: instance.memory_limit = memory_limit instance.order = order instance.save() return instance
def _process_tests(self, total_score=100): indir = os.path.join(self.rootdir, 'in') outdir = os.path.join(self.rootdir, 'out') test_names = [] scored_groups = set() names_re = re.compile(r'^(%s(([0-9]+)([a-z]?[a-z0-9]*))).in$' % (re.escape(self.short_name), )) time_limits = _stringify_keys(self.config.get('time_limits', {})) memory_limits = _stringify_keys(self.config.get('memory_limits', {})) # Find tests and create objects for order, test in enumerate( sorted(os.listdir(indir), key=naturalsort_key)): match = names_re.match(test) if not match: if test.endswith('.in'): raise ProblemPackageError("Unrecognized test: " + test) continue # Examples for odl0ocen.in: basename = match.group(1) # odl0ocen name = match.group(2) # 0ocen group = match.group(3) # 0 suffix = match.group(4) # ocen instance, created = Test.objects.get_or_create( problem=self.problem, name=name) instance.input_file.save( basename + '.in', File(open(os.path.join(indir, basename + '.in'), 'rb'))) instance.output_file.save( basename + '.out', File(open(os.path.join(outdir, basename + '.out'), 'rb'))) if group == '0' or 'ocen' in suffix: # Example tests instance.kind = 'EXAMPLE' instance.group = name else: instance.kind = 'NORMAL' instance.group = group scored_groups.add(group) if created: instance.time_limit = time_limits.get(name, DEFAULT_TIME_LIMIT) if 'memory_limit' in self.config: instance.memory_limit = self.config['memory_limit'] else: instance.memory_limit = memory_limits.get( name, DEFAULT_MEMORY_LIMIT) instance.order = order instance.save() test_names.append(name) # Delete nonexistent tests for test in Test.objects.filter(problem=self.problem) \ .exclude(name__in=test_names): logger.info('%s: deleting test %s', self.filename, test.name) test.delete() # Assign scores if scored_groups: Test.objects.filter(problem=self.problem).update(max_score=0) num_groups = len(scored_groups) group_score = total_score / num_groups extra_score_groups = sorted( scored_groups, key=naturalsort_key)[num_groups - (total_score - num_groups * group_score):] for group in scored_groups: score = group_score if group in extra_score_groups: score += 1 Test.objects.filter(problem=self.problem, group=group) \ .update(max_score=score)
def _process_tests(self, total_score=100): indir = os.path.join(self.rootdir, 'in') outdir = os.path.join(self.rootdir, 'out') test_names = [] scored_groups = set() names_re = re.compile(r'^(%s(([0-9]+)([a-z]?[a-z0-9]*))).in$' % (re.escape(self.short_name), )) time_limits = _stringify_keys(self.config.get('time_limits', {})) memory_limits = _stringify_keys(self.config.get('memory_limits', {})) statement_memory_limit = self._detect_statement_memory_limit() # Find tests and create objects for order, test in enumerate( sorted(os.listdir(indir), key=naturalsort_key)): match = names_re.match(test) if not match: if test.endswith('.in'): raise ProblemPackageError("Unrecognized test: " + test) continue # Examples for odl0ocen.in: basename = match.group(1) # odl0ocen name = match.group(2) # 0ocen group = match.group(3) # 0 suffix = match.group(4) # ocen instance, created = Test.objects.get_or_create( problem=self.problem, name=name) instance.input_file.save( basename + '.in', File(open(os.path.join(indir, basename + '.in'), 'rb'))) instance.output_file.save( basename + '.out', File(open(os.path.join(outdir, basename + '.out'), 'rb'))) if group == '0' or 'ocen' in suffix: # Example tests instance.kind = 'EXAMPLE' instance.group = name else: instance.kind = 'NORMAL' instance.group = group scored_groups.add(group) if created: instance.time_limit = time_limits.get(name, DEFAULT_TIME_LIMIT) # If we find the memory limit specified anywhere in the package: # either in the config.yml or in the problem statement, then we # overwrite potential manual changes. (In the future we should # disallow editing memory limits if they were taken from the # package). if name in memory_limits: instance.memory_limit = memory_limits[name] elif 'memory_limit' in self.config: instance.memory_limit = self.config['memory_limit'] elif statement_memory_limit is not None: instance.memory_limit = statement_memory_limit elif created: instance.memory_limit = DEFAULT_MEMORY_LIMIT instance.order = order try: instance.full_clean() except ValidationError as e: raise ProblemPackageError(e.messages[0]) instance.save() test_names.append(name) # Delete nonexistent tests for test in Test.objects.filter(problem=self.problem) \ .exclude(name__in=test_names): logger.info('%s: deleting test %s', self.filename, test.name) test.delete() # Assign scores if scored_groups: Test.objects.filter(problem=self.problem).update(max_score=0) num_groups = len(scored_groups) group_score = total_score / num_groups extra_score_groups = sorted( scored_groups, key=naturalsort_key)[num_groups - (total_score - num_groups * group_score):] for group in scored_groups: score = group_score if group in extra_score_groups: score += 1 Test.objects.filter(problem=self.problem, group=group) \ .update(max_score=score)