Repository navigation
testing.CliRunner.invoke cannot pass color for Context instantiation #2110
Description
TL;DR
click.testing.CliRunner.invoke and click.Context signatures shares a common color parameter.
So when the former call the later via click.BaseCommand.main, that command's main() cannot get a color parameter in its **extra (because it has been consumed by click.testing.CliRunner.invoke).
Description
I'm in the middle of developing a set of extensions for Click under the name click-extra. It involves a lot of colouring helpers. In my testing suite I need to simulate a command that will end up having its default context's color property initialized to user's liking.
For that I need the **extra dictionnary of click.testing.CliRunner.invoke to contain a "color": "my_custom_value" item. The problem is that invoke() method redefine itself the color property. Which means its signature consume my default value and is never appended to the **extra dictionary of the original command's main() call.
Current behavior
The current implementation allows for the invoke command to specify the color rendering of the output:
import click
from click.testing import CliRunner
runner = CliRunner()
@click.command()
@click.pass_context
def my_cli(ctx):
click.echo(click.style("It works!", fg="blue"))
click.echo(f"Context.color = {ctx.color!r}")
def test_vanilla_invokation():
# Default invokation strip colors, no context influence.
result = runner.invoke(my_cli)
assert result.exit_code == 0
assert "It works!" in result.output
assert "Context.color = None" in result.output
# Force results to strip colors, no context influence.
result = runner.invoke(my_cli, color=False)
assert result.exit_code == 0
assert "It works!" in result.output
assert "Context.color = None" in result.output
# Force results to keep colors, no context influence.
result = runner.invoke(my_cli, color=True)
assert result.exit_code == 0
assert "\x1b[34mIt works!\x1b[0m" in result.output
assert "Context.color = None" in result.outputNotice how it does not have any influence on the command's context.
Expected behavior
I'm looking to have an influence on the command context. Here is what I expect invoke() would allow me to do:
def test_context_color_initialization():
extra = {"color": "my_custom_value"}
# Default invokation strip colors, no context influence.
result = runner.invoke(my_cli, **extra)
assert result.exit_code == 0
assert "It works!" in result.output
assert "Context.color = None" in result.output
# Force results to strip colors, no context influence.
result = runner.invoke(my_cli, color=False, **extra)
assert result.exit_code == 0
assert "It works!" in result.output
assert "Context.color = 'my_custom_value'" in result.output
# Force results to keep colors, no context influence.
result = runner.invoke(my_cli, color=True, **extra)
assert result.exit_code == 0
assert "\x1b[34mIt works!\x1b[0m" in result.output
assert "Context.color = 'my_custom_value'" in result.outputOf course it doesn't work as invoke() ends up with 2 values for color, and is thus unable to pass one of them in the form of my_cli.main(color="my_custom_value") for context instanciation.
Environment
-
Python version:
$ python Python 3.9.7 (default, Oct 13 2021, 06:45:31) [Clang 13.0.0 (clang-1300.0.29.3)] on darwin -
Click version:
>>> import click >>> click.__version__ '8.0.3'
Activity
[-]`testing.CliRunner.invoke` cannot pass `color` for Context instanciation[/-][+]`testing.CliRunner.invoke` cannot pass `color` for Context instantiation[/+]davidism commentedon Oct 26, 2021
The
colorargument and attribute is annotated ascolor: Optional[bool], so passing other values is not supported.Allow context.color initialization of invoked command.
kdeldycke commentedon Oct 26, 2021
Ahah yes indeed! 😅 My example was not that good. The reality is that all I want to pass is a boolean, i.e. end up with either:
my_cli.main(color=True)my_cli.main(color=False)Anyway, I just came up with a convoluted way to bypass that limitation by redefining the
invoke()command and monkeypatching on the fly the command'smain(). It kind of crappy but does the job.All in all, should we consider this as a
testing.CliRunner.invokebug? By the way, how do you test that case in Click itself?davidism commentedon Oct 26, 2021
You can modify
command.context_settings["color"]to change it for the command.I'd have to see where it's tested in Click, the
colorargument was added in version 4 before I started maintaining it. Theinvokearg sets whether the "terminal" supports color, and theContextarg sets whether to override that. It's not a bug though, but I don't think it's worth going through a deprecation cycle to rename right now. It seems like a fairly niche case to want to both set the terminal support and force the color code output anyway, and can be handled withcontext_settings.Passing
invoke(color=True)essentially does what's expected, tells Click to output color by default. Either you don't want to test the color codes in the output, so you don't pass the arg, or you do want the codes and pass the arg. Also overriding the context seems redundant.Add `context_settings` parameter to `CliRunner.invoke`
Add `context_settings` parameter to `CliRunner.invoke`
Add `context_settings` parameter to `CliRunner.invoke`