Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/whoseport/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@
"""

from whoseport.core import PortUser, UnsupportedPlatformError, collect
from whoseport.waitarg import install as _install_waitarg

__version__ = "0.2.0"

_install_waitarg()

__all__ = ["PortUser", "UnsupportedPlatformError", "__version__", "collect"]
42 changes: 42 additions & 0 deletions src/whoseport/waitarg.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
"""Guards for ``--wait`` so a typo cannot hang the CLI forever."""

from __future__ import annotations

import math


def require_finite_wait(seconds: float) -> float:
"""Return *seconds* if it is a finite, positive duration.

``argparse`` ``type=float`` accepts ``nan`` / ``inf``. ``nan <= 0`` is
false, so the old ``<= 0`` check let ``--wait nan`` through and
``--wait inf`` never hit the timeout.
"""
value = float(seconds)
if not math.isfinite(value) or value <= 0:
raise ValueError("--wait needs a positive number of seconds")
return value


def _wait_type(text: str) -> float:
return require_finite_wait(float(text))


def install() -> None:
"""Patch ``cli.build_parser`` so ``--wait`` rejects NaN/Inf."""
import whoseport.cli as cli

if getattr(cli, "_waitarg_installed", False):
return

orig = cli.build_parser

def build_parser():
parser = orig()
for action in parser._actions:
if "--wait" in getattr(action, "option_strings", ()):
action.type = _wait_type
return parser

cli.build_parser = build_parser
cli._waitarg_installed = True
41 changes: 41 additions & 0 deletions tests/test_wait_finite.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import math

import pytest

from whoseport import cli
from whoseport.waitarg import require_finite_wait


def test_require_finite_wait_accepts_positive():
assert require_finite_wait(0.5) == 0.5
assert require_finite_wait(30) == 30


def test_require_finite_wait_rejects_non_positive_and_non_finite():
for bad in (0, -1, float("nan"), float("inf"), float("-inf")):
with pytest.raises(ValueError, match="positive"):
require_finite_wait(bad)


def test_cli_wait_rejects_nan():
with pytest.raises(SystemExit) as exc:
cli.main(["8080", "--wait", "nan"])
assert exc.value.code == 2


def test_cli_wait_rejects_inf():
with pytest.raises(SystemExit) as exc:
cli.main(["8080", "--wait", "inf"])
assert exc.value.code == 2


def test_cli_wait_rejects_negative_inf():
with pytest.raises(SystemExit) as exc:
cli.main(["8080", "--wait", "-inf"])
assert exc.value.code == 2


def test_math_nan_is_not_less_or_equal_zero():
# documents why the old `<= 0` guard was not enough
assert not (float("nan") <= 0)
assert math.isnan(float("nan"))
Loading