about summary refs log tree commit diff
path: root/nixos
diff options
context:
space:
mode:
authorJanne Heß <janne@hess.ooo>2022-03-25 11:36:29 +0100
committerJanne Heß <janne@hess.ooo>2022-03-25 12:08:21 +0100
commit7d0e21c77e3c8b1d6dcd26a3710922087fe99df2 (patch)
tree4b91f3145d1f8aa405fa79d089dcbb5dde44cef6 /nixos
parent6a9b74a92e78e99750607654b16e0085459dd0fd (diff)
downloadnixlib-7d0e21c77e3c8b1d6dcd26a3710922087fe99df2.tar
nixlib-7d0e21c77e3c8b1d6dcd26a3710922087fe99df2.tar.gz
nixlib-7d0e21c77e3c8b1d6dcd26a3710922087fe99df2.tar.bz2
nixlib-7d0e21c77e3c8b1d6dcd26a3710922087fe99df2.tar.lz
nixlib-7d0e21c77e3c8b1d6dcd26a3710922087fe99df2.tar.xz
nixlib-7d0e21c77e3c8b1d6dcd26a3710922087fe99df2.tar.zst
nixlib-7d0e21c77e3c8b1d6dcd26a3710922087fe99df2.zip
nixos/test-runner: Allow writing to qemu stdin
Diffstat (limited to 'nixos')
-rw-r--r--nixos/doc/manual/development/writing-nixos-tests.section.md13
-rw-r--r--nixos/doc/manual/from_md/development/writing-nixos-tests.section.xml28
-rw-r--r--nixos/lib/test-driver/test_driver/machine.py30
3 files changed, 70 insertions, 1 deletions
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
@@ -263,6 +263,19 @@ start_all()
       </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>
+        <term>
           <literal>execute</literal>
         </term>
         <listitem>
@@ -502,6 +515,21 @@ machine.systemctl(&quot;list-jobs --no-pager&quot;, &quot;any-user&quot;) # spaw
           </para>
         </listitem>
       </varlistentry>
+      <varlistentry>
+        <term>
+          <literal>console_interact</literal>
+        </term>
+        <listitem>
+          <para>
+            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.
+            <literal>Ctrl-c</literal> kills QEMU and
+            <literal>Ctrl-d</literal> closes console and returns to the
+            test runner.
+          </para>
+        </listitem>
+      </varlistentry>
     </variablelist>
     <para>
       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