Esempio n. 1
0
def test_ensure_width(monkeypatch):
    screen = HistoryScreen(5, 5, history=50)
    screen.set_mode(mo.LNM)

    monkeypatch.setattr(Stream, "escape",
                        dict(N="next_page", P="prev_page", **Stream.escape))
    stream = Stream()
    stream.attach(screen)

    for idx in range(len(screen.buffer) * 5):
        for ch in str(idx) + os.linesep:
            stream.feed(ch)

    assert screen.display == ["21   ", "22   ", "23   ", "24   ", "     "]

    # a) shrinking the screen, expecting the lines displayed to
    #    be truncated.
    screen.resize(5, 2)
    stream.feed(ctrl.ESC + "P")

    assert all(len(l) != 2 for l in screen.history.top)
    assert all(len(l) == 2 for l in screen.history.bottom)
    assert screen.display == ["18", "19", "20", "21", "22"]

    # b) expading the screen, expecting the lines displayed to
    #    be filled with whitespace characters.
    screen.resize(5, 10)
    stream.feed(ctrl.ESC + "N")

    assert all(len(l) == 10 for l in list(screen.history.top)[-3:])
    assert all(len(l) != 10 for l in screen.history.bottom)
    assert screen.display == [
        "21        ", "22        ", "23        ", "24        ", "          "
    ]
Esempio n. 2
0
def test_draw(monkeypatch):
    screen = HistoryScreen(5, 5, history=50)
    screen.set_mode(mo.LNM)
    escape = dict(Stream.escape)
    escape.update({b"N": "next_page", b"P": "prev_page"})
    monkeypatch.setattr(Stream, "escape", escape)

    stream = Stream(screen)
    for idx in range(len(screen.buffer) * 5):
        stream.feed((str(idx) + os.linesep).encode("utf-8"))

    assert screen.display == [
        "21   ",
        "22   ",
        "23   ",
        "24   ",
        "     "
    ]

    # a) doing a pageup and then a draw -- expecting the screen
    #    to scroll to the bottom before drawing anything.
    stream.feed(ctrl.ESC + b"P")
    stream.feed(ctrl.ESC + b"P")
    stream.feed(ctrl.ESC + b"N")
    stream.feed(b"x")

    assert screen.display == [
        "21   ",
        "22   ",
        "23   ",
        "24   ",
        "x    "
    ]
Esempio n. 3
0
def test_unicode():
    screen = Screen(4, 2)
    stream = Stream(screen)

    try:
        stream.feed("тест".encode("utf-8"))
    except UnicodeDecodeError:
        pytest.fail("Check your code -- we do accept unicode.")

    assert screen.display == ["тест", "    "]
Esempio n. 4
0
def test_private_report_device_attributes():
    # Some console apps (e.g. ADOM) might add ``?`` to the DA request,
    # even though the VT102/VT220 spec does not allow this.
    screen = Screen(10, 10)
    stream = Stream(screen)

    acc = []
    screen.write_process_input = acc.append
    stream.feed(ctrl.CSI + b"?0c")
    assert acc.pop() == ctrl.CSI + b"?6c"
Esempio n. 5
0
def test_private_report_device_attributes():
    # Some console apps (e.g. ADOM) might add ``?`` to the DA request,
    # even though the VT102/VT220 spec does not allow this.
    screen = Screen(10, 10)
    stream = Stream(screen)

    acc = []
    screen.write_process_input = acc.append
    stream.feed(ctrl.CSI + b"?0c")
    assert acc.pop() == ctrl.CSI + b"?6c"
Esempio n. 6
0
def test_unicode():
    screen = Screen(4, 2)
    stream = Stream(screen)

    try:
        stream.feed("тест".encode("utf-8"))
    except UnicodeDecodeError:
        pytest.fail("Check your code -- we do accept unicode.")

    assert screen.display == ["тест", "    "]
def main(path):
    font = ImageFont.truetype("SourceCodePro-Regular.otf", size=16)
    with tempfile.TemporaryDirectory() as dirname, open(path, 'r') as f:
        j = json.load(f)
        screen = Screen(j['width'], j['height'])
        stream = Stream()
        stream.attach(screen)
        i = 0
        for delay, text in j['stdout']:
            img = Image.new("RGBA", (1280, 960))
            draw = ImageDraw.Draw(img)
            stream.feed(text)
            draw.multiline_text((0, 0), "\n".join(screen.display), font=font)
            img.save(os.path.join(dirname, '%04d.gif' % i))
            i = i + 1
        subprocess.run('gifsicle -d 10 --loopcount -O2 --output %s.gif %s/*.gif' % (path, dirname),
                       shell=True, check=True)
