Skip to content

Commit

Permalink
Add a new timeout option to CLI
Browse files Browse the repository at this point in the history
  • Loading branch information
kdeldycke committed Nov 13, 2023
1 parent 7c65921 commit bd713ef
Show file tree
Hide file tree
Showing 5 changed files with 37 additions and 14 deletions.
1 change: 1 addition & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
This version is not released yet and is under active development.
```

- \[mpm\] Add a `-t`/`--timeout` option to set the maximum duration of each CLI call. Defaults to 10 minutes.
- \[mpm\] Drop support of Python 3.7.
- \[scoop\] Fix parsing of Scoop version.
- \[mpm\] Group platforms by family in the `managers` subcommand.
Expand Down
26 changes: 15 additions & 11 deletions meta_package_manager/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,9 @@
from functools import cached_property
from pathlib import Path
from textwrap import dedent, indent, shorten
from typing import ContextManager, Generator, Iterable, Iterator
from typing import ContextManager, Generator, Iterable, Iterator, cast
from unittest.mock import patch
import subprocess

from boltons.iterutils import flatten, unique
from boltons.strutils import strip_ansi
Expand All @@ -41,7 +42,7 @@
NestedArgs,
args_cleanup,
format_cli_prompt,
run_cmd,
env_copy,
)

from .version import TokenizedString, parse_version
Expand Down Expand Up @@ -330,6 +331,9 @@ class PackageManager(metaclass=MetaPackageManager):
dry_run: bool = False
"""Do not actually perform any action, just simulate CLI calls."""

timeout: int | None = None
"""Maximum number of seconds to wait for a CLI call to complete."""

cli_errors: list[CLIError]
"""Accumulate all CLI errors encountered by the package manager."""

Expand Down Expand Up @@ -623,11 +627,6 @@ def run(self, *args: Arg | NestedArgs, extra_env: EnvVars | None = None) -> str:
* returning ready-to-use normalized strings (dedented and stripped)
* letting :option:`mpm --dry-run` and :option:`mpm --stop-on-error` have
expected effect on execution
.. todo::
Move :option:`mpm --dry-run` option and this method to `click-extra
<https://github.com/kdeldycke/click-extra>`_.
"""
# Casting to string helps serialize Path and Version objects.
clean_args = args_cleanup(*args)
Expand All @@ -641,11 +640,16 @@ def run(self, *args: Arg | NestedArgs, extra_env: EnvVars | None = None) -> str:
logging.warning(f"Dry-run: {cli_msg}")
else:
logging.debug(cli_msg)
code, output, error = run_cmd(
*clean_args,
extra_env=extra_env,
print_output=False,
result = subprocess.run(
clean_args,
capture_output=True,
timeout=self.timeout,
encoding="utf-8",
env=cast("subprocess._ENV", env_copy(extra_env)),
)
code = result.returncode
output = result.stdout
error = result.stderr

# Normalize messages.
if error:
Expand Down
10 changes: 10 additions & 0 deletions meta_package_manager/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
Choice,
Context,
File,
IntRange,
Parameter,
Section,
argument,
Expand Down Expand Up @@ -224,6 +225,13 @@ def bar_plugin_path(ctx, param, value):
default=False,
help="Do not actually perform any action, just simulate CLI calls.",
),
option(
"-t",
"--timeout",
type=IntRange(min=0),
default=300,
help="Set maximum duration in seconds for each CLI call.",
),
)
@option_group(
"Output options",
Expand Down Expand Up @@ -275,6 +283,7 @@ def mpm(
ignore_auto_updates,
stop_on_error,
dry_run,
timeout,
description,
sort_by,
stats,
Expand Down Expand Up @@ -332,6 +341,7 @@ def remove_logging_override():
# Does the manager should raise on error or not.
stop_on_error=stop_on_error,
dry_run=dry_run,
timeout=timeout,
)

# Load up current and new global options to the context for subcommand consumption.
Expand Down
4 changes: 2 additions & 2 deletions meta_package_manager/pool.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ class ManagerPool:
"""A dict-like register, instanciating all supported package managers."""

ALLOWED_EXTRA_OPTION: Final = frozenset(
{"ignore_auto_updates", "stop_on_error", "dry_run"},
{"ignore_auto_updates", "stop_on_error", "dry_run", "timeout"},
)
"""List of extra options that are allowed to be set on managers during the use of
the :py:func:`meta_package_manager.pool.ManagerPool.select_managers` helper
Expand Down Expand Up @@ -185,7 +185,7 @@ def select_managers(
keep_unsupported: bool = False,
drop_inactive: bool = True,
implements_operation: Operations | None = None,
**extra_options: bool,
**extra_options: bool | int,
) -> Iterator[PackageManager]:
"""Utility method to extract a subset of the manager pool based on selection
list (``keep`` parameter) and exclusion list (``drop`` parameter) criterion.
Expand Down
10 changes: 9 additions & 1 deletion meta_package_manager/tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ def check_manager_selection(


class TestCommonCLI:
"""Tests CLI behavior shared by all subcommands.
"""Single tests for CLI behavior shared by all subcommands.
If we have to, we only run the test on a single, non-destructive subcommand
(like ``mpm installed`` or ``mpm managers``). Not all subcommands are tested.
Expand Down Expand Up @@ -156,6 +156,14 @@ def test_executable_module(self):
expected_output = strip_ansi(expected_output)
assert process.stdout == expected_output

def test_timeout(self, invoke):
"""Check that the CLI exits with an error when a timeout is reached."""
result = invoke("--timeout", "1", "outdated")
assert result.exit_code == 1
assert not result.stdout
assert not result.stderr

Check failure on line 164 in meta_package_manager/tests/test_cli.py

View workflow job for this annotation

GitHub Actions / tests (ubuntu-22.04, 3.8, stable)

TestCommonCLI.test_timeout assert not "\x1b[33mwarning\x1b[0m: WARNING: apt does not have a stable CLI interface. Use with caution in scripts.\n\x1b[33mwa... '--releasever' to specify release version)\n\x1b[33mwarning\x1b[0m: Skip unavailable \x1b[97memerge\x1b[0m manager.\n" + where "\x1b[33mwarning\x1b[0m: WARNING: apt does not have a stable CLI interface. Use with caution in scripts.\n\x1b[33mwa... '--releasever' to specify release version)\n\x1b[33mwarning\x1b[0m: Skip unavailable \x1b[97memerge\x1b[0m manager.\n" = <ExtraResult TimeoutExpired(('/usr/bin/gem', 'outdated', '--quiet'), 1)>.stderr

Check failure on line 164 in meta_package_manager/tests/test_cli.py

View workflow job for this annotation

GitHub Actions / tests (ubuntu-20.04, 3.12, stable)

TestCommonCLI.test_timeout AssertionError: assert not '\x1b[33mwarning\x1b[0m: WARNING: apt does not have a stable CLI interface. Use with caution in scripts.\n\x1b[33mwa...1b[0m: F: Both min-free-space-percent and -size are mentioned in config. Enforcing min-free-space-size check only.\n' + where '\x1b[33mwarning\x1b[0m: WARNING: apt does not have a stable CLI interface. Use with caution in scripts.\n\x1b[33mwa...1b[0m: F: Both min-free-space-percent and -size are mentioned in config. Enforcing min-free-space-size check only.\n' = <ExtraResult TimeoutExpired(('/usr/bin/gem', 'outdated', '--quiet'), 1)>.stderr
assert isinstance(result.exception, subprocess.TimeoutExpired)

@pytest.mark.parametrize(
("stats_arg", "active_stats"),
(("--stats", True), ("--no-stats", False), (None, True)),
Expand Down

0 comments on commit bd713ef

Please sign in to comment.