Пример #1
0
 def test_feeder(self):
     feeder = Feeder()
     p = capture_stdout([sys.executable, 'echoer.py'], input=feeder,
                        async=True)
     try:
         lines = ('hello', 'goodbye')
         gen = iter(lines)
         # p.commands may not be set yet (separate thread)
         while not p.commands or p.commands[0].returncode is None:
             logger.debug('commands: %s', p.commands)
             try:
                 data = next(gen)
             except StopIteration:
                 break
             feeder.feed(data + '\n')
             if p.commands:
                 p.commands[0].poll()
             time.sleep(0.05)    # wait for child to return echo
     finally:
         # p.commands may not be set yet (separate thread)
         if p.commands:
             p.commands[0].terminate()
         feeder.close()
     self.assertEqual(p.stdout.text.splitlines(),
                      ['hello hello', 'goodbye goodbye'])
Пример #2
0
 def test_feeder(self):
     feeder = Feeder()
     p = capture_stdout([sys.executable, 'echoer.py'],
                        input=feeder,
                        async=True)
     try:
         lines = ('hello', 'goodbye')
         gen = iter(lines)
         # p.commands may not be set yet (separate thread)
         while not p.commands or p.commands[0].returncode is None:
             logger.debug('commands: %s', p.commands)
             try:
                 data = next(gen)
             except StopIteration:
                 break
             feeder.feed(data + '\n')
             if p.commands:
                 p.commands[0].poll()
             time.sleep(0.05)  # wait for child to return echo
     finally:
         # p.commands may not be set yet (separate thread)
         if p.commands:
             p.commands[0].terminate()
         feeder.close()
     self.assertEqual(p.stdout.text.splitlines(),
                      ['hello hello', 'goodbye goodbye'])
Пример #3
0
def cli_cmd_sync(cmd, log_obj=None, write_dots=False, on_out=None, on_err=None, cwd=None):
    """
    Runs command line task synchronously
    :return:
    """
    feeder = Feeder()
    p = run(cmd,
            input=feeder, async=True,
            stdout=Capture(buffer_size=1),
            stderr=Capture(buffer_size=1),
            cwd=cwd)

    out_acc = []
    err_acc = []
    ret_code = 1
    log = None
    close_log = False

    # Logging - either filename or logger itself
    if log_obj is not None:
        if isinstance(log_obj, types.StringTypes):
            delete_file_backup(log_obj, chmod=0o600)
            log = safe_open(log_obj, mode='w', chmod=0o600)
            close_log = True
        else:
            log = log_obj

    try:
        while len(p.commands) == 0:
            time.sleep(0.15)

        while p.commands[0].returncode is None:
            out = p.stdout.readline()
            err = p.stderr.readline()

            # If output - react on input challenges
            if out is not None and len(out) > 0:
                out_acc.append(out)

                if log is not None:
                    log.write(out)
                    log.flush()

                if write_dots:
                    sys.stderr.write('.')

                if on_out is not None:
                    on_out(out, feeder, p)

            # Collect error
            if err is not None and len(err) > 0:
                err_acc.append(err)

                if log is not None:
                    log.write(err)
                    log.flush()

                if write_dots:
                    sys.stderr.write('.')

                if on_err is not None:
                    on_err(err, feeder, p)

            p.commands[0].poll()
            time.sleep(0.01)

        ret_code = p.commands[0].returncode

        # Collect output to accumulator
        rest_out = p.stdout.readlines()
        if rest_out is not None and len(rest_out) > 0:
            for out in rest_out:
                out_acc.append(out)
                if log is not None:
                    log.write(out)
                    log.flush()
                if on_out is not None:
                    on_out(out, feeder, p)

        # Collect error to accumulator
        rest_err = p.stderr.readlines()
        if rest_err is not None and len(rest_err) > 0:
            for err in rest_err:
                err_acc.append(err)
                if log is not None:
                    log.write(err)
                    log.flush()
                if on_err is not None:
                    on_err(err, feeder, p)

        return ret_code, out_acc, err_acc

    finally:
        feeder.close()
        if close_log:
            log.close()