Esempio n. 8
0
def test_ensure_width(monkeypatch):
    screen = HistoryScreen(5, 5, history=50)
    screen.set_mode(mo.LNM)

    monkeypatch.setattr(Stream, "escape",
                        dict(N="next_page", P="prev_page", **Stream.escape))
    stream = Stream()
    stream.attach(screen)

    for idx in range(len(screen.buffer) * 5):
        for ch in str(idx) + os.linesep:
            stream.feed(ch)

    assert screen.display == [
        "21   ",
        "22   ",
        "23   ",
        "24   ",
        "     "
    ]

    # a) shrinking the screen, expecting the lines displayed to
    #    be truncated.
    screen.resize(5, 2)
    stream.feed(ctrl.ESC + "P")

    assert all(len(l) != 2 for l in screen.history.top)
    assert all(len(l) == 2 for l in screen.history.bottom)
    assert screen.display == [
        "18",
        "19",
        "20",
        "21",
        "22"
    ]

    # b) expading the screen, expecting the lines displayed to
    #    be filled with whitespace characters.
    screen.resize(5, 10)
    stream.feed(ctrl.ESC + "N")

    assert all(len(l) == 10 for l in list(screen.history.top)[-3:])
    assert all(len(l) != 10 for l in screen.history.bottom)
    assert screen.display == [
        "21        ",
        "22        ",
        "23        ",
        "24        ",
        "          "
    ]
Esempio n. 9
0
def test_ensure_width():
    screen = HistoryScreen(5, 5, history=50)
    stream = Stream()
    stream.attach(screen)
    stream.escape["N"] = "next_page"
    stream.escape["P"] = "prev_page"

    for idx in range(len(screen) * 5):
        map(stream.feed, unicode(idx) + "\n")

    assert screen.display == [
        "21   ",
        "22   ",
        "23   ",
        "24   ",
        "     "
    ]

    # a) shrinking the screen, expecting the lines displayed to
    #    be truncated.
    screen.resize(5, 2)
    stream.feed(ctrl.ESC + "P")

    assert all(len(l) is not 2 for l in screen.history.top)
    assert all(len(l) is 2 for l in screen.history.bottom)
    assert screen.display == [
        "18",
        "19",
        "20",
        "21",
        "22"
    ]

    # b) expading the screen, expecting the lines displayed to
    #    be filled with whitespace characters.
    screen.resize(5, 10)
    stream.feed(ctrl.ESC + "N")

    assert all(len(l) is 10 for l in list(screen.history.top)[-3:])
    assert all(len(l) is not 10 for l in screen.history.bottom)
    assert screen.display == [
        "21        ",
        "22        ",
        "23        ",
        "24        ",
        "          "
    ]
Esempio n. 10
0
def main(path):
    font = ImageFont.truetype("SourceCodePro-Regular.otf", size=16)
    with tempfile.TemporaryDirectory() as dirname, open(path, 'r') as f:
        j = json.load(f)
        screen = Screen(j['width'], j['height'])
        stream = Stream()
        stream.attach(screen)
        i = 0
        for delay, text in j['stdout']:
            img = Image.new("RGBA", (1280, 960))
            draw = ImageDraw.Draw(img)
            stream.feed(text)
            draw.multiline_text((0, 0), "\n".join(screen.display), font=font)
            img.save(os.path.join(dirname, '%04d.gif' % i))
            i = i + 1
        subprocess.run(
            'gifsicle -d 10 --loopcount -O2 --output %s.gif %s/*.gif' %
            (path, dirname),
            shell=True,
            check=True)
Esempio n. 11
0
def test_draw():
    screen = HistoryScreen(5, 5, history=50)
    screen.set_mode(mo.LNM)
    stream = Stream()
    stream.attach(screen)
    stream.escape["N"] = "next_page"
    stream.escape["P"] = "prev_page"

    for idx in range(len(screen) * 5):
        for ch in str(idx) + os.linesep:
            stream.feed(ch)

    assert screen.display == [
        "21   ",
        "22   ",
        "23   ",
        "24   ",
        "     "
    ]

    # a) doing a pageup and then a draw -- expecting the screen
    #    to scroll to the bottom before drawing anything.
    stream.feed(ctrl.ESC + "P")
    stream.feed(ctrl.ESC + "P")
    stream.feed(ctrl.ESC + "N")
    stream.feed("x")

    assert screen.display == [
        "21   ",
        "22   ",
        "23   ",
        "24   ",
        "x    "
    ]
Esempio n. 12
0
    def _create_terminal(self, options):
        rows, cols = options['geometry']

        self._screen = Screen(cols, rows)
        self._stream = Stream(self._screen)
