summary refs log tree commit diff
path: root/nixos
diff options
context:
space:
mode:
authorFlorian Klink <flokli@flokli.de>2019-12-04 16:18:42 +0100
committerGitHub <noreply@github.com>2019-12-04 16:18:42 +0100
commit746a888f86f2811359d191ea60564bfdfb96fefa (patch)
tree933047b9c4ffc8601b2cb2f3d1b187731207ce23 /nixos
parentf4fd6ad2827b4bfb1046798bd7890cceb02388ce (diff)
parenta16695578b3ff667d5189cbe444baeaafb1b94ae (diff)
downloadnixpkgs-746a888f86f2811359d191ea60564bfdfb96fefa.tar
nixpkgs-746a888f86f2811359d191ea60564bfdfb96fefa.tar.gz
nixpkgs-746a888f86f2811359d191ea60564bfdfb96fefa.tar.bz2
nixpkgs-746a888f86f2811359d191ea60564bfdfb96fefa.tar.lz
nixpkgs-746a888f86f2811359d191ea60564bfdfb96fefa.tar.xz
nixpkgs-746a888f86f2811359d191ea60564bfdfb96fefa.tar.zst
nixpkgs-746a888f86f2811359d191ea60564bfdfb96fefa.zip
Merge pull request #74898 from tfc/nixos-test-retry
nixos/test: Use retry() in all looping functions that need timeouts
Diffstat (limited to 'nixos')
-rw-r--r--nixos/lib/test-driver/test-driver.py108
-rw-r--r--nixos/tests/pam-oath-login.nix110
2 files changed, 120 insertions, 98 deletions
diff --git a/nixos/lib/test-driver/test-driver.py b/nixos/lib/test-driver/test-driver.py
index 02c172c4a4d..7e575189209 100644
--- a/nixos/lib/test-driver/test-driver.py
+++ b/nixos/lib/test-driver/test-driver.py
@@ -312,8 +312,13 @@ class Machine:
         self.monitor.send(message)
         return self.wait_for_monitor_prompt()
 
-    def wait_for_unit(self, unit: str, user: Optional[str] = None) -> bool:
-        while True:
+    def wait_for_unit(self, unit: str, user: Optional[str] = None) -> None:
+        """Wait for a systemd unit to get into "active" state.
+        Throws exceptions on "failed" and "inactive" states as well as
+        after timing out.
+        """
+
+        def check_active(_: Any) -> bool:
             info = self.get_unit_info(unit, user)
             state = info["ActiveState"]
             if state == "failed":
@@ -329,8 +334,10 @@ class Machine:
                                 'unit "{}" is inactive and there ' "are no pending jobs"
                             ).format(unit)
                         )
-            if state == "active":
-                return True
+
+            return state == "active"
+
+        retry(check_active)
 
     def get_unit_info(self, unit: str, user: Optional[str] = None) -> Dict[str, str]:
         status, lines = self.systemctl('--no-pager show "{}"'.format(unit), user)
@@ -421,18 +428,34 @@ class Machine:
                     )
 
     def wait_until_succeeds(self, command: str) -> str:
+        """Wait until a command returns success and return its output.
+        Throws an exception on timeout.
+        """
+        output = ""
+
+        def check_success(_: Any) -> bool:
+            nonlocal output
+            status, output = self.execute(command)
+            return status == 0
+
         with self.nested("waiting for success: {}".format(command)):
-            while True:
-                status, output = self.execute(command)
-                if status == 0:
-                    return output
+            retry(check_success)
+            return output
 
     def wait_until_fails(self, command: str) -> str:
+        """Wait until a command returns failure.
+        Throws an exception on timeout.
+        """
+        output = ""
+
+        def check_failure(_: Any) -> bool:
+            nonlocal output
+            status, output = self.execute(command)
+            return status != 0
+
         with self.nested("waiting for failure: {}".format(command)):
-            while True:
-                status, output = self.execute(command)
-                if status != 0:
-                    return output
+            retry(check_failure)
+            return output
 
     def wait_for_shutdown(self) -> None:
         if not self.booted:
@@ -453,25 +476,38 @@ class Machine:
         )
         return output
 
-    def wait_until_tty_matches(self, tty: str, regexp: str) -> bool:
+    def wait_until_tty_matches(self, tty: str, regexp: str) -> None:
+        """Wait until the visible output on the chosen TTY matches regular
+        expression. Throws an exception on timeout.
+        """
         matcher = re.compile(regexp)