Пример #4
0
def cli_cmd_sync(cmd,
                 log_obj=None,
                 write_dots=False,
                 on_out=None,
                 on_err=None,
                 cwd=None,
                 shell=True,
                 readlines=True,
                 env=None,
                 **kwargs):
    """
    Runs command line task synchronously
    :return: return code, out_acc, err_acc
    """
    from sarge import run, Capture, Feeder
    import time
    import sys

    feeder = Feeder()
    p = run(cmd,
            input=feeder,
            async=True,
            stdout=Capture(timeout=7, buffer_size=1),
            stderr=Capture(timeout=7, buffer_size=1),
            cwd=cwd,
            shell=shell,
            env=env,
            **kwargs)

    out_acc = []
    err_acc = []
    ret_code = 1
    log = None
    close_log = False

    # Logging - either filename or logger itself
    if log_obj is not None:
        if isinstance(log_obj, basestring):
            delete_file_backup(log_obj, chmod=0o600)
            log = safe_open(log_obj, mode='w', chmod=0o600)
            close_log = True
        else:
            log = log_obj

    try:
        while len(p.commands) == 0:
            time.sleep(0.15)

        while p.commands[0].returncode is None:
            out, err = None, None

            if readlines:
                out = p.stdout.readline()
                err = p.stderr.readline()
            else:
                out = p.stdout.read(1)
                err = p.stdout.read(1)

            # If output - react on input challenges
            if out is not None and len(out) > 0:
                out_acc.append(out)

                if log is not None:
                    log.write(out)
                    log.flush()

                if write_dots:
                    sys.stderr.write('.')

                if on_out is not None:
                    on_out(out, feeder, p)

            # Collect error
            if err is not None and len(err) > 0:
                err_acc.append(err)

                if log is not None:
                    log.write(err)
                    log.flush()

                if write_dots:
                    sys.stderr.write('.')

                if on_err is not None:
                    on_err(err, feeder, p)

            p.commands[0].poll()
            time.sleep(0.01)

        ret_code = p.commands[0].returncode
        logger.debug('Command terminated with code: %s' % ret_code)

        # Collect output to accumulator
        rest_out = p.stdout.readlines()
        if rest_out is not None and len(rest_out) > 0:
            for out in rest_out:
                out_acc.append(out)
                if log is not None:
                    log.write(out)
                    log.flush()
                if on_out is not None:
                    on_out(out, feeder, p)

        # Collect error to accumulator
        rest_err = p.stderr.readlines()
        if rest_err is not None and len(rest_err) > 0:
            for err in rest_err:
                err_acc.append(err)
                if log is not None:
                    log.write(err)
                    log.flush()
                if on_err is not None:
                    on_err(err, feeder, p)

        return ret_code, out_acc, err_acc

    finally:
        feeder.close()
        if close_log:
            log.close()
Пример #5
0
    def run_internal(self):
        def preexec_function():
            os.setpgrp()

        cmd = self.cmd
        if self.shell:
            args_str = (" ".join(self.args) if isinstance(
                self.args, (list, tuple)) else self.args)

            if isinstance(cmd, (list, tuple)):
                cmd = " ".join(cmd)

            if args_str and len(args_str) > 0:
                cmd += " " + args_str

        else:
            if self.args and not isinstance(self.args, (list, tuple)):
                raise ValueError("!Shell requires array of args")
            if self.args:
                cmd += self.args

        self.using_stdout_cap = self.stdout is None
        self.using_stderr_cap = self.stderr is None
        self.feeder = Feeder()

        logger.debug("Starting command %s in %s" % (cmd, self.cwd))

        run_args = {}
        if self.preexec_setgrp:
            run_args['preexec_fn'] = preexec_function

        p = run(cmd,
                input=self.feeder,
                async_=True,
                stdout=self.stdout or Capture(timeout=0.1, buffer_size=1),
                stderr=self.stderr or Capture(timeout=0.1, buffer_size=1),
                cwd=self.cwd,
                env=self.env,
                shell=self.shell,
                **run_args)

        self.time_start = time.time()
        self.proc = p
        self.ret_code = 1
        self.out_acc, self.err_acc = [], []
        out_cur, err_cur = [""], [""]

        def process_line(line, is_err=False):
            dst = self.err_acc if is_err else self.out_acc
            dst.append(line)
            if self.log_out_during:
                if self.no_log_just_write:
                    dv = sys.stderr if is_err else sys.stdout
                    dv.write(line + "\n")
                    dv.flush()
                else:
                    logger.debug("Out: %s" % line.strip())
            if self.on_output:
                self.on_output(self, line, is_err)

        def add_output(buffers, is_err=False, finish=False):
            buffers = [
                x.decode("utf8") for x in buffers if x is not None and x != ""
            ]
            lines = [""]
            if not buffers and not finish:
                return

            dst_cur = err_cur if is_err else out_cur
            for x in buffers:
                clines = [v.strip("\r") for v in x.split("\n")]
                lines[-1] += clines[0]
                lines.extend(clines[1:])

            nlines = len(lines)
            dst_cur[0] += lines[0]
            if nlines > 1:
                process_line(dst_cur[0], is_err)
                dst_cur[0] = ""

            for line in lines[1:-1]:
                process_line(line, is_err)

            if not finish and nlines > 1:
                dst_cur[0] = lines[-1] or ""

            if finish:
                cline = dst_cur[0] if nlines == 1 else lines[-1]
                if cline:
                    process_line(cline, is_err)

        try:
            while len(p.commands) == 0:
                time.sleep(0.15)

            logger.debug("Program started, progs: %s" % len(p.commands))
            if p.commands[0] is None:
                self.is_running = False
                self.was_running = True
                logger.error("Program could not be started")
                return

            self.is_running = True
            self.on_change()
            out = None
            err = None

            while p.commands[0] and p.commands[0].returncode is None:
                if self.using_stdout_cap:
                    out = p.stdout.read(-1, False)
                    add_output([out], is_err=False)

                if self.using_stderr_cap:
                    err = p.stderr.read(-1, False)
                    add_output([err], is_err=True)

                if self.on_tick:
                    self.on_tick(self)

                p.commands[0].poll()
                if self.terminating and p.commands[0].returncode is None:
                    logger.debug("Terminating by sigint %s" % p.commands[0])
                    sarge_sigint(p.commands[0], signal.SIGTERM)
                    sarge_sigint(p.commands[0], signal.SIGINT)
                    logger.debug("Sigint sent")
                    logger.debug("Process closed")

                # If there is data, consume it right away.
                if (self.using_stdout_cap and out) or (self.using_stderr_cap
                                                       and err):
                    continue
                time.sleep(0.15)

            logger.debug("Runner while ended")
            p.wait()
            self.ret_code = p.commands[0].returncode if p.commands[0] else -1

            if self.using_stdout_cap:
                try_fnc(lambda: p.stdout.close())
                add_output(self.drain_stream(p.stdout, True), finish=True)

            if self.using_stderr_cap:
                try_fnc(lambda: p.stderr.close())
                add_output(self.drain_stream(p.stderr, True),
                           is_err=True,
                           finish=True)

            self.was_running = True
            self.is_running = False
            self.on_change()

            logger.debug("Program ended with code: %s" % self.ret_code)
            logger.debug("Command: %s" % cmd)

            if self.log_out_after:
                logger.debug("Std out: %s" % "\n".join(self.out_acc))
                logger.debug("Error out: %s" % "\n".join(self.err_acc))

        except Exception as e:
            self.is_running = False
            logger.error("Exception in async runner: %s" % (e, ))

        finally:
            self.was_running = True
            self.time_elapsed = time.time() - self.time_start
            try_fnc(lambda: self.feeder.close())
            try_fnc(lambda: self.proc.close())

            if self.on_finished:
                self.on_finished(self)
