jenkins-bot has submitted this change. ( 
https://gerrit.wikimedia.org/r/c/pywikibot/core/+/1320208?usp=email )

Change subject: tests: Add TimeoutExpired to utils.execute result
......................................................................

tests: Add TimeoutExpired to utils.execute result

Bug: T433834
Change-Id: Ibd628eaae6b93618ef1d607d702e3ce3a65ce4e6
---
M tests/aspects.py
M tests/pwb_tests.py
M tests/script_tests.py
M tests/utils.py
4 files changed, 91 insertions(+), 20 deletions(-)

Approvals:
  jenkins-bot: Verified
  Xqt: Looks good to me, approved




diff --git a/tests/aspects.py b/tests/aspects.py
index 9486323..6ff4c77 100644
--- a/tests/aspects.py
+++ b/tests/aspects.py
@@ -54,6 +54,7 @@
     AssertAPIErrorContextManager,
     DryRequest,
     DrySite,
+    ExecuteResult,
     WarningSourceSkipContextManager,
     execute_pwb,
     skipping,
@@ -1552,15 +1553,18 @@
         if self.orig_pywikibot_dir:  # pragma: no cover
             os.environ['PYWIKIBOT_DIR'] = self.orig_pywikibot_dir

-    def execute(self, args: list[str], **kwargs):
+    def execute(self, args: list[str], **kwargs) -> ExecuteResult:
         """Run :func:`tests.utils.execute_pwb` with default site.

         .. version-changed:: 9.1
            pass all arguments to :func:`tests.utils.execute_pwb`; make
            this method public.
+        .. version-changed:: 11.7
+           Return timeout information.

         :param args: :mod:`pwb` warapper script arguments
         :param kwargs: keyword arguments of :func:`tests.utils.execute_pwb`
+        :return: Result of :func:`tests.utils.execute_pwb`.
         """
         site = self.get_site()
         args.append(f'-site:{site.sitename}')
diff --git a/tests/pwb_tests.py b/tests/pwb_tests.py
index b4d4710..508a8cb 100755
--- a/tests/pwb_tests.py
+++ b/tests/pwb_tests.py
@@ -43,6 +43,9 @@

         direct = execute([sys.executable, '-m', package_name])
         vpwb = execute_pwb([script_path])
+
+        self.assertIsNone(direct['timeout'])
+        self.assertIsNone(vpwb['timeout'])
         self.maxDiff = None
         self.assertEqual(direct['stdout'], vpwb['stdout'])

@@ -112,6 +115,8 @@
             'in 5.0 seconds; type CTRL-C to stop.',
         ]
         stream = execute_pwb(['hello'], data_in=chr(3), timeout=10)
+        self.assertIsNone(stream['timeout'])
+
         stderr = io.StringIO(stream['stderr'])
         with self.subTest(line=0):
             self.assertEqual(stderr.readline().strip(), result[0])
diff --git a/tests/script_tests.py b/tests/script_tests.py
index 07e6051..28fcf00 100755
--- a/tests/script_tests.py
+++ b/tests/script_tests.py
@@ -236,13 +236,23 @@
                     test_overrides['pywikibot.Site'] = 'lambda *a, **k: None'

                 # run the script
-                result = execute_pwb(cmd, data_in=data_in, timeout=timeout,
-                                     overrides=test_overrides)
+                result = execute_pwb(
+                    cmd,
+                    data_in=data_in,
+                    timeout=timeout,
+                    overrides=test_overrides,
+                )

                 err_result = result['stderr']
                 out_result = result['stdout']
                 stderr_other = err_result.splitlines()

+                if timeout_error := result['timeout']:
+                    unittest_print(
+                        f' timeout after {timeout_error.timeout} s',
+                        end='  '
+                    )
+
                 if result['exit_code'] == -9:
                     unittest_print(' killed', end='  ')

diff --git a/tests/utils.py b/tests/utils.py
index 63977c2..e3a328a 100644
--- a/tests/utils.py
+++ b/tests/utils.py
@@ -16,7 +16,7 @@
 from contextlib import contextmanager, suppress
 from pathlib import Path
 from subprocess import PIPE, Popen, TimeoutExpired
-from typing import Any, NoReturn
+from typing import NoReturn, TypedDict

 import pywikibot
 from pywikibot import config
@@ -463,15 +463,37 @@
         """Ignore password changes."""