Esempio n. 13
0
class PexepctMixin(object):
    def __init__(self, PS1_re, any_PS_re):
        self.PS1_re = re.compile(PS1_re)
        self.any_PS_re = re.compile(any_PS_re)

        self.last_output = []

    def _spawn_interpreter(self,
                           cmd,
                           options,
                           wait_first_prompt=True,
                           first_prompt_timeout=None):
        if first_prompt_timeout is None:
            first_prompt_timeout = options['x']['dfl_timeout']

        rows, cols = options['geometry']
        self._terminal_default_geometry = (rows, cols)

        env = os.environ.copy()
        env.update({'LINES': str(rows), 'COLUMNS': str(cols)})

        self._drop_output()  # there shouldn't be any output yet but...
        self.interpreter = pexpect.spawn(cmd,
                                         echo=False,
                                         encoding=self.encoding,
                                         dimensions=(rows, cols),
                                         env=env)
        self.interpreter.delaybeforesend = options['x']['delaybeforesend']
        self.interpreter.delayafterread = None

        self._create_terminal(options)

        if wait_first_prompt:
            self._expect_prompt(options,
                                timeout=first_prompt_timeout,
                                prompt_re=self.PS1_re)
            self._drop_output()  # discard banner and things like that

    def interact(self,
                 send='\n',
                 escape_character=chr(29),
                 input_filter=None,
                 output_filter=None):  # pragma: no cover
        def ensure_cooked_mode(input_str):
            self._set_cooked_mode(True)
            if input_filter:
                return input_filter(input_str)
            return input_str

        attr = termios.tcgetattr(self.interpreter.child_fd)
        try:
            if send:
                self.interpreter.send(send)
            self.interpreter.interact(escape_character=escape_character,
                                      input_filter=ensure_cooked_mode,
                                      output_filter=output_filter)
        finally:
            termios.tcsetattr(self.interpreter.child_fd, termios.TCSANOW, attr)

    def _run(self, example, options):
        with self._change_terminal_geometry_ctx(options):
            return self._run_impl(example, options)

    def _run_impl(self, example, options):
        raise NotImplementedError()  # pragma: no cover

    def _drop_output(self):
        self.last_output = []

    def _shutdown_interpreter(self):
        self.interpreter.sendeof()
        self.interpreter.close()
        time.sleep(0.001)
        self.interpreter.terminate(force=True)

    def _exec_and_wait(self, source, options, timeout=None):
        if timeout == None:
            timeout = options['timeout']

        lines = source.split('\n')
        for line in lines[:-1]:
            self.interpreter.sendline(line)

            begin = time.time()
            self._expect_prompt(options, timeout)
            timeout -= max(time.time() - begin, 0)

        self.interpreter.sendline(lines[-1])
        self._expect_prompt(options, timeout, prompt_re=self.PS1_re)

        return self._get_output(options)

    def _create_terminal(self, options):
        rows, cols = options['geometry']

        self._screen = Screen(cols, rows)
        self._stream = Stream(self._screen)

    @contextlib.contextmanager
    def _change_terminal_geometry_ctx(self, options, force=False):
        ''' Context manager to change the terminal geometry temporally.

            Change to the new (rows, cols) and restore it back to the
            default (read from options).

            Nothing is changed if (rows, cols) is equal to the default
            geometry unless you set force=True.

            Override/extend the method _change_terminal_geometry to customize
            what's to be done upon each window change.
            '''
        rows, cols = options['geometry']
        need_change = (self._terminal_default_geometry != (rows, cols)
                       or force)
        if need_change:
            self._change_terminal_geometry(rows, cols, options)
            try:
                yield
            finally:
                rows, cols = self._terminal_default_geometry
                self._change_terminal_geometry(rows, cols, options)
        else:
            yield

    def _change_terminal_geometry(self, rows, cols, options):
        ''' Change the interpreter geometry or window size.

            By default just send a SIGWINCH signal but you may want to
            extend this with more things.
            '''
        self._screen.resize(rows, cols)
        self.interpreter.setwinsize(rows, cols)

    @staticmethod
    def _universal_new_lines(out):
        return '\n'.join(out.splitlines())

    def _emulate_ansi_terminal(self, chunks, join=True):
        for chunk in chunks:
            self._stream.feed(chunk)

        lines = self._screen.display
        self._screen.reset()
        if str == bytes:
            # Python 2.7 support only: it works on str/bytes only
            # XXX this is a limitation, if the output has a single non-ascii
            # character this will blow up without the 'ignore'
            lines = (str(line.rstrip().encode('ascii', 'ignore'))
                     for line in lines)
        else:
            lines = (line.rstrip() for line in lines)

        return '\n'.join(lines) if join else lines

    def _emulate_dumb_terminal(self, chunks):
        chunks = (self._universal_new_lines(chunk) for chunk in chunks)
        chunks = (chunk.expandtabs(8) for chunk in chunks)

        # remove trailing space from each line
        lines_group = (chunk.split('\n') for chunk in chunks)
        chunks = ('\n'.join(l.rstrip() for l in lines)
                  for lines in lines_group)

        return ''.join(chunks)

    def _emulate_as_is_terminal(self, chunks):
        return ''.join((self._universal_new_lines(chunk) for chunk in chunks))

    def _expect_prompt(self, options, timeout, prompt_re=None):
        ''' Wait for a <prompt_re> (any self.any_PS_re if <prompt_re> is None)
            and raise a timeout if we cannot find one.

            After the successful expect, collect the 'before' output into
            self.last_output
        '''
        if timeout == None:
            timeout = options['timeout']

        # timeout of 0 or negative means do not wait, just do a single read and return back
        timeout = max(timeout, 0)

        if not prompt_re:
            prompt_re = self.any_PS_re

        expect = [prompt_re, pexpect.TIMEOUT]
        PS_found, Timeout = range(len(expect))

        what = self.interpreter.expect(expect, timeout=timeout)
        self.last_output.append(self.interpreter.before)

        if what == Timeout:
            msg = "Prompt not found: the code is taking too long to finish or there is a syntax error.\nLast 1000 bytes read:\n%s"
            msg = msg % ''.join(self.last_output)[-1000:]
            out = self._get_output(options)
            raise TimeoutException(msg, out)

    def _get_output(self, options):
        if options['term'] == 'dumb':
            out = self._emulate_dumb_terminal(self.last_output)
        elif options['term'] == 'ansi':
            out = self._emulate_ansi_terminal(self.last_output)
        elif options['term'] == 'as-is':
            out = self._emulate_as_is_terminal(self.last_output)
        else:
            raise TypeError("Unknown terminal type '+term=%s'." %
                            options['term'])

        self._drop_output()
        return out

    def _set_cooked_mode(self, state):  # pragma: no cover
        # code borrowed from ptyprocess/ptyprocess.py, _setecho, and
        # adapted adding more flags to it based in stty(1)
        errmsg = '_set_cooked_mode() may not be called on this platform'

        fd = self.interpreter.child_fd

        try:
            attr = termios.tcgetattr(fd)
        except termios.error as err:
            if err.args[0] == errno.EINVAL:
                raise IOError(err.args[0], '%s: %s.' % (err.args[1], errmsg))
            raise

        input_flags = (
            'BRKINT',
            'IGNPAR',
            'ISTRIP',
            'ICRNL',
            'IXON',
        )

        output_flags = ('OPOST', )

        local_flags = (
            'ECHO',
            'ISIG',
            'ICANON',
        )

        if state:
            attr[0] |= reduce(
                operator.or_,
                [getattr(termios, flag_name) for flag_name in input_flags])
            attr[1] |= reduce(
                operator.or_,
                [getattr(termios, flag_name) for flag_name in output_flags])
            attr[3] |= reduce(
                operator.or_,
                [getattr(termios, flag_name) for flag_name in local_flags])
        else:
            attr[0] &= reduce(
                operator.and_,
                [~getattr(termios, flag_name) for flag_name in input_flags])
            attr[1] &= reduce(
                operator.and_,
                [~getattr(termios, flag_name) for flag_name in output_flags])
            attr[3] &= reduce(
                operator.and_,
                [~getattr(termios, flag_name) for flag_name in local_flags])

        try:
            termios.tcsetattr(fd, termios.TCSANOW, attr)
        except IOError as err:
            if err.args[0] == errno.EINVAL:
                raise IOError(err.args[0], '%s: %s.' % (err.args[1], errmsg))
            raise

    def _abort(self, example, options):
        self.interpreter.sendcontrol('c')

        try:
            # wait for the prompt, ignore any extra output
            self._expect_prompt(options,
                                timeout=options['x']['dfl_timeout'],
                                prompt_re=self.PS1_re)
            self._drop_output()
            return True
        except TimeoutException as ex:
            self._drop_output()
            return False