Пример #6
0
    def run_internal(self):
        def preexec_function():
            os.setpgrp()

        def preexec_setsid():
            logger.debug("setsid called")
            os.setsid()

        cmd = self.cmd
        if self.shell:
            args_str = (" ".join(self.args) if isinstance(
                self.args, (list, tuple)) else self.args)

            if isinstance(cmd, (list, tuple)):
                cmd = " ".join(cmd)

            if args_str and len(args_str) > 0:
                cmd += " " + args_str

        else:
            if self.args and not isinstance(self.args, (list, tuple)):
                raise ValueError("!Shell requires array of args")
            if self.args:
                cmd += self.args

        self.using_stdout_cap = self.stdout is None
        self.using_stderr_cap = self.stderr is None
        self.feeder = Feeder()

        logger.debug("Starting command %s in %s" % (cmd, self.cwd))

        run_args = {}
        if self.create_new_group:
            if self.is_win:
                self.win_create_process_group = True
            else:
                self.preexec_setsid = True

        if self.preexec_setgrp:
            run_args['preexec_fn'] = preexec_function
        if self.preexec_setsid:
            run_args['preexec_fn'] = preexec_setsid

        # https://stackoverflow.com/questions/44124338/trying-to-implement-signal-ctrl-c-event-in-python3-6
        if self.win_create_process_group:
            run_args['creationflags'] = subprocess.CREATE_NEW_PROCESS_GROUP

        p = run(cmd,
                input=self.feeder,
                async_=True,
                stdout=self.stdout or Capture(timeout=0.1, buffer_size=1),
                stderr=self.stderr or Capture(timeout=0.1, buffer_size=1),
                cwd=self.cwd,
                env=self.env,
                shell=self.shell,
                **run_args)

        self.p = p
        self.time_start = time.time()
        self.proc = p
        self.ret_code = 1
        self.out_acc, self.err_acc = [], []
        out_cur, err_cur = [""], [""]

        def process_line(line, is_err=False):
            dst = self.err_acc if is_err else self.out_acc
            dst.append(line)
            if self.log_out_during:
                if self.no_log_just_write:
                    dv = sys.stderr if is_err else sys.stdout
                    dv.write(line + "\n")
                    dv.flush()
                else:
                    logger.debug("Out: %s" % line.strip())
            if self.on_output:
                self.on_output(self, line, is_err)

        def add_output(buffers, is_err=False, finish=False):
            buffers = [
                x.decode("utf8") for x in buffers if x is not None and x != ""
            ]
            lines = [""]
            if not buffers and not finish:
                return

            dst_cur = err_cur if is_err else out_cur
            for x in buffers:
                clines = [v.strip("\r") for v in x.split("\n")]
                lines[-1] += clines[0]
                lines.extend(clines[1:])

            nlines = len(lines)
            dst_cur[0] += lines[0]
            if nlines > 1:
                process_line(dst_cur[0], is_err)
                dst_cur[0] = ""

            for line in lines[1:-1]:
                process_line(line, is_err)

            if not finish and nlines > 1:
                dst_cur[0] = lines[-1] or ""

            if finish:
                cline = dst_cur[0] if nlines == 1 else lines[-1]
                if cline:
                    process_line(cline, is_err)

        try:
            while len(p.commands) == 0:
                time.sleep(0.15)

            logger.debug("Program started, progs: %s, pid: %s" %
                         (len(p.commands), self.get_pid()))
            if p.commands[0] is None:
                self.is_running = False
                self.was_running = True
                logger.error("Program could not be started")
                return

            self.is_running = True
            self.on_change()
            out = None
            err = None

            while p.commands[0] and p.commands[0].returncode is None:
                if self.using_stdout_cap:
                    out = p.stdout.read(-1, False)
                    add_output([out], is_err=False)

                if self.using_stderr_cap:
                    err = p.stderr.read(-1, False)
                    add_output([err], is_err=True)

                if self.on_tick:
                    self.on_tick(self)

                p.commands[0].poll()
                if self.terminating and p.commands[0].returncode is None:
                    self.send_term_signals()
                    logger.debug("Process closed")

                # If there is data, consume it right away.
                if (self.using_stdout_cap and out) or (self.using_stderr_cap
                                                       and err):
                    continue
                time.sleep(0.15)

            try_fnc(lambda: p.commands[0].poll())
            self.ret_code = p.commands[0].returncode if p.commands[0] else -1

            logger.debug("Runner while-loop ended, retcode: %s" %
                         (p.commands[0].returncode, ))
            if self.do_not_block_runner_thread_on_termination:
                logger.debug(
                    "Not blocking runner thread on termination. Finishing, some output may be lost"
                )
                self.was_running = True
                self.is_running = False
                return

            if self.force_runner_thread_termination:
                self.was_running = True
                self.is_running = False
                return

            logger.debug("Waiting for process to complete")
            p.wait()

            self.ret_code = p.commands[0].returncode if p.commands[0] else -1
            if self.do_drain_streams and self.using_stdout_cap:
                logger.debug("Draining stdout stream")
                try_fnc(lambda: p.stdout.close())
                add_output(self.drain_stream(p.stdout, True), finish=True)

            if self.do_drain_streams and self.using_stderr_cap:
                logger.debug("Draining stderr stream")
                try_fnc(lambda: p.stderr.close())
                add_output(self.drain_stream(p.stderr, True),
                           is_err=True,
                           finish=True)

            self.was_running = True
            self.is_running = False
            self.on_change()

            logger.debug("Program ended with code: %s" % self.ret_code)
            logger.debug("Command: %s" % cmd)

            if self.log_out_after:
                logger.debug("Std out: %s" % "\n".join(self.out_acc))
                logger.debug("Error out: %s" % "\n".join(self.err_acc))

        except Exception as e:
            self.is_running = False
            logger.error("Exception in async runner: %s" % (e, ))

        finally:
            self.was_running = True
            self.time_elapsed = time.time() - self.time_start
            rtt_utils.try_fnc(lambda: self.feeder.close())

            if not self.do_not_block_runner_thread_on_termination:
                rtt_utils.try_fnc(lambda: self.proc.close())

            if self.on_finished:
                self.on_finished(self)
