def compress(self, in_ext=None, sol_ext=None): """Compress all test cases in this dataset in a single zip file. The basename of the corresponding subtask subdirectory is prepended to each file. """ in_ext = in_ext or self._in_ext sol_ext = sol_ext or self._sol_ext dst_file = FilePath(self._directory, 'data.zip') if dst_file.exists() and dst_file.mtime() >= self.mtime(): return True tmpdir = Directory.tmpdir() try: copied = 0 for subtask in self._subtasks: copied += subtask.copy_to(tmpdir) if not copied: # ui.show_message("Warning", "no files in dataset", ui.WARNING) return True cmd = 'cd %s && zip data.zip *%s *%s' % (tmpdir, in_ext, sol_ext) st = subprocess.call(cmd, stdout=subprocess.DEVNULL, shell=True) FilePath(tmpdir, 'data.zip').copy(dst_file) finally: tmpdir.rmtree() return st == 0
def run_subtask(self, stn, subtask): groups = subtask['groups'] for (group, tests) in sorted(groups.items()): for (i, test) in enumerate(tests, 1): cmd = test['cmd'] test_file = self.test_filepath(stn, group, i) if cmd == 'copy': self.copy(test['file'], test_file) elif cmd == 'echo': self.echo(test['args'], test_file) else: test['args'].insert(0, '%s-%s-%s' % (stn, group, i)) source = FilePath(self._directory, test['source']) if cmd == 'cpp': self.run_cpp_generator(source, test['args'], test_file) elif cmd in ['py', 'py2', 'py3']: self.run_py_generator(source, test['args'], test_file, cmd) elif cmd == 'java': self.run_java_generator(source, test['args'], test_file) elif cmd == 'run': bin_path = FilePath(self._directory, test['bin']) self.run_bin_generator(bin_path, test['args'], test_file) else: ui.fatal_error( 'unexpected command when running plan: %s ' % cmd)
def compress(self, in_ext=None, sol_ext=None, random_sort=False): """Compress all test cases in this dataset in a single zip file. The basename of the corresponding subtask subdirectory is prepended to each file. """ in_ext = in_ext or self._in_ext sol_ext = sol_ext or self._sol_ext dst_file = FilePath(self._directory, 'data.zip') if dst_file.exists() and dst_file.mtime() >= self.mtime(): return True tmpdir = Directory.tmpdir() try: copied = 0 for subtask in self._subtasks: copied += subtask.copy_to(tmpdir, random_sort=random_sort) if not copied: # ui.show_message("Warning", "no files in dataset", ui.WARNING) return True cmd = 'cd %s && zip data.zip *%s *%s' % (tmpdir, in_ext, sol_ext) st = subprocess.call(cmd, stdout=subprocess.DEVNULL, shell=True) FilePath(tmpdir, 'data.zip').copy(dst_file) finally: tmpdir.rmtree() return st == 0
def __init__(self, source): """ Args: source (FilePath) """ self._source = source self._compiler = CppCompiler(['-I"%s"' % source.directory()]) self._binary_path = FilePath(source.directory(), 'checker')
def copy(self, src, dst): fp = FilePath(self._task_directory, src) if not fp.exists(): return (False, 'No such file') try: fp.copy(dst) (st, msg) = (True, 'OK') return (st, msg) except Exception: # pylint: disable=broad-except return (False, 'Error when copying file')
def run(self): (subtasks, cmds) = self.parse_file() for stn in range(1, subtasks + 1): dire = FilePath(self._dataset_directory, 'st%d' % stn).get_or_create_dir() dire.clear() if not cmds: ui.show_message("Warning", 'no commands were executed for the plan.', ui.WARNING) for (stn, subtask) in sorted(cmds.items()): self.run_subtask(stn, subtask)
def __init__(self, directory, num=None, codename=None): """ Args: directory (Directory): Directory to search for statement source file. num (int): Number of the statement in the contest starting from 0 """ assert FilePath(directory, 'statement.tex').exists() self._source = FilePath(directory, 'statement.tex') self._pdf = self._source.chext('.pdf') self._compiler = LatexCompiler() self._directory = directory self._num = num self._codename = codename
def create_layout(contest_path, config): """Copies contest skeleton to contest_path and saves specified configurations Args: contest_path (Filepath) """ ocimatic_dir = FilePath(__file__).directory() contest_skel = ocimatic_dir.chdir('resources', 'contest-skel') contest_skel.copy_tree(contest_path, ['auto']) contest_dir = contest_path.get_or_create_dir() with FilePath(contest_dir, '.ocimatic_contest').open('w') as config_file: json.dump(config, config_file, indent=4)
def __init__(self, directory, task_directory, dataset_directory, filename='testplan.txt'): self._directory = directory self._testplan_path = FilePath(directory, filename) if not self._testplan_path.exists(): ui.fatal_error('No such file plan for creating dataset: "%s"' % self._testplan_path) self._task_directory = task_directory self._dataset_directory = dataset_directory self._cpp_compiler = CppCompiler() self._java_compiler = JavaCompiler()
def build_validator(self, source): fp = FilePath(self._directory, source) if not fp.exists(): return (None, 'File does not exists.') if fp.ext == '.cpp': binary = fp.chext('.bin') if binary.mtime() < fp.mtime() and not self._cpp_compiler(fp, binary): return (None, 'Failed to build validator.') return (Runnable(binary), 'OK') if fp.ext in ['.py', '.py3']: return (Runnable('python3', [str(source)]), 'OK') if fp.ext == '.py2': return (Runnable('python2', [str(source)]), 'OK') return (None, 'Not supported source file.')
def build_validator(self, source): fp = FilePath(self._directory, source) if not fp.exists(): return (None, 'File does not exists.') if fp.ext == '.cpp': binary = fp.chext('.bin') if binary.mtime() < fp.mtime() and not self._cpp_compiler( fp, binary): return (None, 'Failed to build validator.') return (Runnable(binary), 'OK') if fp.ext in ['.py', '.py3']: return (Runnable('python3', [str(source)]), 'OK') if fp.ext == '.py2': return (Runnable('python2', [str(source)]), 'OK') return (None, 'Not supported source file.')
def __init__(self, directory): """ Args: directory (Directory): Directory where the contest reside. """ self._directory = directory self._config = pjson.load(FilePath(directory, '.ocimatic_contest')) self._init_tasks() if 'phase' in self._config: os.environ['OCIMATIC_PHASE'] = self._config['phase'] self._titlepage = FilePath(directory, 'titlepage.tex') self._compiler = LatexCompiler()
class CppChecker(Checker): def __init__(self, source): """ Args: source (FilePath) """ self._source = source self._compiler = CppCompiler(['-I"%s"' % source.directory()]) self._binary_path = FilePath(source.directory(), 'checker') def __call__(self, in_path, expected_path, out_path): """Run checker to evaluate outcome. Parameters correspond to convention for checker in cms. Args: in_path (FilePath) expected_path (FilePath) out_path (FilePath) """ assert in_path.exists() assert expected_path.exists() assert out_path.exists() if self._binary_path.mtime() < self._source.mtime(): if not self.build(): return (False, 0.0, "Failed to build checker") complete = subprocess.run([ str(self._binary_path), str(in_path), str(expected_path), str(out_path) ], universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) ret = complete.returncode st = ret == 0 if st: outcome = float(complete.stdout) msg = complete.stderr else: stderr = complete.stderr.strip('\n') outcome = 0.0 if stderr and len(stderr) < 75: msg = stderr else: if ret < 0: sig = -ret msg = 'Execution killed with signal %d' % sig if sig in SIGNALS: msg += ': %s' % SIGNALS[sig] else: msg = 'Execution ended with error (return code %d)' % ret return (st, outcome, msg) def build(self): """Build source of the checker Returns: bool: True if compilation is successful. False otherwise """ return self._compiler(self._source, self._binary_path)
class CppChecker(Checker): def __init__(self, source): """ Args: source (FilePath) """ self._source = source self._compiler = CppCompiler(['-I"%s"' % source.directory()]) self._binary_path = FilePath(source.directory(), 'checker') def __call__(self, in_path, expected_path, out_path): """Run checker to evaluate outcome. Parameters correspond to convention for checker in cms. Args: in_path (FilePath) expected_path (FilePath) out_path (FilePath) """ assert in_path.exists() assert expected_path.exists() assert out_path.exists() if self._binary_path.mtime() < self._source.mtime(): if not self.build(): return (False, 0.0, "Failed to build checker") complete = subprocess.run( [str(self._binary_path), str(in_path), str(expected_path), str(out_path)], universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) ret = complete.returncode st = ret == 0 if st: outcome = float(complete.stdout) msg = complete.stderr else: stderr = complete.stderr.strip('\n') outcome = 0.0 if stderr and len(stderr) < 75: msg = stderr else: if ret < 0: sig = -ret msg = 'Execution killed with signal %d' % sig if sig in SIGNALS: msg += ': %s' % SIGNALS[sig] else: msg = 'Execution ended with error (return code %d)' % ret return (st, outcome, msg) def build(self): """Build source of the checker Returns: bool: True if compilation is successful. False otherwise """ return self._compiler(self._source, self._binary_path)
def copy_to(self, directory, random_sort=False): copied = 0 for test in self._tests: if test.expected_path.exists(): # Sort testcases withing a subtask randomly if random_sort: choices = string.ascii_lowercase rnd_str = ''.join(random.choice(choices) for _ in range(3)) in_name = "%s-%s-%s" % (self._name, rnd_str, test.in_path.name) sol_name = "%s-%s-%s" % (self._name, rnd_str, test.expected_path.name) else: in_name = "%s-%s-%s" % (self._name, rnd_str, test.in_path.name) sol_name = "%s-%s-%s" % (self._name, rnd_str, test.expected_path.name) test.in_path.copy(FilePath(directory, in_name)) test.expected_path.copy(FilePath(directory, sol_name)) copied += 1 return copied
def merge_pdfs(self, filename): """Merges statements and title page in a single file """ if not shutil.which('gs'): return (False, 'Cannot find gs') pdfs = ' '.join('"%s"' % t.statement.pdf for t in self._tasks if t.statement.pdf) titlepage = FilePath(self._directory, 'titlepage.pdf') if titlepage.exists(): pdfs = '"%s" %s' % (titlepage, pdfs) cmd = ('gs -dBATCH -dNOPAUSE -q -sDEVICE=pdfwrite' ' -dPDFSETTINGS=/prepress -sOutputFile=%s %s') % (FilePath( self._directory, filename), pdfs) complete = subprocess.run(cmd, shell=True, timeout=20, stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) st = complete.returncode == 0 return (st, 'OK' if st else 'FAILED')
def io_samples(self): """Find sample input data in the satement Returns: List[FilePath]: list of paths """ latex_file = self._source.open('r') samples = set() for line in latex_file: m = re.match(r'[^%]*\\sampleIO(\[[^\]]*\]){0,2}{([^}]+)}', line) if m: samples.add(m.group(2)) latex_file.close() return [FilePath(self._directory, s) for s in samples]
def copy_to(self, directory): new_dir = directory.mkdir(str(self)) (st, _) = self.compress_dataset() if st: dataset = FilePath(self._directory.chdir('dataset'), 'data.zip') dataset_dst = FilePath(new_dir, 'data.zip') if dataset.exists(): dataset.copy(dataset_dst) (st, _) = self.build_statement() if st: statement = FilePath(new_dir, 'statement.pdf') FilePath(self._directory.chdir('statement'), 'statement.pdf').copy(statement)
def run(self, in_path, out_path, args=None, timeout=None): # pylint: disable=too-many-locals """Run binary redirecting standard input and output. Args: in_path (Optional[FilePath]): Path to redirect stdin from. If None input is redirected from /dev/null. out_path (Optional[FilePath]): File to redirec stdout to. If None output is redirected to /dev/null. args (List[str]): Additional parameters Returns: (bool, str, float): Returns a tuple (status, time, errmsg). status is True if the execution terminates with exit code zero or False otherwise. time corresponds to execution time. if status is False errmsg contains an explanatory error message, otherwise it contains a success message. """ args = args or [] assert in_path is None or in_path.exists() with contextlib.ExitStack() as stack: if in_path is None: in_path = FilePath('/dev/null') in_file = stack.enter_context(in_path.open('r')) if not out_path: out_path = FilePath('/dev/null') out_file = stack.enter_context(out_path.open('w')) start = pytime.monotonic() self._cmd.extend(args) try: complete = subprocess.run( self._cmd, timeout=timeout, stdin=in_file, stdout=out_file, universal_newlines=True, stderr=subprocess.PIPE) except subprocess.TimeoutExpired: return (False, pytime.monotonic() - start, 'Execution timed out') time = pytime.monotonic() - start ret = complete.returncode status = ret == 0 msg = 'OK' if not status: stderr = complete.stderr.strip('\n') if stderr and len(stderr) < 100: msg = stderr else: if ret < 0: sig = -ret msg = 'Execution killed with signal %d' % sig if sig in SIGNALS: msg += ': %s' % SIGNALS[sig] else: msg = 'Execution ended with error (return code %d)' % ret return (status, time, msg)
def new_contest(args, optlist): if not args: ui.fatal_error('You have to specify a name for the contest.') name = args[0] contest_config = {} if '--phase' in optlist: contest_config['phase'] = optlist['--phase'] try: cwd = Directory.getcwd() if cwd.find(name): ui.fatal_error("Couldn't create contest. Path already exists") contest_path = FilePath(cwd, name) core.Contest.create_layout(contest_path, contest_config) ui.show_message('Info', 'Contest [%s] created' % name) except Exception as exc: # pylint: disable=broad-except ui.fatal_error("Couldn't create contest: %s." % exc)
def run(self, runnable, checker, check=False): """Run runnable whit this test as input and check output correctness Args: runnable (Runnable) checker (Checker): Checker to check outcome check (bool): If true this only report if expected output correspond to binary execution output. """ out_path = FilePath.tmpfile() if not self.expected_path.exists(): out_path.remove() return (False, 'No expected output file') (st, time, errmsg) = runnable.run(self.in_path, out_path, timeout=ocimatic.config['timeout']) # Execution failed if not st: if check: return (st, errmsg) return (st, '%s' % errmsg) (st, outcome, checkmsg) = checker(self.in_path, self.expected_path, out_path) # Checker failed if not st: msg = 'Failed to run checker: %s' % checkmsg return (st, msg) st = outcome == 1.0 if check: msg = 'OK' if st else 'FAILED' return (st, msg) msg = '%s [%.2fs]' % (outcome, time) if checkmsg: msg += ' - %s' % checkmsg return (st, msg)
def run(self, runnable, checker, check=False): """Run runnable whit this test as input and check output correctness Args: runnable (Runnable) checker (Checker): Checker to check outcome check (bool): If true this only report if expected output correspond to binary execution output. """ out_path = FilePath.tmpfile() if not self.expected_path.exists(): out_path.remove() return (False, 'No expected output file') (st, time, errmsg) = runnable.run( self.in_path, out_path, timeout=ocimatic.config['timeout']) # Execution failed if not st: if check: return (st, errmsg) return (st, '%s' % errmsg) (st, outcome, checkmsg) = checker(self.in_path, self.expected_path, out_path) # Checker failed if not st: msg = 'Failed to run checker: %s' % checkmsg return (st, msg) st = outcome == 1.0 if check: msg = 'OK' if st else 'FAILED' return (st, msg) msg = '%s [%.2fs]' % (outcome, time) if checkmsg: msg += ' - %s' % checkmsg return (st, msg)
def new_task(self, name): task_dir = FilePath(self._directory, name) Task.create_layout(task_dir) self._config.setdefault('tasks', []).append(name)
class Statement: """Represents a statement. A statement is formed by a latex source and a pdf file. """ def __init__(self, directory, num=None, codename=None): """ Args: directory (Directory): Directory to search for statement source file. num (int): Number of the statement in the contest starting from 0 """ assert FilePath(directory, 'statement.tex').exists() self._source = FilePath(directory, 'statement.tex') self._pdf = self._source.chext('.pdf') self._compiler = LatexCompiler() self._directory = directory self._num = num self._codename = codename @property def pdf(self): """Returns path to pdf file and compiles it if necessary. Returns: Optional[FilePath]: The file path if the binary is present or None if the pdf file cannot be generated. """ if self._pdf.mtime() < self._source.mtime(): (st, _msg) = self.build() if not st: return None return self._pdf def __str__(self): return str(self._source) @ui.work('PDF') def build(self, blank_page=False): """Compile statement latex source Args: blank_page (Optional[bool]) if true adds a blank page at the end of the problem. Returns: (bool, msg) a tuple containing status code and result message. """ if self._num is not None: os.environ['OCIMATIC_PROBLEM_NUMBER'] = chr(ord('A') + self._num) if self._codename: os.environ['OCIMATIC_CODENAME'] = self._codename if blank_page: os.environ['OCIMATIC_BLANK_PAGE'] = 'True' st = self._compiler(self._source) return (st, 'OK' if st else 'FAILED') def io_samples(self): """Find sample input data in the satement Returns: List[FilePath]: list of paths """ latex_file = self._source.open('r') samples = set() for line in latex_file: m = re.match(r'[^%]*\\sampleIO(\[[^\]]*\]){0,2}{([^}]+)}', line) if m: samples.add(m.group(2)) latex_file.close() return [FilePath(self._directory, s) for s in samples]
class DatasetPlan: """Functionality to read and run a plan for generating dataset.""" def __init__(self, directory, task_directory, dataset_directory, filename='testplan.txt'): self._directory = directory self._testplan_path = FilePath(directory, filename) if not self._testplan_path.exists(): ui.fatal_error('No such file plan for creating dataset: "%s"' % self._testplan_path) self._task_directory = task_directory self._dataset_directory = dataset_directory self._cpp_compiler = CppCompiler() self._java_compiler = JavaCompiler() def test_filepath(self, stn, group, i): st_dir = FilePath(self._dataset_directory, 'st%d' % stn).get_or_create_dir() return FilePath(st_dir, '%s-%d.in' % (group, i)) def validate_input(self): (_, cmds) = self.parse_file() for (st, subtask) in sorted(cmds.items()): self.validate_subtask(st, subtask) @ui.workgroup('Subtask {1}') def validate_subtask(self, stn, subtask): validator = None if subtask['validator']: (validator, msg) = self.build_validator(subtask['validator']) if validator is None: ui.show_message('Warning', 'Failed to build validator: %s' % msg, ui.WARNING) else: ui.show_message('Info', 'No validator specified', ui.INFO) if validator: for (group, tests) in sorted(subtask['groups'].items()): for (i, _) in enumerate(tests, 1): test_file = self.test_filepath(stn, group, i) self.validate_test_input(test_file, validator) @ui.work('Validating', '{1}') def validate_test_input(self, test_file, validator): if not test_file.exists(): return False, 'Test file does not exist' (st, _time, msg) = validator.run(test_file, None) return st, msg def build_validator(self, source): fp = FilePath(self._directory, source) if not fp.exists(): return (None, 'File does not exists.') if fp.ext == '.cpp': binary = fp.chext('.bin') if binary.mtime() < fp.mtime() and not self._cpp_compiler( fp, binary): return (None, 'Failed to build validator.') return (Runnable(binary), 'OK') if fp.ext in ['.py', '.py3']: return (Runnable('python3', [str(source)]), 'OK') if fp.ext == '.py2': return (Runnable('python2', [str(source)]), 'OK') return (None, 'Not supported source file.') def run(self): (subtasks, cmds) = self.parse_file() for stn in range(1, subtasks + 1): dire = FilePath(self._dataset_directory, 'st%d' % stn).get_or_create_dir() dire.clear() if not cmds: ui.show_message("Warning", 'no commands were executed for the plan.', ui.WARNING) for (stn, subtask) in sorted(cmds.items()): self.run_subtask(stn, subtask) @ui.workgroup('Subtask {1}') def run_subtask(self, stn, subtask): groups = subtask['groups'] for (group, tests) in sorted(groups.items()): for (i, test) in enumerate(tests, 1): cmd = test['cmd'] test_file = self.test_filepath(stn, group, i) if cmd == 'copy': self.copy(test['file'], test_file) elif cmd == 'echo': self.echo(test['args'], test_file) else: test['args'].insert(0, '%s-%s-%s' % (stn, group, i)) source = FilePath(self._directory, test['source']) if cmd == 'cpp': self.run_cpp_generator(source, test['args'], test_file) elif cmd in ['py', 'py2', 'py3']: self.run_py_generator(source, test['args'], test_file, cmd) elif cmd == 'java': self.run_java_generator(source, test['args'], test_file) elif cmd == 'run': bin_path = FilePath(self._directory, test['bin']) self.run_bin_generator(bin_path, test['args'], test_file) else: ui.fatal_error( 'unexpected command when running plan: %s ' % cmd) @ui.work('Copy', '{1}') def copy(self, src, dst): fp = FilePath(self._task_directory, src) if not fp.exists(): return (False, 'No such file') try: fp.copy(dst) (st, msg) = (True, 'OK') return (st, msg) except Exception: # pylint: disable=broad-except return (False, 'Error when copying file') @ui.work('Echo', '{1}') def echo(self, args, dst): with dst.open('w') as test_file: test_file.write(' '.join(args) + '\n') (st, msg) = (True, 'Ok') return (st, msg) @ui.work('Gen', '{1}') def run_cpp_generator(self, source, args, dst): if not source.exists(): return (False, 'No such file') binary = source.chext('.bin') if binary.mtime() < source.mtime(): st = self._cpp_compiler(source, binary) if not st: return (st, 'Failed to build generator') (st, _time, msg) = Runnable(binary).run(None, dst, args) return (st, msg) @ui.work('Gen', '{1}') def run_py_generator(self, source, args, dst, cmd): if not source.exists(): return (False, 'No such file') python = 'python2' if cmd == 'py2' else 'python3' (st, _time, msg) = Runnable(python, [str(source)]).run(None, dst, args) return (st, msg) @ui.work('Gen', '{1}') def run_java_generator(self, source, args, dst): if not source.exists(): return (False, 'No such file') bytecode = source.chext('.class') if bytecode.mtime() < source.mtime(): st = self._java_compiler(source) if not st: return (st, 'Failed to build generator') classname = bytecode.rootname() classpath = str(bytecode.directory().path()) (st, _time, msg) = Runnable('java', ['-cp', classpath, classname]).run(None, dst, args) return (st, msg) @ui.work('Gen', '{1}') def run_bin_generator(self, bin_path, args, dst): if not bin_path.exists(): return (False, 'No such file') if not Runnable.is_callable(bin_path): return (False, 'Cannot run file, it may not have correct permissions') (st, _time, msg) = Runnable(bin_path).run(None, dst, args) return (st, msg) def parse_file(self): # pylint: disable=too-many-locals,too-many-branches """ Args: path (FilePath) """ cmds = {} st = 0 for (lineno, line) in enumerate(self._testplan_path.open('r').readlines(), 1): line = line.strip() subtask_header = re.compile( r'\s*\[\s*Subtask\s*(\d+)\s*(?:-\s*([^\]\s]+))?\s*\]\s*') cmd_line = re.compile(r'\s*([^;\s]+)\s*;\s*(\S+)(:?\s+(.*))?') comment = re.compile(r'\s*#.*') if not line: continue if not comment.fullmatch(line): header_match = subtask_header.fullmatch(line) cmd_match = cmd_line.fullmatch(line) if header_match: found_st = int(header_match.group(1)) validator = header_match.group(2) if st + 1 != found_st: ui.fatal_error( 'line %d: found subtask %d, but subtask %d was expected' % (lineno, found_st, st + 1)) st += 1 cmds[st] = {'validator': validator, 'groups': {}} elif cmd_match: if st == 0: ui.fatal_error( 'line %d: found command before declaring a subtask.' % lineno) group = cmd_match.group(1) cmd = cmd_match.group(2) args = _parse_args(cmd_match.group(3) or '') if group not in cmds[st]['groups']: cmds[st]['groups'][group] = [] if cmd == 'copy': if len(args) > 2: ui.fatal_error( 'line %d: command copy expects exactly one argument.' % lineno) cmds[st]['groups'][group].append({ 'cmd': 'copy', 'file': args[0], }) elif cmd == 'echo': cmds[st]['groups'][group].append({ 'cmd': 'echo', 'args': args }) else: f = FilePath(self._directory, cmd) if f.ext in ['.cpp', '.java', '.py', '.py2', '.py3']: cmds[st]['groups'][group].append({ 'cmd': f.ext[1:], 'source': cmd, 'args': args }) else: cmds[st]['groups'][group].append({ 'cmd': 'run', 'bin': cmd, 'args': args }) else: ui.fatal_error('line %d: error while parsing line `%s`\n' % (lineno, line)) return (st, cmds)
def create_layout(task_path): ocimatic_dir = FilePath(__file__).directory() skel = ocimatic_dir.chdir('resources', 'task-skel') skel.copy_tree(task_path)
class DatasetPlan: """Functionality to read and run a plan for generating dataset.""" def __init__(self, directory, task_directory, dataset_directory, filename='testplan.txt'): self._directory = directory self._testplan_path = FilePath(directory, filename) if not self._testplan_path.exists(): ui.fatal_error('No such file plan for creating dataset: "%s"' % self._testplan_path) self._task_directory = task_directory self._dataset_directory = dataset_directory self._cpp_compiler = CppCompiler() self._java_compiler = JavaCompiler() def test_filepath(self, stn, group, i): st_dir = FilePath(self._dataset_directory, 'st%d' % stn).get_or_create_dir() return FilePath(st_dir, '%s-%d.in' % (group, i)) def validate_input(self): (_, cmds) = self.parse_file() for (st, subtask) in sorted(cmds.items()): self.validate_subtask(st, subtask) @ui.workgroup('Subtask {1}') def validate_subtask(self, stn, subtask): validator = None if subtask['validator']: (validator, msg) = self.build_validator(subtask['validator']) if validator is None: ui.show_message('Warning', 'Failed to build validator: %s' % msg, ui.WARNING) else: ui.show_message('Info', 'No validator specified', ui.INFO) if validator: for (group, tests) in sorted(subtask['groups'].items()): for (i, _) in enumerate(tests, 1): test_file = self.test_filepath(stn, group, i) self.validate_test_input(test_file, validator) @ui.work('Validating', '{1}') def validate_test_input(self, test_file, validator): if not test_file.exists(): return False, 'Test file does not exist' (st, _time, msg) = validator.run(test_file, None) return st, msg def build_validator(self, source): fp = FilePath(self._directory, source) if not fp.exists(): return (None, 'File does not exists.') if fp.ext == '.cpp': binary = fp.chext('.bin') if binary.mtime() < fp.mtime() and not self._cpp_compiler(fp, binary): return (None, 'Failed to build validator.') return (Runnable(binary), 'OK') if fp.ext in ['.py', '.py3']: return (Runnable('python3', [str(source)]), 'OK') if fp.ext == '.py2': return (Runnable('python2', [str(source)]), 'OK') return (None, 'Not supported source file.') def run(self): (subtasks, cmds) = self.parse_file() for stn in range(1, subtasks + 1): dire = FilePath(self._dataset_directory, 'st%d' % stn).get_or_create_dir() dire.clear() if not cmds: ui.show_message("Warning", 'no commands were executed for the plan.', ui.WARNING) for (stn, subtask) in sorted(cmds.items()): self.run_subtask(stn, subtask) @ui.workgroup('Subtask {1}') def run_subtask(self, stn, subtask): groups = subtask['groups'] for (group, tests) in sorted(groups.items()): for (i, test) in enumerate(tests, 1): cmd = test['cmd'] test_file = self.test_filepath(stn, group, i) if cmd == 'copy': self.copy(test['file'], test_file) elif cmd == 'echo': self.echo(test['args'], test_file) else: test['args'].insert(0, '%s-%s-%s' % (stn, group, i)) source = FilePath(self._directory, test['source']) if cmd == 'cpp': self.run_cpp_generator(source, test['args'], test_file) elif cmd in ['py', 'py2', 'py3']: self.run_py_generator(source, test['args'], test_file, cmd) elif cmd == 'java': self.run_java_generator(source, test['args'], test_file) elif cmd == 'run': bin_path = FilePath(self._directory, test['bin']) self.run_bin_generator(bin_path, test['args'], test_file) else: ui.fatal_error('unexpected command when running plan: %s ' % cmd) @ui.work('Copy', '{1}') def copy(self, src, dst): fp = FilePath(self._task_directory, src) if not fp.exists(): return (False, 'No such file') try: fp.copy(dst) (st, msg) = (True, 'OK') return (st, msg) except Exception: # pylint: disable=broad-except return (False, 'Error when copying file') @ui.work('Echo', '{1}') def echo(self, args, dst): with dst.open('w') as test_file: test_file.write(' '.join(args) + '\n') (st, msg) = (True, 'Ok') return (st, msg) @ui.work('Gen', '{1}') def run_cpp_generator(self, source, args, dst): if not source.exists(): return (False, 'No such file') binary = source.chext('.bin') if binary.mtime() < source.mtime(): st = self._cpp_compiler(source, binary) if not st: return (st, 'Failed to build generator') (st, _time, msg) = Runnable(binary).run(None, dst, args) return (st, msg) @ui.work('Gen', '{1}') def run_py_generator(self, source, args, dst, cmd): if not source.exists(): return (False, 'No such file') python = 'python2' if cmd == 'py2' else 'python3' (st, _time, msg) = Runnable(python, [str(source)]).run(None, dst, args) return (st, msg) @ui.work('Gen', '{1}') def run_java_generator(self, source, args, dst): if not source.exists(): return (False, 'No such file') bytecode = source.chext('.class') if bytecode.mtime() < source.mtime(): st = self._java_compiler(source) if not st: return (st, 'Failed to build generator') classname = bytecode.rootname() classpath = str(bytecode.directory().path()) (st, _time, msg) = Runnable('java', ['-cp', classpath, classname]).run(None, dst, args) return (st, msg) @ui.work('Gen', '{1}') def run_bin_generator(self, bin_path, args, dst): if not bin_path.exists(): return (False, 'No such file') if not Runnable.is_callable(bin_path): return (False, 'Cannot run file, it may not have correct permissions') (st, _time, msg) = Runnable(bin_path).run(None, dst, args) return (st, msg) def parse_file(self): # pylint: disable=too-many-locals,too-many-branches """ Args: path (FilePath) """ cmds = {} st = 0 for (lineno, line) in enumerate(self._testplan_path.open('r').readlines(), 1): line = line.strip() subtask_header = re.compile(r'\s*\[\s*Subtask\s*(\d+)\s*(?:-\s*([^\]\s]+))?\s*\]\s*') cmd_line = re.compile(r'\s*([^;\s]+)\s*;\s*(\S+)(:?\s+(.*))?') comment = re.compile(r'\s*#.*') if not line: continue if not comment.fullmatch(line): header_match = subtask_header.fullmatch(line) cmd_match = cmd_line.fullmatch(line) if header_match: found_st = int(header_match.group(1)) validator = header_match.group(2) if st + 1 != found_st: ui.fatal_error('line %d: found subtask %d, but subtask %d was expected' % (lineno, found_st, st + 1)) st += 1 cmds[st] = {'validator': validator, 'groups': {}} elif cmd_match: if st == 0: ui.fatal_error( 'line %d: found command before declaring a subtask.' % lineno) group = cmd_match.group(1) cmd = cmd_match.group(2) args = (cmd_match.group(3) or '').split() if group not in cmds[st]['groups']: cmds[st]['groups'][group] = [] if cmd == 'copy': if len(args) > 2: ui.fatal_error( 'line %d: command copy expects exactly one argument.' % lineno) cmds[st]['groups'][group].append({ 'cmd': 'copy', 'file': args[0], }) elif cmd == 'echo': cmds[st]['groups'][group].append({'cmd': 'echo', 'args': args}) else: f = FilePath(self._directory, cmd) if f.ext in ['.cpp', '.java', '.py', '.py2', '.py3']: cmds[st]['groups'][group].append({ 'cmd': f.ext[1:], 'source': cmd, 'args': args }) else: cmds[st]['groups'][group].append({ 'cmd': 'run', 'bin': cmd, 'args': args }) else: ui.fatal_error('line %d: error while parsing line `%s`\n' % (lineno, line)) return (st, cmds)
def package(self): """Compress statement and dataset of all tasks in a single file""" tmpdir = Directory.tmpdir() try: for task in self._tasks: task.copy_to(tmpdir) self.build_problemset_twoside() self.build_problemset_oneside() oneside = FilePath(self._directory, 'oneside.pdf') if oneside.exists(): oneside.copy(FilePath(tmpdir, 'oneside.pdf')) twoside = FilePath(self._directory, 'twoside.pdf') if twoside.exists(): twoside.copy(FilePath(tmpdir, 'twoside.pdf')) cmd = 'cd %s && zip -r contest.zip .' % tmpdir st = subprocess.call(cmd, stdout=subprocess.DEVNULL, shell=True) contest = FilePath(self._directory, '%s.zip' % self.name) FilePath(tmpdir, 'contest.zip').copy(contest) finally: tmpdir.rmtree() return st == 0
def test_filepath(self, stn, group, i): st_dir = FilePath(self._dataset_directory, 'st%d' % stn).get_or_create_dir() return FilePath(st_dir, '%s-%d.in' % (group, i))
def parse_file(self): # pylint: disable=too-many-locals,too-many-branches """ Args: path (FilePath) """ cmds = {} st = 0 for (lineno, line) in enumerate(self._testplan_path.open('r').readlines(), 1): line = line.strip() subtask_header = re.compile( r'\s*\[\s*Subtask\s*(\d+)\s*(?:-\s*([^\]\s]+))?\s*\]\s*') cmd_line = re.compile(r'\s*([^;\s]+)\s*;\s*(\S+)(:?\s+(.*))?') comment = re.compile(r'\s*#.*') if not line: continue if not comment.fullmatch(line): header_match = subtask_header.fullmatch(line) cmd_match = cmd_line.fullmatch(line) if header_match: found_st = int(header_match.group(1)) validator = header_match.group(2) if st + 1 != found_st: ui.fatal_error( 'line %d: found subtask %d, but subtask %d was expected' % (lineno, found_st, st + 1)) st += 1 cmds[st] = {'validator': validator, 'groups': {}} elif cmd_match: if st == 0: ui.fatal_error( 'line %d: found command before declaring a subtask.' % lineno) group = cmd_match.group(1) cmd = cmd_match.group(2) args = _parse_args(cmd_match.group(3) or '') if group not in cmds[st]['groups']: cmds[st]['groups'][group] = [] if cmd == 'copy': if len(args) > 2: ui.fatal_error( 'line %d: command copy expects exactly one argument.' % lineno) cmds[st]['groups'][group].append({ 'cmd': 'copy', 'file': args[0], }) elif cmd == 'echo': cmds[st]['groups'][group].append({ 'cmd': 'echo', 'args': args }) else: f = FilePath(self._directory, cmd) if f.ext in ['.cpp', '.java', '.py', '.py2', '.py3']: cmds[st]['groups'][group].append({ 'cmd': f.ext[1:], 'source': cmd, 'args': args }) else: cmds[st]['groups'][group].append({ 'cmd': 'run', 'bin': cmd, 'args': args }) else: ui.fatal_error('line %d: error while parsing line `%s`\n' % (lineno, line)) return (st, cmds)