Esempio n. 14
0
 def __init__(self, *args, **kwargs):
     Stream.__init__(self, *args, **kwargs)
     self.listeners = defaultdict(lambda: [])
Esempio n. 15
0
def make_benchmark(path):
    with open(path, "rt") as handle:
        data = handle.read()

    stream = Stream(Screen(80, 24))
    return partial(stream.feed, data)
Esempio n. 16
0
class PexpectMixin(object):
    def __init__(self, PS1_re, any_PS_re):
        self.PS1_re = re.compile(PS1_re)
        self.any_PS_re = re.compile(any_PS_re)

        self.output_between_prompts = []
        self.last_output_may_be_incomplete = False

    def _spawn_interpreter(self,
                           cmd,
                           options,
                           wait_first_prompt=True,
                           first_prompt_timeout=None,
                           initial_prompt=None):
        if first_prompt_timeout is None:
            first_prompt_timeout = options['x']['dfl_timeout']

        rows, cols = options['geometry']
        self._terminal_default_geometry = (rows, cols)

        env = os.environ.copy()
        env.update({'LINES': str(rows), 'COLUMNS': str(cols)})

        self._drop_output()  # there shouldn't be any output yet but...
        self.interpreter = pexpect.spawn(cmd,
                                         echo=False,
                                         encoding=self.encoding,
                                         dimensions=(rows, cols),
                                         env=env)
        self.interpreter.delaybeforesend = options['x']['delaybeforesend']
        self.interpreter.delayafterread = None

        self._create_terminal(options)

        if wait_first_prompt:
            prompt_re = self.PS1_re if initial_prompt is None else initial_prompt
            self._expect_prompt(options,
                                countdown=Countdown(first_prompt_timeout),
                                prompt_re=prompt_re)
            self._drop_output()  # discard banner and things like that

    def interact(self,
                 send='\n',
                 escape_character=chr(29),
                 input_filter=None,
                 output_filter=None):  # pragma: no cover
        def ensure_cooked_mode(input_str):
            self._set_cooked_mode(True)
            if input_filter:
                return input_filter(input_str)
            return input_str

        attr = termios.tcgetattr(self.interpreter.child_fd)
        try:
            if send:
                self.interpreter.send(send)
            self.interpreter.interact(escape_character=escape_character,
                                      input_filter=ensure_cooked_mode,
                                      output_filter=output_filter)
        finally:
            termios.tcsetattr(self.interpreter.child_fd, termios.TCSANOW, attr)

    def _run(self, example, options):
        with self._change_terminal_geometry_ctx(options):
            return self._run_impl(example, options)

    def _run_impl(self, example, options):
        raise NotImplementedError()  # pragma: no cover

    def _drop_output(self):
        self.output_between_prompts = []
        self.last_output_may_be_incomplete = False

    def _shutdown_interpreter(self):
        self.interpreter.sendeof()
        self.interpreter.close()
        time.sleep(0.001)
        self.interpreter.terminate(force=True)

    def _exec_and_wait(self, source, options, *, from_example=None, **kargs):
        if from_example is None:
            input_list = kargs.get('input_list', [])
        else:
            input_list = kargs.get('input_list', from_example.input_list)

        timeout = kargs.get('timeout', options['timeout'])

        # get a copy: _expect_prompt_or_type will modify this in-place
        input_list = list(input_list)

        countdown = Countdown(timeout)
        lines = source.split('\n')
        for line in lines[:-1]:
            self.interpreter.sendline(line)
            self._expect_prompt_or_type(options,
                                        countdown,
                                        input_list=input_list)

        self.interpreter.sendline(lines[-1])
        self._expect_prompt_or_type(options,
                                    countdown,
                                    prompt_re=self.PS1_re,
                                    input_list=input_list)

        if input_list:
            s = short_string(input_list[0][-1])
            if len(input_list) > 1:
                msg = "Some inputs were not typed: [%s] and %i more."
                args = (s, len(input_list) - 1)
            else:
                msg = "The last input was not typed: [%s]."
                args = (s, )

            clog().warn(msg, *args)

        return self._get_output(options)

    def _create_terminal(self, options):
        rows, cols = options['geometry']

        self._screen = Screen(cols, rows)
        self._stream = Stream(self._screen)

    @contextlib.contextmanager
    def _change_terminal_geometry_ctx(self, options, force=False):
        ''' Context manager to change the terminal geometry temporally.

            Change to the new (rows, cols) and restore it back to the
            default (read from options).

            Nothing is changed if (rows, cols) is equal to the default
            geometry unless you set force=True.

            Override/extend the method _change_terminal_geometry to customize
            what's to be done upon each window change.
            '''
        rows, cols = options['geometry']
        need_change = (self._terminal_default_geometry != (rows, cols)
                       or force)
        if need_change:
            self._change_terminal_geometry(rows, cols, options)
            try:
                yield
            finally:
                rows, cols = self._terminal_default_geometry
                self._change_terminal_geometry(rows, cols, options)
        else:
            yield

    def _change_terminal_geometry(self, rows, cols, options):
        ''' Change the interpreter geometry or window size.

            By default just send a SIGWINCH signal but you may want to
            extend this with more things.
            '''
        self._screen.resize(rows, cols)
        self.interpreter.setwinsize(rows, cols)

    UNIV_NL = re.compile('\r\n|\r')

    @staticmethod
    def _universal_new_lines(out):
        return re.sub(PexpectMixin.UNIV_NL, '\n', out)

    def _emulate_ansi_terminal(self, chunks, join=True):
        for chunk in chunks:
            self._stream.feed(chunk)

        lines = self._screen.display
        self._screen.reset()
        lines = (line.rstrip() for line in lines)

        return '\n'.join(lines) if join else lines

    def _emulate_dumb_terminal(self, chunks):
        chunks = (self._universal_new_lines(chunk) for chunk in chunks)
        chunks = (chunk.expandtabs(8) for chunk in chunks)

        # remove trailing space from each line
        lines_group = (chunk.split('\n') for chunk in chunks)
        chunks = ('\n'.join(l.rstrip() for l in lines)
                  for lines in lines_group)

        return ''.join(chunks)

    def _emulate_as_is_terminal(self, chunks):
        return ''.join((self._universal_new_lines(chunk) for chunk in chunks))

    def _expect_prompt_or_type(self,
                               options,
                               countdown,
                               prompt_re=None,
                               input_list=[]):
        assert isinstance(input_list, list)

        i = 0
        prompt_found = False
        while i < len(input_list):
            prefix, prefix_regex, input = input_list[i]

            # the regex may contain "escaped" newlines (\n) while
            # the runner may output any form of end line like
            # \n, \r and \r\n. In order to match any of those
            # we replace the litera "escaped" \n with a regex
            prefix_regex = prefix_regex.replace('\\\n', r'(?:\r\n|\n|\r)')
            try:
                prompt_found = self._expect_prompt(options,
                                                   countdown,
                                                   prompt_re,
                                                   earlier_re=prefix_regex)
            except TimeoutException as ex:
                raise InputPrefixNotFound(prefix, input, ex)

            if prompt_found:
                break

            # Add the prefix (output comming from the interpreter) and
            # the [ <input> ] that we typed in. This is a sort of
            # echo-emulation (TODO: some interpreters have echo activated,
            # should this be necessary?)
            chunk = "{}[{}]\n".format(self.interpreter.match.group(), input)
            self.output_between_prompts[-1] += chunk
            assert self.last_output_may_be_incomplete

            self.interpreter.sendline(input)
            i += 1

        # remove in-place the inputs that were typed
        del input_list[:i]

        if not prompt_found:
            self._expect_prompt(options, countdown, prompt_re)

    def _expect_prompt(self,
                       options,
                       countdown,
                       prompt_re=None,
                       earlier_re=None):
        ''' Wait for a <prompt_re> (any self.any_PS_re if <prompt_re> is None)
            and raise a timeout if we cannot find one.

            If <earlier_re> is given, wait it along with the prompt: if it
            is found before the prompt, _expect_prompt will return False,
            otherwise will return True (or raise an exception if a timeout
            happens)

            During the waiting, collect the 'before' output into
            self.output_between_prompts
        '''
        if countdown == None:
            countdown = Countdown(options['timeout'])

        if not isinstance(countdown, Countdown):
            raise TypeError("Invalid object for countdown: %s" %
                            type(countdown))

        # timeout of 0 or negative means do not wait, just do a single read and return back
        timeout = countdown.left()
        assert timeout >= 0

        if not prompt_re:
            prompt_re = self.any_PS_re

        expect = [prompt_re, pexpect.TIMEOUT, earlier_re]
        PS_found, Timeout, Earlier = range(len(expect))

        # remove it if it was actually None (adding it before and
        # removing it now is weird but it makes the code easier and shorter)
        if earlier_re is None:
            del expect[-1]

        countdown.start()
        what = self.interpreter.expect(expect, timeout=timeout)
        countdown.stop()

        output = self.interpreter.before
        if self.last_output_may_be_incomplete:
            self.output_between_prompts[-1] += output
        else:
            self.output_between_prompts.append(output)

        if what == Timeout:
            msg = "Prompt not found: the code is taking too long to finish or there is a syntax error.\nLast 1000 bytes read:\n%s"
            msg = msg % ''.join(self.output_between_prompts)[-1000:]
            out = self._get_output(options)
            raise TimeoutException(msg, out)

        elif what == Earlier:
            self.last_output_may_be_incomplete = True
            return False

        self.last_output_may_be_incomplete = False
        return True

    def _get_output(self, options):
        if options['term'] == 'dumb':
            out = self._emulate_dumb_terminal(self.output_between_prompts)
        elif options['term'] == 'ansi':
            out = self._emulate_ansi_terminal(self.output_between_prompts)
        elif options['term'] == 'as-is':
            out = self._emulate_as_is_terminal(self.output_between_prompts)
        else:
            raise TypeError("Unknown terminal type '+term=%s'." %
                            options['term'])

        self._drop_output()
        return out

    def _get_output_echo_filtered(self, options):
        # if the interpreter doesn't disable the TTY's echo,
        # everything we type in it will be reflected in the output.
        # so this breaks badly self._get_output
        # experimental feature, use this instead of _get_output

        # self.output_between_prompts is a list of strings found by pexpect
        # after returning of each pexpect.expect
        # in other words if we prefix each line with the prompt
        # should get the original output from the process
        cookie = '[byexamplecookie]$'
        lines = (cookie + ' ' + line for line in self.output_between_prompts)
        self._drop_output()

        # now, feed those lines to our ANSI Terminal emulator
        lines = self._emulate_ansi_terminal(lines, join=False)

        # get each line in the Terminal's display and ignore each one that
        # starts with our cookie: those are the "echo" lines that
        # *we* sent to the interpreter and they are not part of *its* output.
        lines = (line for line in lines if not line.startswith(cookie))

        return '\n'.join(lines)

    def _set_cooked_mode(self, state):  # pragma: no cover
        # code borrowed from ptyprocess/ptyprocess.py, _setecho, and
        # adapted adding more flags to it based in stty(1)
        errmsg = '_set_cooked_mode() may not be called on this platform'

        fd = self.interpreter.child_fd

        try:
            attr = termios.tcgetattr(fd)
        except termios.error as err:
            if err.args[0] == errno.EINVAL:
                raise IOError(err.args[0], '%s: %s.' % (err.args[1], errmsg))
            raise

        input_flags = (
            'BRKINT',
            'IGNPAR',
            'ISTRIP',
            'ICRNL',
            'IXON',
        )

        output_flags = ('OPOST', )

        local_flags = (
            'ECHO',
            'ISIG',
            'ICANON',
        )

        if state:
            attr[0] |= reduce(
                operator.or_,
                [getattr(termios, flag_name) for flag_name in input_flags])
            attr[1] |= reduce(
                operator.or_,
                [getattr(termios, flag_name) for flag_name in output_flags])
            attr[3] |= reduce(
                operator.or_,
                [getattr(termios, flag_name) for flag_name in local_flags])
        else:
            attr[0] &= reduce(
                operator.and_,
                [~getattr(termios, flag_name) for flag_name in input_flags])
            attr[1] &= reduce(
                operator.and_,
                [~getattr(termios, flag_name) for flag_name in output_flags])
            attr[3] &= reduce(
                operator.and_,
                [~getattr(termios, flag_name) for flag_name in local_flags])

        try:
            termios.tcsetattr(fd, termios.TCSANOW, attr)
        except IOError as err:
            if err.args[0] == errno.EINVAL:
                raise IOError(err.args[0], '%s: %s.' % (err.args[1], errmsg))
            raise

    def _abort(self, example, options):
        self.interpreter.sendcontrol('c')
        return self._recover_prompt_sync(example, options)

    def _recover_prompt_sync(self, example, options, cnt=5):
        ''' Expect for at least one prompt, return False if we
            didn't find one in a reasonable time (dfl_timeout).

            If we found one, keep reading up to find less than
            <cnt> additional prompts.

            If we found <cnt>, we fail too and return False.

            In other case, return sucess True.

            The idea is that if we lost the synchronization with
            the interpreter we may recover it reading at least
            one prompt (the interpreter is still alive) and
            less than <cnt> prompts (so we are at the 'end').

            This algorithm is not bug-free, just a best-effort one.
            '''
        try:
            # wait for the prompt, ignore any extra output
            self._expect_prompt(options,
                                countdown=Countdown(
                                    options['x']['dfl_timeout']),
                                prompt_re=self.PS1_re)
            self._drop_output()
            good = True
        except TimeoutException as ex:
            self._drop_output()
            good = False

        if good:
            try:
                cnt = 0
                while cnt < 5:
                    # "consume" spurious prompts until we know that we are
                    # in 'sync' with the interpreter (no more prompts
                    # are without be read)
                    self._expect_prompt(options,
                                        countdown=Countdown(
                                            options['x']['dfl_timeout']),
                                        prompt_re=self.PS1_re)
                    self._drop_output()
                    cnt += 1

                good = False  # we cannot ensure that we are in sync
            except TimeoutException as ex:
                self._drop_output()

        return good