-def execute(command: list[str], *, data_in=None, timeout=None):
+class ExecuteResult(TypedDict):
+
+    """Result returned by execute()."""
+
+    exit_code: int
+    stdout: str
+    stderr: str
+    timeout: TimeoutExpired | None
+
+
+def execute(
+    command: list[str],
+    *,
+    data_in: Sequence[str] | None = None,
+    timeout: int | float | None = None,
+) -> ExecuteResult:
     """Execute a command and capture outputs.

     .. version-changed:: 8.2
        *error* parameter was removed.
     .. version-changed:: 9.1
        parameters except *command* are keyword only.
+    .. version-changed:: 11.7
+       Return timeout information.

-    :param command: executable to run and arguments to use
+    :param command: executable to run and arguments to use.
+    :param data_in: Lines to send to the process via standard input.
+    :param timeout: Maximum number of seconds to wait for the process.
+    :return: Mapping containing the process exit code, captured stdout,
+        captured stderr, and the :exc:`subprocess.TimeoutExpired`
+        exception if the process timed out.
     """
     env = os.environ.copy()

@@ -493,28 +515,43 @@
     # Set EDITOR to an executable that ignores all arguments and does nothing.
     env['EDITOR'] = 'break' if OSWIN32 else 'true'

-    p = Popen(command, env=env, stdout=PIPE, stderr=PIPE,
-              stdin=PIPE if data_in is not None else None)
+    p = Popen(
+        command,
+        env=env,
+        stdout=PIPE,
+        stderr=PIPE,
+        stdin=PIPE if data_in is not None else None
+    )

     if data_in is not None:
         data_in = data_in.encode(config.console_encoding)

+    timeout_error: TimeoutExpired | None = None
     try:
-        stdout_data, stderr_data = p.communicate(input=data_in,
-                                                 timeout=timeout)
-    except TimeoutExpired:
+        stdout_data, stderr_data = p.communicate(
+            input=data_in,
+            timeout=timeout
+        )
+    except TimeoutExpired as e:
+        timeout_error = e
         p.kill()
         stdout_data, stderr_data = p.communicate()

-    return {'exit_code': p.returncode,
-            'stdout': stdout_data.decode(config.console_encoding),
-            'stderr': stderr_data.decode(config.console_encoding)}
+    return {
+        'exit_code': p.returncode,
+        'stdout': stdout_data.decode(config.console_encoding),
+        'stderr': stderr_data.decode(config.console_encoding),
+        'timeout': timeout_error,
+    }


-def execute_pwb(args: list[str], *,
-                data_in: Sequence[str] | None = None,
-                timeout: int | float | None = None,
-                overrides: dict[str, str] | None = None) -> dict[str, Any]:
+def execute_pwb(
+    args: list[str],
+    *,
+    data_in: Sequence[str] | None = None,
+    timeout: int | float | None = None,
+    overrides: dict[str, str] | None = None,
+) -> ExecuteResult:
     """Execute the pwb.py script and capture outputs.

     .. version-changed:: 8.2
@@ -524,9 +561,18 @@
     .. version-changed:: 10.4
        coverage is used if running github actions and a temporary file
        is used for overrides.
+    .. version-changed:: 11.7
+       The return value includes timeout information.
+
+    .. seealso:: :func:`execute`

     :param args: list of arguments for pwb.py
+    :param data_in: Lines to send to the process via standard input.
+    :param timeout: Maximum number of seconds to wait for the process.
     :param overrides: mapping of pywikibot symbols to test replacements
+    :return: Mapping containing the process exit code, captured stdout,
+        captured stderr, and the :exc:`subprocess.TimeoutExpired`
+        exception if the process timed out.
     """
     tmp_path: Path | None = None
     command = [sys.executable]
@@ -547,7 +593,10 @@
         if use_coverage:
             # Write overrides in temporary file
             with tempfile.NamedTemporaryFile(
-                    'w', suffix='.py', delete=False) as f:
+                'w',
+                suffix='.py',
+                delete=False,
+            ) as f:
                 f.write(override_code)
                 tmp_path = Path(f.name)
                 command.append(f.name)
@@ -560,7 +609,10 @@
     try:
         # Run subprocess
         result = execute(
-            command=command + args, data_in=data_in, timeout=timeout)
+            command=command + args,
+            data_in=data_in,
+            timeout=timeout,
+        )
     finally:
         # delete temporary file if created
         if tmp_path and tmp_path.exists():

--
To view, visit 
https://gerrit.wikimedia.org/r/c/pywikibot/core/+/1320208?usp=email
To unsubscribe, or for help writing mail filters, visit 
https://gerrit.wikimedia.org/r/settings?usp=email

Gerrit-MessageType: merged
Gerrit-Project: pywikibot/core
Gerrit-Branch: master
Gerrit-Change-Id: Ibd628eaae6b93618ef1d607d702e3ce3a65ce4e6
Gerrit-Change-Number: 1320208
Gerrit-PatchSet: 2
Gerrit-Owner: Xqt <[email protected]>
Gerrit-Reviewer: Xqt <[email protected]>
Gerrit-Reviewer: jenkins-bot
_______________________________________________
Pywikibot-commits mailing list -- [email protected]
To unsubscribe send an email to [email protected]

Reply via email to