Skip to content
Open
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
1 change: 1 addition & 0 deletions news/130.bugfix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixed `Command.call()` and `Command.call_silent()` misbinding arguments to the called command.
7 changes: 5 additions & 2 deletions src/cleo/commands/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,8 @@ def call(self, name: str, args: str | None = None) -> int:
command = self.application.get(name)

return self.application._run_command(
command, self._io.with_input(StringInput(args or ""))
command,
self._io.with_input(StringInput(f"{name} {args}" if args else name)),
)

def call_silent(self, name: str, args: str | None = None) -> int:
Expand All @@ -103,7 +104,9 @@ def call_silent(self, name: str, args: str | None = None) -> int:
assert self.application is not None
command = self.application.get(name)

return self.application._run_command(command, NullIO(StringInput(args or "")))
return self.application._run_command(
command, NullIO(StringInput(f"{name} {args}" if args else name))
)

def argument(self, name: str) -> Any:
"""
Expand Down
52 changes: 52 additions & 0 deletions tests/commands/test_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from cleo.application import Application
from cleo.commands.command import Command
from cleo.helpers import argument
from cleo.testers.application_tester import ApplicationTester
from cleo.testers.command_tester import CommandTester
from tests.fixtures.inherited_command import ChildCommand
from tests.fixtures.signature_command import SignatureCommand
Expand Down Expand Up @@ -87,3 +88,54 @@ def test_explicit_multiple_argument() -> None:
tester.execute("1 2 3")

assert tester.io.fetch_output() == "1,2,3\n"


class CalleeCommand(Command):
name = "callee"
arguments: ClassVar[list[Argument]] = [argument("value", "The value to print.")]

def handle(self) -> int:
self.line(self.argument("value"))
return 0


class CallerCommand(Command):
name = "caller"

def handle(self) -> int:
return self.call("callee", "hello")


class SilentCallerCommand(Command):
name = "silent-caller"

def handle(self) -> int:
return self.call_silent("callee", "hello")


def _app_with_callee_and(caller: Command) -> Application:
application = Application()
application.add(CalleeCommand())
application.add(caller)

return application


def test_call_binds_arguments_to_the_called_command() -> None:
# Regression test for https://github.com/python-poetry/cleo/issues/130: the
# application's own top-level "command" argument used to be first in line for
# binding, so the called command's first real argument was silently swallowed
# by that phantom slot instead of reaching the called command's own argument.
tester = ApplicationTester(_app_with_callee_and(CallerCommand()))

assert tester.execute("caller") == 0
assert tester.io.fetch_output() == "hello\n"


def test_call_silent_binds_arguments_to_the_called_command() -> None:
tester = ApplicationTester(_app_with_callee_and(SilentCallerCommand()))

assert tester.execute("silent-caller") == 0
# call_silent runs the called command against a NullIO, so its own output
# isn't captured here -- the point of this test is that the call above didn't
# raise "Not enough arguments" and returned a successful status code.