diff --git a/nixos/doc/manual/development/writing-nixos-tests.section.md b/nixos/doc/manual/development/writing-nixos-tests.section.md
index 7de57d0d2a37..433e1906f775 100644
--- a/nixos/doc/manual/development/writing-nixos-tests.section.md
+++ b/nixos/doc/manual/development/writing-nixos-tests.section.md
@@ -158,6 +158,12 @@ The following methods are available on machine objects:
e.g., `send_chars("foobar\n")` will type the string `foobar`
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 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
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
`user` argument can be used:
diff --git a/nixos/doc/manual/from_md/development/writing-nixos-tests.section.xml b/nixos/doc/manual/from_md/development/writing-nixos-tests.section.xml
index 45c9c40c6095..4f856f98f2a2 100644
--- a/nixos/doc/manual/from_md/development/writing-nixos-tests.section.xml
+++ b/nixos/doc/manual/from_md/development/writing-nixos-tests.section.xml
@@ -261,6 +261,19 @@ start_all()
+
+
+ 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
@@ -502,6 +515,21 @@ machine.systemctl("list-jobs --no-pager", "any-user") # spaw
+
+
+ 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
diff --git a/nixos/lib/test-driver/test_driver/machine.py b/nixos/lib/test-driver/test_driver/machine.py
index 569a0f3c61e4..f3e615fe5bf9 100644
--- a/nixos/lib/test-driver/test_driver/machine.py
+++ b/nixos/lib/test-driver/test_driver/machine.py
@@ -198,7 +198,7 @@ class StartCommand:
) -> subprocess.Popen:
return subprocess.Popen(
self.cmd(monitor_socket_path, shell_socket_path),
- stdin=subprocess.DEVNULL,
+ stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
shell=True,
@@ -558,6 +558,28 @@ class Machine:
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:
"""Execute each command and check that it succeeds."""
output = ""
@@ -834,6 +856,12 @@ class Machine:
self.send_monitor_command("sendkey {}".format(key))
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:
if self.booted:
return