Esempio n. 17
0
def test_cursor_is_hidden():
    screen = HistoryScreen(5, 5, history=50)
    stream = Stream()
    stream.attach(screen)
    stream.escape["N"] = "next_page"
    stream.escape["P"] = "prev_page"

    for idx in range(len(screen) * 5):
        for ch in str(idx) + os.linesep:
            stream.feed(ch)

    assert not screen.cursor.hidden

    stream.feed(ctrl.ESC + "P")
    assert screen.cursor.hidden
    stream.feed(ctrl.ESC + "P")
    assert screen.cursor.hidden
    stream.feed(ctrl.ESC + "N")
    assert screen.cursor.hidden
    stream.feed(ctrl.ESC + "N")
    assert not screen.cursor.hidden
Esempio n. 18
0
def test_cursor_is_hidden():
    screen = HistoryScreen(5, 5, history=50)
    stream = Stream()
    stream.attach(screen)
    stream.escape["N"] = "next_page"
    stream.escape["P"] = "prev_page"

    for idx in range(len(screen.buffer) * 5):
        for ch in str(idx) + os.linesep:
            stream.feed(ch)

    assert not screen.cursor.hidden

    stream.feed(ctrl.ESC + "P")
    assert screen.cursor.hidden
    stream.feed(ctrl.ESC + "P")
    assert screen.cursor.hidden
    stream.feed(ctrl.ESC + "N")
    assert screen.cursor.hidden
    stream.feed(ctrl.ESC + "N")
    assert not screen.cursor.hidden