+
+        def tty_matches(last: bool) -> bool:
+            text = self.get_tty_text(tty)
+            if last:
+                self.log(
+                    f"Last chance to match /{regexp}/ on TTY{tty}, "
+                    f"which currently contains: {text}"
+                )
+            return len(matcher.findall(text)) > 0
+
         with self.nested("waiting for {} to appear on tty {}".format(regexp, tty)):
-            while True:
-                text = self.get_tty_text(tty)
-                if len(matcher.findall(text)) > 0:
-                    return True
+            retry(tty_matches)
 
     def send_chars(self, chars: List[str]) -> None:
         with self.nested("sending keys ‘{}‘".format(chars)):
             for char in chars:
                 self.send_key(char)
 
-    def wait_for_file(self, filename: str) -> bool:
+    def wait_for_file(self, filename: str) -> None:
+        """Waits until the file exists in machine's file system."""
+
+        def check_file(_: Any) -> bool:
+            status, _ = self.execute("test -e {}".format(filename))
+            return status == 0
+
         with self.nested("waiting for file ‘{}‘".format(filename)):
-            while True:
-                status, _ = self.execute("test -e {}".format(filename))
-                if status == 0:
-                    return True
+            retry(check_file)
 
     def wait_for_open_port(self, port: int) -> None:
         def port_is_open(_: Any) -> bool:
@@ -494,8 +530,8 @@ class Machine:
     def stop_job(self, jobname: str, user: Optional[str] = None) -> Tuple[int, str]:
         return self.systemctl("stop {}".format(jobname), user)
 
-    def wait_for_job(self, jobname: str) -> bool:
-        return self.wait_for_unit(jobname)
+    def wait_for_job(self, jobname: str) -> None:
+        self.wait_for_unit(jobname)
 
     def connect(self) -> None:
         if self.connected:
@@ -700,18 +736,20 @@ class Machine:
         """Wait until it is possible to connect to the X server.  Note that
         testing the existence of /tmp/.X11-unix/X0 is insufficient.
         """
+
+        def check_x(_: Any) -> bool:
+            cmd = (
+                "journalctl -b SYSLOG_IDENTIFIER=systemd | "
+                + 'grep "Reached target Current graphical"'
+            )
+            status, _ = self.execute(cmd)
+            if status != 0:
+                return False
+            status, _ = self.execute("[ -e /tmp/.X11-unix/X0 ]")
+            return status == 0
+
         with self.nested("waiting for the X11 server"):