Пример #7
0
class AsyncRunner:
    def __init__(self,
                 cmd,
                 args=None,
                 stdout=None,
                 stderr=None,
                 cwd=None,
                 shell=True,
                 env=None):
        self.cmd = cmd
        self.args = args
        self.on_finished = None
        self.on_output = None
        self.on_tick = None
        self.no_log_just_write = False
        self.log_out_during = True
        self.log_out_after = True
        self.stdout = stdout
        self.stderr = stderr
        self.cwd = cwd
        self.shell = shell
        self.env = env

        self.create_new_group = None
        self.preexec_setgrp = False
        self.preexec_setsid = False
        self.win_create_process_group = False

        self.using_stdout_cap = True
        self.using_stderr_cap = True
        self.do_drain_streams = True
        self.do_not_block_runner_thread_on_termination = False
        self.force_runner_thread_termination = False
        self.try_terminate_children_for_shell = False

        self.ret_code = None
        self.out_acc = []
        self.err_acc = []
        self.time_start = None
        self.time_elapsed = None
        self.feeder = None
        self.proc = None
        self.is_running = False
        self.was_running = False
        self.terminating = False
        self.thread = None
        self.p = None
        self.terminate_timeout = 0.5
        self.signal_timeout = 0.5
        self.terminate_ctrlc_timeout = 0.5
        self.is_win = sys.platform.startswith('win')

    def run(self):
        try:
            self.run_internal()
        except Exception as e:
            self.is_running = False
            logger.error("Unexpected exception in runner: %s" % (e, ),
                         exc_info=e)
        finally:
            self.was_running = True
            logger.debug("Runner thread finished")

        if self.force_runner_thread_termination:
            raise SystemError("Terminate runner")

    def __del__(self):
        self.deinit()

    def deinit(self):
        rtt_utils.try_fnc(lambda: self.feeder.close())

        if not self.proc:
            return

        if self.do_not_block_runner_thread_on_termination or self.force_runner_thread_termination:
            return

        if self.using_stdout_cap:
            rtt_utils.try_fnc(lambda: self.proc.stdout.close())

        if self.using_stderr_cap:
            rtt_utils.try_fnc(lambda: self.proc.stderr.close())

        rtt_utils.try_fnc(lambda: self.proc.close())

    def drain_stream(self, s, block=False, timeout=0.15):
        ret = []
        while True:
            rs = s.read(-1, block, timeout)
            if not rs:
                break
            ret.append(rs)
        return ret

    def run_internal(self):
        def preexec_function():
            os.setpgrp()

        def preexec_setsid():
            logger.debug("setsid called")
            os.setsid()

        cmd = self.cmd
        if self.shell:
            args_str = (" ".join(self.args) if isinstance(
                self.args, (list, tuple)) else self.args)

            if isinstance(cmd, (list, tuple)):
                cmd = " ".join(cmd)

            if args_str and len(args_str) > 0:
                cmd += " " + args_str

        else:
            if self.args and not isinstance(self.args, (list, tuple)):
                raise ValueError("!Shell requires array of args")
            if self.args:
                cmd += self.args

        self.using_stdout_cap = self.stdout is None
        self.using_stderr_cap = self.stderr is None
        self.feeder = Feeder()

        logger.debug("Starting command %s in %s" % (cmd, self.cwd))

        run_args = {}
        if self.create_new_group:
            if self.is_win:
                self.win_create_process_group = True
            else:
                self.preexec_setsid = True

        if self.preexec_setgrp:
            run_args['preexec_fn'] = preexec_function
        if self.preexec_setsid:
            run_args['preexec_fn'] = preexec_setsid

        # https://stackoverflow.com/questions/44124338/trying-to-implement-signal-ctrl-c-event-in-python3-6
        if self.win_create_process_group:
            run_args['creationflags'] = subprocess.CREATE_NEW_PROCESS_GROUP

        p = run(cmd,
                input=self.feeder,
                async_=True,
                stdout=self.stdout or Capture(timeout=0.1, buffer_size=1),
                stderr=self.stderr or Capture(timeout=0.1, buffer_size=1),
                cwd=self.cwd,
                env=self.env,
                shell=self.shell,
                **run_args)

        self.p = p
        self.time_start = time.time()
        self.proc = p
        self.ret_code = 1
        self.out_acc, self.err_acc = [], []
        out_cur, err_cur = [""], [""]

        def process_line(line, is_err=False):
            dst = self.err_acc if is_err else self.out_acc
            dst.append(line)
            if self.log_out_during:
                if self.no_log_just_write:
                    dv = sys.stderr if is_err else sys.stdout
                    dv.write(line + "\n")
                    dv.flush()
                else:
                    logger.debug("Out: %s" % line.strip())
            if self.on_output:
                self.on_output(self, line, is_err)

        def add_output(buffers, is_err=False, finish=False):
            buffers = [
                x.decode("utf8") for x in buffers if x is not None and x != ""
            ]
            lines = [""]
            if not buffers and not finish:
                return

            dst_cur = err_cur if is_err else out_cur
            for x in buffers:
                clines = [v.strip("\r") for v in x.split("\n")]
                lines[-1] += clines[0]
                lines.extend(clines[1:])

            nlines = len(lines)
            dst_cur[0] += lines[0]
            if nlines > 1:
                process_line(dst_cur[0], is_err)
                dst_cur[0] = ""

            for line in lines[1:-1]:
                process_line(line, is_err)

            if not finish and nlines > 1:
                dst_cur[0] = lines[-1] or ""

            if finish:
                cline = dst_cur[0] if nlines == 1 else lines[-1]
                if cline:
                    process_line(cline, is_err)

        try:
            while len(p.commands) == 0:
                time.sleep(0.15)

            logger.debug("Program started, progs: %s, pid: %s" %
                         (len(p.commands), self.get_pid()))
            if p.commands[0] is None:
                self.is_running = False
                self.was_running = True
                logger.error("Program could not be started")
                return

            self.is_running = True
            self.on_change()
            out = None
            err = None

            while p.commands[0] and p.commands[0].returncode is None:
                if self.using_stdout_cap:
                    out = p.stdout.read(-1, False)
                    add_output([out], is_err=False)

                if self.using_stderr_cap:
                    err = p.stderr.read(-1, False)
                    add_output([err], is_err=True)

                if self.on_tick:
                    self.on_tick(self)

                p.commands[0].poll()
                if self.terminating and p.commands[0].returncode is None:
                    self.send_term_signals()
                    logger.debug("Process closed")

                # If there is data, consume it right away.
                if (self.using_stdout_cap and out) or (self.using_stderr_cap
                                                       and err):
                    continue
                time.sleep(0.15)

            try_fnc(lambda: p.commands[0].poll())
            self.ret_code = p.commands[0].returncode if p.commands[0] else -1

            logger.debug("Runner while-loop ended, retcode: %s" %
                         (p.commands[0].returncode, ))
            if self.do_not_block_runner_thread_on_termination:
                logger.debug(
                    "Not blocking runner thread on termination. Finishing, some output may be lost"
                )
                self.was_running = True
                self.is_running = False
                return

            if self.force_runner_thread_termination:
                self.was_running = True
                self.is_running = False
                return

            logger.debug("Waiting for process to complete")
            p.wait()

            self.ret_code = p.commands[0].returncode if p.commands[0] else -1
            if self.do_drain_streams and self.using_stdout_cap:
                logger.debug("Draining stdout stream")
                try_fnc(lambda: p.stdout.close())
                add_output(self.drain_stream(p.stdout, True), finish=True)

            if self.do_drain_streams and self.using_stderr_cap:
                logger.debug("Draining stderr stream")
                try_fnc(lambda: p.stderr.close())
                add_output(self.drain_stream(p.stderr, True),
                           is_err=True,
                           finish=True)

            self.was_running = True
            self.is_running = False
            self.on_change()

            logger.debug("Program ended with code: %s" % self.ret_code)
            logger.debug("Command: %s" % cmd)

            if self.log_out_after:
                logger.debug("Std out: %s" % "\n".join(self.out_acc))
                logger.debug("Error out: %s" % "\n".join(self.err_acc))

        except Exception as e:
            self.is_running = False
            logger.error("Exception in async runner: %s" % (e, ))

        finally:
            self.was_running = True
            self.time_elapsed = time.time() - self.time_start
            rtt_utils.try_fnc(lambda: self.feeder.close())

            if not self.do_not_block_runner_thread_on_termination:
                rtt_utils.try_fnc(lambda: self.proc.close())

            if self.on_finished:
                self.on_finished(self)

    def test_is_running(self):
        try_fnc(lambda: self.p.commands[0].poll())
        return self.is_running and self.p.commands[
            0] and self.p.commands[0].returncode is None

    def sleep_if_running(self, tm):
        stime = time.time()
        while time.time() - stime < tm:
            if not self.test_is_running():
                return False
            time.sleep(0.2)
        return True

    def send_term_signals(self):
        p = self.p
        pid = self.get_pid()
        logger.debug("Terminating by sigint %s, PID: %s" %
                     (p.commands[0], pid))

        test_is_running = self.test_is_running
        sleep_if_running = self.sleep_if_running

        if not test_is_running(): return

        # PGid works only on POSIX
        if self.preexec_setsid and pid is not None:
            pgid = os.getpgid(pid)
            logger.debug("Terminating process group %s for process %s" %
                         (pgid, pid))
            logger.debug("Sending pg SIGINT")
            try_fnc(lambda: os.killpg(pgid, signal.SIGINT))
            sleep_if_running(self.terminate_ctrlc_timeout)

            if not test_is_running(): return
            logger.debug("Sending pg SIGTERM")
            try_fnc(lambda: os.killpg(pgid, signal.SIGTERM))
            sleep_if_running(self.terminate_timeout)
            if not test_is_running(): return

            logger.debug("Sending pg SIGKILL")
            try_fnc(lambda: os.killpg(pgid, signal.SIGKILL))
            sleep_if_running(self.signal_timeout)
            if not test_is_running(): return

        if self.is_win:
            cmd = "tasklist /fi \"pid eq %s\"" % pid
            logger.debug("Retrieving process info on the process %s" % (pid, ))
            subprocess.run(cmd, shell=True)
            time.sleep(self.signal_timeout)

        if self.is_win:
            if self.shell and self.try_terminate_children_for_shell:
                logger.debug(
                    "Experimental: sending CTRL+C to children. "
                    "May cause interruption of all processes running in the console"
                )
                self._win_terminate_children(pid)

            # Windows - process has to be process leader, otherwise this sends signal to everyone
            # Thus do this only if win && is process group leader
            if self.win_create_process_group:
                logger.debug("Trying to invoke CTRL+C (win) in process group")
                try_fnc(lambda: os.kill(pid, signal.CTRL_C_EVENT))
                try_fnc(lambda: p.commands[0].process.send_signal(
                    signal.CTRL_C_EVENT))
                sleep_if_running(self.terminate_ctrlc_timeout)

            cmd = "Taskkill /PID %s /F /T" % pid
            logger.debug("Closing process with taskkill: %s" % (cmd, ))
            subprocess.run(cmd, shell=True)
            time.sleep(self.terminate_timeout)

            logger.debug("Sending win SIGTERM")
            try_fnc(lambda: os.kill(pid, signal.SIGTERM))
            sleep_if_running(self.terminate_timeout)

            logger.debug("Sending win SIGKILL")
            try_fnc(lambda: os.kill(pid, signal.SIGKILL))
            sleep_if_running(self.terminate_timeout)

            try_fnc(lambda: p.commands[0].terminate())
            time.sleep(self.signal_timeout)
            try_fnc(lambda: p.commands[0].kill())
            time.sleep(self.signal_timeout)
            return

        # Posix process termination
        logger.debug("Sending SIGINT")
        try_fnc(lambda: sarge_sigint(p.commands[0], signal.SIGINT))
        sleep_if_running(self.terminate_ctrlc_timeout)

        logger.debug("Sending SIGTERM")
        try_fnc(lambda: p.commands[0].terminate())
        sleep_if_running(self.terminate_timeout)
        if not test_is_running(): return

        logger.debug("Sending SIGHUP")
        try_fnc(lambda: sarge_sigint(p.commands[0], signal.SIGHUP))
        sleep_if_running(self.signal_timeout)
        if not test_is_running(): return

        logger.debug("Sending SIGTERM")
        try_fnc(lambda: sarge_sigint(p.commands[0], signal.SIGTERM))
        sleep_if_running(self.signal_timeout)
        if not test_is_running(): return

        logger.debug("Sending SIGKILL")
        try_fnc(lambda: p.commands[0].kill())
        try_fnc(lambda: sarge_sigint(p.commands[0], signal.SIGKILL))

    def _win_get_children(self, pid):
        cmd = "wmic process where (ParentProcessId=%s) get ProcessId" % pid
        logger.debug("Obtaining child processes for %s: %s" % (
            pid,
            cmd,
        ))
        r = subprocess.run(cmd,
                           shell=True,
                           check=True,
                           text=True,
                           stderr=subprocess.STDOUT,
                           stdout=subprocess.PIPE)
        lines = [x.strip() for x in r.stdout.splitlines()[1:] if x.strip()]
        return [z for z in [try_fnc(lambda: int(y)) for y in lines] if z]

    def _win_terminate_children(self, pid):
        """
        Tries to send CTRL+C signal to the child process - useful when command is executed with shell=True.
        On Windows, CTRL+C signal is not transmitted to the child process from cmd.exe (apparently).

        This solution does not work with `create_new_group`, from some reason, sending CTRL+C event does not work
        to new sessions - or at least we did not observe CTRL+C event in our java process.

        On the other hand - calling CTRL+C to children process in this method also causes interrupt event
        in the main python code (caller of the shutdown()). From this reason all sleeps has to be guarded with
        KeyboardInterrupt checking. This indicates we cannot just send CTRL+C to a child process selectively
        but that it is broadcasted to the whole session. -> kills all other running tasks by sending them CTRL+C

        Useful explanation:
            - send_signal(CTRL_C_EVENT) does not work because CTRL_C_EVENT is only for os.kill. [REF1]
            - os.kill(CTRL_C_EVENT) sends the signal to all processes running in the current cmd window [REF2]
            - Popen(..., creationflags=CREATE_NEW_PROCESS_GROUP) does not work because CTRL_C_EVENT is ignored for
                process groups [REF2]. This is a bug in the python documentation [REF3].

        [REF1]: http://docs.python.org/library/signal.html#signal.CTRL_C_EVENT
        [REF2]: http://msdn.microsoft.com/en-us/library/windows/desktop/ms683155%28v=vs.85%29.aspx
        [REF3]: http://docs.python.org/library/subprocess.html#subprocess.Popen.send_signal

        Proposed workaround:
            - Let your program run in a different cmd window with the Windows shell command start.
            - Add a CTRL-C request wrapper between your control application and the application which should get the
              CTRL-C signal. The wrapper will run in the same cmd window as the application which should get the
              CTRL-C signal.
            - The wrapper will shutdown itself and the program which should get the CTRL-C signal by sending all
               processes in the cmd window the CTRL_C_EVENT.
            - The control program should be able to request the wrapper to send the CTRL-C signal. This might be
              implemented trough IPC means, e.g. sockets.

        Overall, it is quite pain to make implement graceful shutdown of child processes by sending CTRL+C signal on
        Windows. We explored several combinations of settings, none of which enabled sending targeted CTRL+C signal.
        We resorted to calling taskkill with killing all child processes (/T). Without this we were not able to
        terminate child process (when shell=True) is used, wrappers were hanging on sarge process closing,
        processes stayed after python finished, console was uninterruptable and so on.
        src:
            - https://stackoverflow.com/questions/7085604/sending-c-to-python-subprocess-objects-on-windows
            - https://stackoverflow.com/questions/44124338/trying-to-implement-signal-ctrl-c-event-in-python3-6/44128151
        """
        try:
            children = self._win_get_children(pid)
            logger.debug("Children processes of %s: %s" % (pid, children))
            if len(children) == 0:
                return

            for cpid in children:
                logger.debug(
                    "Trying to invoke CTRL+C (win) for %s (parent %s)" %
                    (cpid, pid))
                try:
                    try_fnc(lambda: os.kill(cpid, signal.CTRL_C_EVENT))
                    time.sleep(0.1)
                except KeyboardInterrupt:
                    logger.debug("Keyboard interrupt _win_terminate_children")

            self.sleep_if_running(self.terminate_ctrlc_timeout)
            for cpid in children:
                logger.debug("Sending win SIGTERM for %s (parent %s)" %
                             (cpid, pid))
                try_fnc(lambda: os.kill(cpid, signal.SIGTERM))

            self.sleep_if_running(self.terminate_timeout)

        except Exception as e:
            logger.debug("Child process termination failed: %s" % (e, ))

    def on_change(self):
        pass

    def get_pid(self):
        try:
            return self.p.commands[0].process.pid
        except:
            return None

    def wait(self, timeout=None, require_ok=False):
        tstart = time.time()
        while self.is_running:
            if timeout is not None and time.time() - tstart > timeout:
                raise Exception("Timeout")
            try:
                time.sleep(0.1)
            except KeyboardInterrupt:
                logger.debug("Keyboard interrupt wait()")

        if require_ok and self.ret_code != 0:
            raise Exception("Return code is not zero: %s" % self.ret_code)

    def shutdown(self, timeout=None):
        if not self.is_running:
            return

        try:
            self.terminating = True
            time.sleep(1)
        except KeyboardInterrupt:
            logger.debug("Shutdown keyboard interrupt")

        # Terminating with sigint
        logger.debug("Waiting for program to terminate...")
        tstart = time.time()
        while self.is_running:
            if timeout is not None and time.time() - tstart > timeout:
                raise Exception("Timeout")
            try:
                time.sleep(0.1)
            except KeyboardInterrupt:
                logger.debug("Shutdown Keyboard interrupt loop")

        logger.debug("Program terminated")
        self.deinit()

    def start(self, wait_running=True, timeout=None):
        install_sarge_filter()
        self.thread = threading.Thread(target=self.run, args=())
        self.thread.setDaemon(False)

        self.terminating = False
        self.is_running = False
        self.thread.start()

        if not wait_running:
            self.is_running = True
            return

        tstart = time.time()
        while not self.is_running and not self.was_running:
            if timeout is not None and time.time() - tstart > timeout:
                raise Exception("Timeout")
            time.sleep(0.1)
        return self
Пример #8
0
import re

from sarge import Capture, Feeder, run

f = Feeder()
c = Capture(buffer_size=1)
p = run('python login_test.py', async_=True, stdout=c, input=f)

c.expect('Username:'******'input username')
f.feed('user\n')

c.expect('Password:'******'input password')
f.feed('pass\n')

VERIFICATION_CODE_REGEX = re.compile(rb'Input verification code \((\d{4})\): ')
match = c.expect(VERIFICATION_CODE_REGEX)
print('input verification code', match.group(1))
f.feed(match.group(1) + b'\n')

c.expect('>>>', timeout=5)
f.feed('print(1 + 1)\n')
f.feed('exit()\n')
p.wait()

print('final output:\n', b''.join(c.readlines()).decode('utf-8'))