Esempio n. 19
0
def test_ensure_width():
    screen = HistoryScreen(5, 5, history=50)
    screen.set_mode(mo.LNM)
    stream = Stream()
    stream.attach(screen)
    stream.escape["N"] = "next_page"
    stream.escape["P"] = "prev_page"

    for idx in range(len(screen) * 5):
        for ch in str(idx) + os.linesep:
            stream.feed(ch)

    assert screen.display == ["21   ", "22   ", "23   ", "24   ", "     "]

    # a) shrinking the screen, expecting the lines displayed to
    #    be truncated.
    screen.resize(5, 2)
    stream.feed(ctrl.ESC + "P")

    assert all(len(l) is not 2 for l in screen.history.top)
    assert all(len(l) is 2 for l in screen.history.bottom)
    assert screen.display == ["18", "19", "20", "21", "22"]

    # b) expading the screen, expecting the lines displayed to
    #    be filled with whitespace characters.
    screen.resize(5, 10)
    stream.feed(ctrl.ESC + "N")

    assert all(len(l) is 10 for l in list(screen.history.top)[-3:])
    assert all(len(l) is not 10 for l in screen.history.bottom)
    assert screen.display == [
        "21        ", "22        ", "23        ", "24        ", "          "
    ]
Esempio n. 20
0
def test_cursor_is_hidden():
    screen = HistoryScreen(5, 5, history=50)
    stream = Stream()
    stream.attach(screen)
    stream.escape["N"] = "next_page"
    stream.escape["P"] = "prev_page"

    for idx in range(len(screen) * 5):
        map(stream.feed, unicode(idx) + "\n")

    assert not screen.cursor.hidden

    stream.feed(ctrl.ESC + "P")
    assert screen.cursor.hidden
    stream.feed(ctrl.ESC + "P")
    assert screen.cursor.hidden
    stream.feed(ctrl.ESC + "N")
    assert screen.cursor.hidden
    stream.feed(ctrl.ESC + "N")
    assert not screen.cursor.hidden
