mirror of
https://github.com/NixOS/nixpkgs.git
synced 2025-06-12 20:55:31 +03:00
nixos/test-runner: Fix execute() flakiness
Instead of using the magic string, we now just base64-encode everything and check for a newline.
This commit is contained in:
parent
5f917bc275
commit
1640359f33
5 changed files with 43 additions and 16 deletions
|
@ -159,6 +159,11 @@ The following methods are available on machine objects:
|
||||||
`execute`
|
`execute`
|
||||||
|
|
||||||
: Execute a shell command, returning a list `(status, stdout)`.
|
: Execute a shell command, returning a list `(status, stdout)`.
|
||||||
|
Takes an optional parameter `check_return` that defaults to `True`.
|
||||||
|
Setting this parameter to `False` will not check for the return code
|
||||||
|
and return -1 instead. This can be used for commands that shut down
|
||||||
|
the VM and would therefore break the pipe that would be used for
|
||||||
|
retrieving the return code.
|
||||||
|
|
||||||
`succeed`
|
`succeed`
|
||||||
|
|
||||||
|
|
|
@ -266,7 +266,13 @@ start_all()
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
Execute a shell command, returning a list
|
Execute a shell command, returning a list
|
||||||
<literal>(status, stdout)</literal>.
|
<literal>(status, stdout)</literal>. Takes an optional
|
||||||
|
parameter <literal>check_return</literal> that defaults to
|
||||||
|
<literal>True</literal>. Setting this parameter to
|
||||||
|
<literal>False</literal> will not check for the return code
|
||||||
|
and return -1 instead. This can be used for commands that shut
|
||||||
|
down the VM and would therefore break the pipe that would be
|
||||||
|
used for retrieving the return code.
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
|
|
|
@ -581,24 +581,40 @@ class Machine:
|
||||||
+ "'{}' but it is in state ‘{}’".format(require_state, state)
|
+ "'{}' but it is in state ‘{}’".format(require_state, state)
|
||||||
)
|
)
|
||||||
|
|
||||||
def execute(self, command: str) -> Tuple[int, str]:
|
def _next_newline_closed_block_from_shell(self) -> str:
|
||||||
|
assert self.shell
|
||||||
|
output_buffer = []
|
||||||
|
while True:
|
||||||
|
# This receives up to 4096 bytes from the socket
|
||||||
|
chunk = self.shell.recv(4096)
|
||||||
|
if not chunk:
|
||||||
|
# Probably a broken pipe, return the output we have
|
||||||
|
break
|
||||||
|
|
||||||
|
decoded = chunk.decode()
|
||||||
|
output_buffer += [decoded]
|
||||||
|
if decoded[-1] == "\n":
|
||||||
|
break
|
||||||
|
return "".join(output_buffer)
|
||||||
|
|
||||||
|
def execute(self, command: str, check_return: bool = True) -> Tuple[int, str]:
|
||||||
self.connect()
|
self.connect()
|
||||||
|
|
||||||
out_command = "( set -euo pipefail; {} ); echo '|!=EOF' $?\n".format(command)
|
out_command = f"( set -euo pipefail; {command} ) | (base64 --wrap 0; echo)\n"
|
||||||
assert self.shell
|
assert self.shell
|
||||||
self.shell.send(out_command.encode())
|
self.shell.send(out_command.encode())
|
||||||
|
|
||||||
output = ""
|
# Get the output
|
||||||
status_code_pattern = re.compile(r"(.*)\|\!=EOF\s+(\d+)")
|
output = base64.b64decode(self._next_newline_closed_block_from_shell())
|
||||||
|
|
||||||
while True:
|
if not check_return:
|
||||||
chunk = self.shell.recv(4096).decode(errors="ignore")
|
return (-1, output.decode())
|
||||||
match = status_code_pattern.match(chunk)
|
|
||||||
if match:
|
# Get the return code
|
||||||
output += match[1]
|
self.shell.send("echo ${PIPESTATUS[0]}\n".encode())
|
||||||
status_code = int(match[2])
|
rc = int(self._next_newline_closed_block_from_shell().strip())
|
||||||
return (status_code, output)
|
|
||||||
output += chunk
|
return (rc, output.decode())
|
||||||
|
|
||||||
def shell_interact(self) -> None:
|
def shell_interact(self) -> None:
|
||||||
"""Allows you to interact with the guest shell
|
"""Allows you to interact with the guest shell
|
||||||
|
|
|
@ -95,7 +95,7 @@ in makeTest {
|
||||||
"mkswap /dev/vda1 -L swap",
|
"mkswap /dev/vda1 -L swap",
|
||||||
# Install onto /mnt
|
# Install onto /mnt
|
||||||
"nix-store --load-db < ${pkgs.closureInfo {rootPaths = [installedSystem];}}/registration",
|
"nix-store --load-db < ${pkgs.closureInfo {rootPaths = [installedSystem];}}/registration",
|
||||||
"nixos-install --root /mnt --system ${installedSystem} --no-root-passwd",
|
"nixos-install --root /mnt --system ${installedSystem} --no-root-passwd --no-channel-copy >&2",
|
||||||
)
|
)
|
||||||
machine.shutdown()
|
machine.shutdown()
|
||||||
|
|
||||||
|
@ -110,7 +110,7 @@ in makeTest {
|
||||||
)
|
)
|
||||||
|
|
||||||
# Hibernate machine
|
# Hibernate machine
|
||||||
hibernate.succeed("systemctl hibernate &")
|
hibernate.execute("systemctl hibernate &", check_return=False)
|
||||||
hibernate.wait_for_shutdown()
|
hibernate.wait_for_shutdown()
|
||||||
|
|
||||||
# Restore machine from hibernation, validate our ramfs file is there.
|
# Restore machine from hibernation, validate our ramfs file is there.
|
||||||
|
|
|
@ -18,7 +18,7 @@ import ./make-test-python.nix ({ pkgs, lib, ...} : {
|
||||||
testScript =
|
testScript =
|
||||||
''
|
''
|
||||||
machine.wait_for_unit("multi-user.target")
|
machine.wait_for_unit("multi-user.target")
|
||||||
machine.execute("systemctl kexec &")
|
machine.execute("systemctl kexec &", check_return=False)
|
||||||
machine.connected = False
|
machine.connected = False
|
||||||
machine.wait_for_unit("multi-user.target")
|
machine.wait_for_unit("multi-user.target")
|
||||||
'';
|
'';
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue