Merge pull request #165752 from helsinki-systems/feat/test-runner-send-serial

nixos/test-runner: Allow writing to qemu stdin
This commit is contained in:
Jacek Galowicz 2022-03-25 13:32:28 +01:00 committed by GitHub
commit 5a650acbb1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 70 additions and 1 deletions

View file

@ -158,6 +158,12 @@ The following methods are available on machine objects:
e.g., `send_chars("foobar\n")` will type the string `foobar` e.g., `send_chars("foobar\n")` will type the string `foobar`
followed by the Enter key. followed by the Enter key.
`send_console`
: Send keys to the kernel console. This allows interaction with the systemd
emergency mode, for example. Takes a string that is sent, e.g.,
`send_console("\n\nsystemctl default\n")`.
`execute` `execute`
: Execute a shell command, returning a list `(status, stdout)`. : Execute a shell command, returning a list `(status, stdout)`.
@ -272,6 +278,13 @@ The following methods are available on machine objects:
Killing the interactive session with `Ctrl-d` or `Ctrl-c` also ends Killing the interactive session with `Ctrl-d` or `Ctrl-c` also ends
the guest session. the guest session.
`console_interact`
: Allows you to directly interact with QEMU's stdin. This should
only be used during test development, not in production tests.
Output from QEMU is only read line-wise. `Ctrl-c` kills QEMU and
`Ctrl-d` closes console and returns to the test runner.
To test user units declared by `systemd.user.services` the optional To test user units declared by `systemd.user.services` the optional
`user` argument can be used: `user` argument can be used:

View file

@ -261,6 +261,19 @@ start_all()
</para> </para>
</listitem> </listitem>
</varlistentry> </varlistentry>
<varlistentry>
<term>
<literal>send_console</literal>
</term>
<listitem>
<para>
Send keys to the kernel console. This allows interaction
with the systemd emergency mode, for example. Takes a string
that is sent, e.g.,
<literal>send_console(&quot;\n\nsystemctl default\n&quot;)</literal>.
</para>
</listitem>
</varlistentry>
<varlistentry> <varlistentry>
<term> <term>
<literal>execute</literal> <literal>execute</literal>
@ -502,6 +515,21 @@ machine.systemctl(&quot;list-jobs --no-pager&quot;, &quot;any-user&quot;) # spaw
</para> </para>
</listitem> </listitem>
</varlistentry> </varlistentry>
<varlistentry>
<term>
<literal>console_interact</literal>
</term>
<listitem>
<para>
Allows you to directly interact with QEMUs stdin. This
should only be used during test development, not in
production tests. Output from QEMU is only read line-wise.
<literal>Ctrl-c</literal> kills QEMU and
<literal>Ctrl-d</literal> closes console and returns to the
test runner.
</para>
</listitem>
</varlistentry>
</variablelist> </variablelist>
<para> <para>
To test user units declared by To test user units declared by

View file

@ -198,7 +198,7 @@ class StartCommand:
) -> subprocess.Popen: ) -> subprocess.Popen:
return subprocess.Popen( return subprocess.Popen(
self.cmd(monitor_socket_path, shell_socket_path), self.cmd(monitor_socket_path, shell_socket_path),
stdin=subprocess.DEVNULL, stdin=subprocess.PIPE,
stdout=subprocess.PIPE, stdout=subprocess.PIPE,
stderr=subprocess.STDOUT, stderr=subprocess.STDOUT,
shell=True, shell=True,
@ -558,6 +558,28 @@ class Machine:
pass_fds=[self.shell.fileno()], pass_fds=[self.shell.fileno()],
) )
def console_interact(self) -> None:
"""Allows you to interact with QEMU's stdin
The shell can be exited with Ctrl+D. Note that Ctrl+C is not allowed to be used.
QEMU's stdout is read line-wise.
Should only be used during test development, not in the production test."""
self.log("Terminal is ready (there is no prompt):")
assert self.process
assert self.process.stdin
while True:
try:
char = sys.stdin.buffer.read(1)
except KeyboardInterrupt:
break
if char == b"": # ctrl+d
self.log("Closing connection to the console")
break
self.send_console(char.decode())
def succeed(self, *commands: str, timeout: Optional[int] = None) -> str: def succeed(self, *commands: str, timeout: Optional[int] = None) -> str:
"""Execute each command and check that it succeeds.""" """Execute each command and check that it succeeds."""
output = "" output = ""
@ -834,6 +856,12 @@ class Machine:
self.send_monitor_command("sendkey {}".format(key)) self.send_monitor_command("sendkey {}".format(key))
time.sleep(0.01) time.sleep(0.01)
def send_console(self, chars: str) -> None:
assert self.process
assert self.process.stdin
self.process.stdin.write(chars.encode())
self.process.stdin.flush()
def start(self) -> None: def start(self) -> None:
if self.booted: if self.booted:
return return