## ecr.core._Runner

src/ecr/core/_Runner.py
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    import subprocess
    import time
    from enum import Enum
    from typing import Dict, Optional, Tuple, Callable

    from .. import log, ui
    from ..types import CommandList
    from ..ui import color

    class RunResult(Enum):
        Success: int = 0
        Error: int = 1
        TimeOut: int = 2

    class Runner:
        def __init__(self, proc: subprocess.Popen, io: str, timelimit: Optional[int] = None):
            self.proc: subprocess.Popen = proc
            self.timeLimit: Optional[int] = timelimit
            self.communicate = self.proc.communicate
            self.usedTime: float = 0
            self.isRunning: bool = False
            self.io: str = io
            self.canInput: bool = io[0] == "s"

        def terminate(self) -> None:
            self.proc.terminate()
            # os.kill(self.proc.pid, 9)
            self.isRunning = False
            self.proc.wait()

        def input(self, data: str)->None:
            self.proc.stdin.write(data.encode("utf-8"))
            self.proc.stdin.flush()

        def run(self)->Tuple[RunResult, Optional[int]]:
            self.isRunning = True
            isTimeout = False
            bg_time = time.time()
            try:
                # if self.io[1] != "s":  # not stdout
                self.communicate(timeout=self.timeLimit)
                # pylint: disable=W0105
                """else:  # stdout for async
                    while not self.proc.poll() and self.isRunning:
                        if self.timeLimit != None and time.time() - bg_time > self.timeLimit:
                            raise subprocess.TimeoutExpired(
                                self.proc.cmd, self.timeLimit)
                        s = self.proc.stdout.readline().decode("utf-8")
                        safeOutput(s)
                    if self.proc.poll() != 0 and self.isRunning:  # hit error
                        s = self.proc.stderr.read().decode("utf-8")
                        safeOutput(s)"""
            except subprocess.TimeoutExpired:
                isTimeout = True
                self.terminate()
            except KeyboardInterrupt:
                self.terminate()
            finally:
                ed_time = time.time()
                self.isRunning = False
                self.usedTime = ed_time - bg_time
            if isTimeout:
                return (RunResult.TimeOut, self.proc.returncode)
            if self.proc.returncode != 0:
                return (RunResult.Error, self.proc.returncode)
            return (RunResult.Success, self.proc.returncode)

    def runCommands(io: str, commands: CommandList, variables: Dict[str, str], wdir: str, getSystemCommand: Callable[[str], str], inputFile: str, outputFile: str, defaultTimeLimit: Optional[int] = None, showLog: bool = True) -> bool:
        errf = color.useRed("×")
        passf = color.useGreen("√")
        isSuccess = True
        sumStep = len(commands)
        cwd = wdir
        console = ui.getConsole()
        for ind, bcmd in enumerate(commands):
            if not isSuccess:
                break
            cmd, timelimit = None, None
            if not isinstance(bcmd, str):
                cmd, timelimit = bcmd
            else:
                cmd, timelimit = bcmd, defaultTimeLimit
            _cmd = cmd.format(**variables)
            if showLog:
                console.write(
                    "(", color.useYellow(str(ind+1)), f"/{sumStep}) ", _cmd, sep="")
            proc = None
            rresult, retcode = None, None
            runner = None
            try:
                rcmd = getSystemCommand(_cmd)
                if ind == sumStep - 1:  # last command
                    if io[0] == "s":  # stdin
                        timelimit = None
                    if showLog:
                        console.write("-"*20)
                    proc = subprocess.Popen(
                        rcmd,
                        cwd=cwd,
                        stdin=None if io[0] == "s"
                        else open(inputFile, "r"),
                        stdout=None if io[1] == "s"
                        else open(outputFile, "w"),
                        stderr=None)
                else:
                    proc = subprocess.Popen(
                        rcmd,
                        cwd=cwd,
                        stdin=None, stdout=None, stderr=None)

                runner = Runner(proc=proc, io=io, timelimit=timelimit)
                rresult, retcode = runner.run()
            except BaseException:
                log.errorWithException(f"Run command failed: {rcmd}")
                isSuccess = False
            finally:
                if ind == sumStep - 1:  # last command
                    if showLog:
                        console.write("-"*20)
                if showLog:
                    console.write(
                        "   ->",
                        passf if retcode == 0 else errf,
                        f"{round(runner.usedTime*1000)/1000}s")
                if rresult != RunResult.Success:
                    if showLog:
                        console.write(
                            "(", color.useRed(str(ind + 1)), f"/{sumStep}) ",
                            _cmd, " -> ", color.useRed(str(retcode)), sep="", end=" ")
                        if rresult == RunResult.TimeOut:
                            console.write(color.useRed("Time out"))
                        else:
                            console.write()
                    isSuccess = False
        return isSuccess