Skip to content

Commit

Permalink
Add bang to invert exit code
Browse files Browse the repository at this point in the history
This makes it so that you can provide a "!" before the command line to
invert the success/failure of a command. Just like with regular POSIX
shell.
  • Loading branch information
Asger Gitz-Johansen committed Apr 25, 2024
1 parent 822c9d0 commit 061501d
Show file tree
Hide file tree
Showing 4 changed files with 27 additions and 4 deletions.
11 changes: 8 additions & 3 deletions src/tox/config/types.py
Expand Up @@ -16,15 +16,20 @@ def __init__(self, args: list[str]) -> None:
:param args: the command line arguments (first value can be ``-`` to indicate ignore the exit code)
"""
self.ignore_exit_code: bool = args[0] == "-" #: a flag indicating if the exit code should be ignored
self.args: list[str] = args[1:] if self.ignore_exit_code else args #: the command line arguments
self.invert_exit_code: bool = args[0] == "!" #: a flag for flipped exit code (non-zero = success, 0 = error)
self.args: list[str] = (
args[1:] if self.ignore_exit_code or self.invert_exit_code else args
) #: the command line arguments

def __repr__(self) -> str:
return f"{type(self).__name__}(args={(['-'] if self.ignore_exit_code else []) + self.args!r})"
args = (["-"] if self.ignore_exit_code else ["!"] if self.invert_exit_code else []) + self.args
return f"{type(self).__name__}(args={args!r})"

def __eq__(self, other: object) -> bool:
return type(self) == type(other) and (self.args, self.ignore_exit_code) == (
return type(self) == type(other) and (self.args, self.ignore_exit_code, self.invert_exit_code) == (
other.args, # type: ignore[attr-defined]
other.ignore_exit_code, # type: ignore[attr-defined]
other.invert_exit_code, # type: ignore[attr-defined]
)

def __ne__(self, other: object) -> bool:
Expand Down
6 changes: 6 additions & 0 deletions src/tox/execute/api.py
Expand Up @@ -252,6 +252,12 @@ def assert_success(self) -> None:
self._assert_fail()
self.log_run_done(logging.INFO)

def assert_failure(self) -> None:
"""Assert that the execution failed."""
if self.exit_code is not None and self.exit_code == self.OK:
self._assert_fail()
self.log_run_done(logging.INFO)

def _assert_fail(self) -> NoReturn:
if self.show_on_standard is False:
if self.out:
Expand Down
5 changes: 4 additions & 1 deletion src/tox/session/cmd/run/single.py
Expand Up @@ -112,7 +112,10 @@ def run_command_set(
)
outcomes.append(current_outcome)
try:
current_outcome.assert_success()
if cmd.invert_exit_code:
current_outcome.assert_failure()
else:
current_outcome.assert_success()
except SystemExit as exception:
if cmd.ignore_exit_code:
logging.warning("command failed but is marked ignore outcome so handling it as success")
Expand Down
9 changes: 9 additions & 0 deletions tests/config/test_types.py
Expand Up @@ -6,15 +6,24 @@
def tests_command_repr() -> None:
cmd = Command(["python", "-m", "pip", "list"])
assert repr(cmd) == "Command(args=['python', '-m', 'pip', 'list'])"
assert cmd.invert_exit_code is False
assert cmd.ignore_exit_code is False


def tests_command_repr_ignore() -> None:
cmd = Command(["-", "python", "-m", "pip", "list"])
assert repr(cmd) == "Command(args=['-', 'python', '-m', 'pip', 'list'])"
assert cmd.invert_exit_code is False
assert cmd.ignore_exit_code is True


def tests_command_repr_invert() -> None:
cmd = Command(["!", "python", "-m", "pip", "list"])
assert repr(cmd) == "Command(args=['!', 'python', '-m', 'pip', 'list'])"
assert cmd.invert_exit_code is True
assert cmd.ignore_exit_code is False


def tests_command_eq() -> None:
cmd_1 = Command(["python", "-m", "pip", "list"])
cmd_2 = Command(["python", "-m", "pip", "list"])
Expand Down

0 comments on commit 061501d

Please sign in to comment.