Exemple #1
0
    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)
Exemple #2
0
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]
Exemple #3
0
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)
Exemple #4
0
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)