Esempio n. 21
0
def test_draw():
    screen = HistoryScreen(5, 5, history=50)
    screen.set_mode(mo.LNM)
    stream = Stream()
    stream.attach(screen)
    stream.escape["N"] = "next_page"
    stream.escape["P"] = "prev_page"

    for idx in range(len(screen.buffer) * 5):
        for ch in str(idx) + os.linesep:
            stream.feed(ch)

    assert screen.display == ["21   ", "22   ", "23   ", "24   ", "     "]

    # a) doing a pageup and then a draw -- expecting the screen
    #    to scroll to the bottom before drawing anything.
    stream.feed(ctrl.ESC + "P")
    stream.feed(ctrl.ESC + "P")
    stream.feed(ctrl.ESC + "N")
    stream.feed("x")

    assert screen.display == ["21   ", "22   ", "23   ", "24   ", "x    "]
Esempio n. 22
0
def test_cursor_is_hidden(monkeypatch):
    screen = HistoryScreen(5, 5, history=50)
    escape = dict(Stream.escape)
    escape.update({b"N": "next_page", b"P": "prev_page"})
    monkeypatch.setattr(Stream, "escape", escape)

    stream = Stream(screen)
    for idx in range(len(screen.buffer) * 5):
        stream.feed((str(idx) + os.linesep).encode("utf-8"))

    assert not screen.cursor.hidden

    stream.feed(ctrl.ESC + b"P")
    assert screen.cursor.hidden
    stream.feed(ctrl.ESC + b"P")
    assert screen.cursor.hidden
    stream.feed(ctrl.ESC + b"N")
    assert screen.cursor.hidden
    stream.feed(ctrl.ESC + b"N")
    assert not screen.cursor.hidden
Esempio n. 23
0
 def __init__(self, *args, **kwargs):
     Stream.__init__(self, *args, **kwargs)
     self.listeners = defaultdict(lambda: [])
Esempio n. 24
0
def project_output(stream):
    screen = Screen(80, 24)
    terminal_stream = Stream(screen)
    terminal_stream.feed(stream.getvalue())
    return screen.display