def assert_bash_exec( bash: pexpect.spawn, cmd: str, want_output: bool = False) -> str: # Send command bash.sendline(cmd) bash.expect_exact(cmd) # Find prompt, output is before it bash.expect_exact("\r\n" + PS1) output = bash.before # Retrieve exit status echo = "echo $?" bash.sendline(echo) got = bash.expect([ r"^%s\r\n(\d+)\r\n%s" % (re.escape(echo), re.escape(PS1)), PS1, pexpect.EOF, pexpect.TIMEOUT, ]) status = bash.match.group(1) if got == 0 else "unknown" assert status == "0", \ 'Error running "%s": exit status=%s, output="%s"' % \ (cmd, status, output) if output: assert want_output, \ 'Unexpected output from "%s": exit status=%s, output="%s"' % \ (cmd, status, output) else: assert not want_output, \ 'Expected output from "%s": exit status=%s, output="%s"' % \ (cmd, status, output) return output
def is_bash_type(bash: pexpect.spawn, cmd: str) -> bool: typecmd = "type %s &>/dev/null && echo -n 0 || echo -n 1" % cmd bash.sendline(typecmd) bash.expect_exact(typecmd + "\r\n") result = bash.expect_exact(["0", "1"]) == 0 bash.expect_exact(PS1) return result
def is_bash_type(bash: pexpect.spawn, cmd: str) -> bool: typecmd = "type %s &>/dev/null && echo -n 0 || echo -n 1" % cmd bash.sendline(typecmd) bash.expect_exact(typecmd + "\r\n") result = bash.expect_exact(["0", "1"]) == 0 bash.expect_exact(PS1) return result
def assert_complete(bash: pexpect.spawn, cmd: str, **kwargs) -> CompletionResult: skipif = kwargs.get("skipif") if skipif: try: assert_bash_exec(bash, skipif, want_output=None) except AssertionError: pass else: pytest.skip(skipif) xfail = kwargs.get("xfail") if xfail: try: assert_bash_exec(bash, xfail, want_output=None) except AssertionError: pass else: pytest.xfail(xfail) with bash_env_saved(bash, sendintr=True) as bash_env: cwd = kwargs.get("cwd") if cwd: bash_env.chdir(str(cwd)) for k, v in kwargs.get("env", {}).items(): bash_env.write_env(k, v, quote=False) for k, v in kwargs.get("shopt", {}).items(): bash_env.shopt(k, v) bash.send(cmd + "\t") # Sleep a bit if requested, to avoid `.*` matching too early time.sleep(kwargs.get("sleep_after_tab", 0)) bash.expect_exact(kwargs.get("rendered_cmd", cmd)) bash.send(MAGIC_MARK) got = bash.expect([ # 0: multiple lines, result in .before r"\r\n" + re.escape(PS1 + cmd) + ".*" + re.escape(MAGIC_MARK), # 1: no completion r"^" + re.escape(MAGIC_MARK), # 2: on same line, result in .match r"^([^\r]+)%s$" % re.escape(MAGIC_MARK), pexpect.EOF, pexpect.TIMEOUT, ]) if got == 0: output = bash.before if output.endswith(MAGIC_MARK): output = bash.before[:-len(MAGIC_MARK)] return CompletionResult(output) elif got == 2: output = bash.match.group(1) return CompletionResult(output) else: # TODO: warn about EOF/TIMEOUT? return CompletionResult()
def assert_bash_exec( bash: pexpect.spawn, cmd: str, want_output: Optional[bool] = False, want_newline=True, ) -> str: """ :param want_output: if None, don't care if got output or not """ # Send command bash.sendline(cmd) bash.expect_exact(cmd) # Find prompt, output is before it bash.expect_exact("%s%s" % ("\r\n" if want_newline else "", PS1)) output = bash.before # Retrieve exit status echo = "echo $?" bash.sendline(echo) got = bash.expect( [ r"^%s\r\n(\d+)\r\n%s" % (re.escape(echo), re.escape(PS1)), PS1, pexpect.EOF, pexpect.TIMEOUT, ] ) status = bash.match.group(1) if got == 0 else "unknown" assert status == "0", 'Error running "%s": exit status=%s, output="%s"' % ( cmd, status, output, ) if want_output is not None: if output: assert ( want_output ), 'Unexpected output from "%s": exit status=%s, output="%s"' % ( cmd, status, output, ) else: assert ( not want_output ), 'Expected output from "%s": exit status=%s, output="%s"' % ( cmd, status, output, ) return output
def assert_complete_at_point( bash: pexpect.spawn, cmd: str, trail: str ) -> CompletionResult: # TODO: merge to assert_complete fullcmd = "%s%s%s" % ( cmd, trail, "\002" * len(trail), ) # \002 = ^B = cursor left bash.send(fullcmd + "\t") bash.send(MAGIC_MARK) bash.expect_exact(fullcmd.replace("\002", "\b")) got = bash.expect_exact( [ # 0: multiple lines, result in .before PS1 + fullcmd.replace("\002", "\b"), # 1: no completion MAGIC_MARK, pexpect.EOF, pexpect.TIMEOUT, ] ) if got == 0: output = bash.before result = CompletionResult(output) # At this point, something weird happens. For most test setups, as # expected (pun intended!), MAGIC_MARK follows as is. But for some # others (e.g. CentOS 6, Ubuntu 14 test containers), we get MAGIC_MARK # one character a time, followed each time by trail and the corresponding # number of \b's. Don't know why, but accept it until/if someone finds out. # Or just be fine with it indefinitely, the visible and practical end # result on a terminal is the same anyway. repeat = "(%s%s)?" % (re.escape(trail), "\b" * len(trail)) fullexpected = "".join( "%s%s" % (re.escape(x), repeat) for x in MAGIC_MARK ) bash.expect(fullexpected) else: # TODO: warn about EOF/TIMEOUT? result = CompletionResult() return result
def assert_bash_exec(bash: pexpect.spawn, cmd: str, want_output: bool = False) -> str: # Send command bash.sendline(cmd) bash.expect_exact(cmd) # Find prompt, output is before it bash.expect_exact("\r\n" + PS1) output = bash.before # Retrieve exit status echo = "echo $?" bash.sendline(echo) got = bash.expect([ r"^%s\r\n(\d+)\r\n%s" % (re.escape(echo), re.escape(PS1)), PS1, pexpect.EOF, pexpect.TIMEOUT, ]) status = bash.match.group(1) if got == 0 else "unknown" assert status == "0", 'Error running "%s": exit status=%s, output="%s"' % ( cmd, status, output, ) if output: assert want_output, ( 'Unexpected output from "%s": exit status=%s, output="%s"' % (cmd, status, output)) else: assert not want_output, ( 'Expected output from "%s": exit status=%s, output="%s"' % (cmd, status, output)) return output
def assert_complete(bash: pexpect.spawn, cmd: str, **kwargs) -> CompletionResult: skipif = kwargs.get("skipif") if skipif: try: assert_bash_exec(bash, skipif) except AssertionError: pass else: pytest.skip(skipif) xfail = kwargs.get("xfail") if xfail: try: assert_bash_exec(bash, xfail) except AssertionError: pass else: pytest.xfail(xfail) cwd = kwargs.get("cwd") if cwd: assert_bash_exec(bash, "cd '%s'" % cwd) env_prefix = "_BASHCOMP_TEST_" env = kwargs.get("env", {}) if env: # Back up environment and apply new one assert_bash_exec( bash, " ".join('%s%s="$%s"' % (env_prefix, k, k) for k in env.keys()), ) assert_bash_exec( bash, "export %s" % " ".join("%s=%s" % (k, v) for k, v in env.items()), ) bash.send(cmd + "\t") bash.expect_exact(cmd) bash.send(MAGIC_MARK) got = bash.expect([ # 0: multiple lines, result in .before r"\r\n" + re.escape(PS1 + cmd) + ".*" + MAGIC_MARK, # 1: no completion r"^" + MAGIC_MARK, # 2: on same line, result in .match r"^([^\r]+)%s$" % MAGIC_MARK, pexpect.EOF, pexpect.TIMEOUT, ]) if got == 0: output = bash.before if output.endswith(MAGIC_MARK): output = bash.before[:-len(MAGIC_MARK)] result = CompletionResult(output) elif got == 2: output = bash.match.group(1) result = CompletionResult(output, [shlex.split(cmd + output)[-1]]) else: # TODO: warn about EOF/TIMEOUT? result = CompletionResult("", []) bash.sendintr() bash.expect_exact(PS1) if env: # Restore environment, and clean up backup # TODO: Test with declare -p if a var was set, backup only if yes, and # similarly restore only backed up vars. Should remove some need # for ignore_env. assert_bash_exec( bash, "export %s" % " ".join('%s="$%s%s"' % (k, env_prefix, k) for k in env.keys()), ) assert_bash_exec( bash, "unset -v %s" % " ".join("%s%s" % (env_prefix, k) for k in env.keys()), ) if cwd: assert_bash_exec(bash, "cd - >/dev/null") return result
def slow_sendline(exp: spawn, string: str): """Send one character at a time and wait for echo. The expect session deadlocks if we don't do this.""" for i in range(len(string)): exp.send(string[i]) exp.expect_exact(string[i]) exp.send('\n')
def wait_for_pexpect_process(process: pexpect.spawn) -> int: process.expect_exact(pexpect.EOF) return process.wait()
def assert_complete( bash: pexpect.spawn, cmd: str, **kwargs) -> CompletionResult: skipif = kwargs.get("skipif") if skipif: try: assert_bash_exec(bash, skipif) except AssertionError: pass else: pytest.skip(skipif) return CompletionResult("", []) cwd = kwargs.get("cwd") if cwd: assert_bash_exec(bash, "cd '%s'" % cwd) env_prefix = "_BASHCOMP_TEST_" env = kwargs.get("env", {}) if env: # Back up environment and apply new one assert_bash_exec(bash, " ".join( '%s%s="$%s"' % (env_prefix, k, k) for k in env.keys() )) assert_bash_exec(bash, "export %s" % " ".join( "%s=%s" % (k, v) for k, v in env.items() )) bash.send(cmd + "\t") bash.expect_exact(cmd) bash.send(MAGIC_MARK) got = bash.expect([ r"\r\n" + re.escape(PS1 + cmd), # 0: multiple lines, result in .before r"^" + MAGIC_MARK, # 1: no completion r"^[^\r]+", # 2: on same line, result in .after pexpect.EOF, pexpect.TIMEOUT, ]) if got == 0: line = bash.before if line.endswith(MAGIC_MARK): line = bash.before[:-len(MAGIC_MARK)] result = CompletionResult( line, sorted(x for x in re.split(r" {2,}|\r\n", line) if x), ) elif got == 2: line = bash.after if line.endswith(MAGIC_MARK): line = bash.after[:-len(MAGIC_MARK)] result = CompletionResult( line, [shlex.split(cmd + line)[-1]], ) else: # TODO: warn about EOF/TIMEOUT? result = CompletionResult("", []) bash.sendintr() bash.expect_exact(PS1) if env: # Restore environment, and clean up backup # TODO: Test with declare -p if a var was set, backup only if yes, and # similarly restore only backed up vars. Should remove some need # for ignore_env. assert_bash_exec(bash, "export %s" % " ".join( '%s="$%s%s"' % (k, env_prefix, k) for k in env.keys() )) assert_bash_exec(bash, "unset -v %s" % " ".join( "%s%s" % (env_prefix, k) for k in env.keys() )) if cwd: assert_bash_exec(bash, "cd - >/dev/null") return result