-            while True:
-                cmd = (
-                    "journalctl -b SYSLOG_IDENTIFIER=systemd | "
-                    + 'grep "Reached target Current graphical"'
-                )
-                status, _ = self.execute(cmd)
-                if status != 0:
-                    continue
-                status, _ = self.execute("[ -e /tmp/.X11-unix/X0 ]")
-                if status == 0:
-                    return
+            retry(check_x)
 
     def get_window_names(self) -> List[str]:
         return self.succeed(
diff --git a/nixos/tests/pam-oath-login.nix b/nixos/tests/pam-oath-login.nix
index b9d489950e7..6d48199eda9 100644
--- a/nixos/tests/pam-oath-login.nix
+++ b/nixos/tests/pam-oath-login.nix
@@ -1,4 +1,4 @@
-import ./make-test.nix ({ ... }:
+import ./make-test-python.nix ({ ... }:
 
 let
   oathSnakeoilSecret = "cdd4083ef8ff1fa9178c6d46bfb1a3";
@@ -55,70 +55,54 @@ in
       };
     };
 
-  testScript =
-    ''
-      $machine->waitForUnit('multi-user.target');
-      $machine->waitUntilSucceeds("pgrep -f 'agetty.*tty1'");
-      $machine->screenshot("postboot");
-
-
-      subtest "Invalid password", sub {
-        $machine->fail("pgrep -f 'agetty.*tty2'");
-        $machine->sendKeys("alt-f2");
-        $machine->waitUntilSucceeds("[ \$(fgconsole) = 2 ]");
-        $machine->waitForUnit('getty@tty2.service');
-        $machine->waitUntilSucceeds("pgrep -f 'agetty.*tty2'");
-
-        $machine->waitUntilTTYMatches(2, "login: ");
-        $machine->sendChars("alice\n");
-        $machine->waitUntilTTYMatches(2, "login: alice");
-        $machine->waitUntilSucceeds("pgrep login");
-
-        $machine->waitUntilTTYMatches(2, "One-time password");
-        $machine->sendChars("${oathSnakeOilPassword1}\n");
-        $machine->waitUntilTTYMatches(2, "Password: ");
-        $machine->sendChars("blorg\n");
-        $machine->waitUntilTTYMatches(2, "Login incorrect");
-      };
+  testScript = ''
+    def switch_to_tty(tty_number):
+        machine.fail(f"pgrep -f 'agetty.*tty{tty_number}'")
+        machine.send_key(f"alt-f{tty_number}")
+        machine.wait_until_succeeds(f"[ $(fgconsole) = {tty_number} ]")
+        machine.wait_for_unit(f"getty@tty{tty_number}.service")
+        machine.wait_until_succeeds(f"pgrep -f 'agetty.*tty{tty_number}'")
 
-      subtest "Invalid oath token", sub {
-        $machine->fail("pgrep -f 'agetty.*tty3'");
-        $machine->sendKeys("alt-f3");
-        $machine->waitUntilSucceeds("[ \$(fgconsole) = 3 ]");
-        $machine->waitForUnit('getty@tty3.service');
-        $machine->waitUntilSucceeds("pgrep -f 'agetty.*tty3'");
-
-        $machine->waitUntilTTYMatches(3, "login: ");
-        $machine->sendChars("alice\n");
-        $machine->waitUntilTTYMatches(3, "login: alice");
-        $machine->waitUntilSucceeds("pgrep login");
-        $machine->waitUntilTTYMatches(3, "One-time password");
-        $machine->sendChars("000000\n");
-        $machine->waitUntilTTYMatches(3, "Login incorrect");
-        $machine->waitUntilTTYMatches(3, "login:");
-      };
 
-      subtest "Happy path (both passwords are mandatory to get us in)", sub {
-        $machine->fail("pgrep -f 'agetty.*tty4'");
-        $machine->sendKeys("alt-f4");
-        $machine->waitUntilSucceeds("[ \$(fgconsole) = 4 ]");
-        $machine->waitForUnit('getty@tty4.service');
-        $machine->waitUntilSucceeds("pgrep -f 'agetty.*tty4'");
-
-        $machine->waitUntilTTYMatches(4, "login: ");
-        $machine->sendChars("alice\n");
-        $machine->waitUntilTTYMatches(4, "login: alice");
-        $machine->waitUntilSucceeds("pgrep login");
-        $machine->waitUntilTTYMatches(4, "One-time password");
-        $machine->sendChars("${oathSnakeOilPassword2}\n");
-        $machine->waitUntilTTYMatches(4, "Password: ");
-        $machine->sendChars("${alicePassword}\n");
-
-        $machine->waitUntilSucceeds("pgrep -u alice bash");
-        $machine->sendChars("touch  done4\n");
-        $machine->waitForFile("/home/alice/done4");
-      };
+    def enter_user_alice(tty_number):
+        machine.wait_until_tty_matches(tty_number, "login: ")
+        machine.send_chars("alice\n")
+        machine.wait_until_tty_matches(tty_number, "login: alice")
+        machine.wait_until_succeeds("pgrep login")
+        machine.wait_until_tty_matches(tty_number, "One-time password")
 
-    '';
 
+    machine.wait_for_unit("multi-user.target")
+    machine.wait_until_succeeds("pgrep -f 'agetty.*tty1'")
+    machine.screenshot("postboot")
+
+    with subtest("Invalid password"):
+        switch_to_tty(2)
+        enter_user_alice(2)
+
+        machine.send_chars("${oathSnakeOilPassword1}\n")
+        machine.wait_until_tty_matches(2, "Password: ")
+        machine.send_chars("blorg\n")
+        machine.wait_until_tty_matches(2, "Login incorrect")
+
+    with subtest("Invalid oath token"):
+        switch_to_tty(3)
+        enter_user_alice(3)
+
+        machine.send_chars("000000\n")
+        machine.wait_until_tty_matches(3, "Login incorrect")
+        machine.wait_until_tty_matches(3, "login:")
+
+    with subtest("Happy path: Both passwords are mandatory to get us in"):
+        switch_to_tty(4)
+        enter_user_alice(4)
+
+        machine.send_chars("${oathSnakeOilPassword2}\n")
+        machine.wait_until_tty_matches(4, "Password: ")
+        machine.send_chars("${alicePassword}\n")
+
+        machine.wait_until_succeeds("pgrep -u alice bash")
+        machine.send_chars("touch  done4\n")
+        machine.wait_for_file("/home/alice/done4")
+    '';
 })