Source code for tests.test_parameters

# Copyright Kevin Deldycke <kevin@deldycke.com> and contributors.
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

from __future__ import annotations

import re
from collections.abc import Sequence
from itertools import permutations
from os.path import sep
from pathlib import Path
from textwrap import dedent

import click
import pytest
from boltons.iterutils import flatten, unique
from boltons.strutils import strip_ansi
from extra_platforms import is_windows

from click_extra import (
    BOOL,
    FLOAT,
    INT,
    STRING,
    UNPROCESSED,
    UNSET,
    UUID,
    Choice,
    ConfigFormat,
    DateTime,
    File,
    FloatRange,
    IntRange,
    ParamType,
    ShowParamsOption,
    TableFormat,
    Tuple,
    argument,
    color_option,
    command,
    echo,
    get_app_dir,
    group,
    option,
    render_table,
    search_params,
    show_params_option,
    table_format_option,
)
from click_extra.pytest import command_decorators

from .test_colorize import HashType


[docs] class Custom(ParamType): """A dummy custom type.""" name = "Custom"
[docs] def convert(self, value, param, ctx): return value
[docs] @pytest.mark.parametrize("option_decorator", (show_params_option, show_params_option())) def test_params_auto_types(invoke, option_decorator): """Check parameters types and structure are properly derived from CLI.""" @click.command @option("--flag1/--no-flag1") @option("--flag2", is_flag=True) @option("--str-param1", type=str) @option("--str-param2", type=STRING) @option("--int-param1", type=int) @option("--int-param2", type=INT) @option("--float-param1", type=float) @option("--float-param2", type=FLOAT) @option("--bool-param1", type=bool) @option("--bool-param2", type=BOOL) @option("--uuid-param", type=UUID) @option("--unprocessed-param", type=UNPROCESSED) @option("--file-param", type=File()) @option("--path-param", type=click.Path()) @option("--choice-param", type=Choice(("a", "b", "c"))) @option("--number-choice", type=Choice([1, 2, 3])) @option("--hash-type", type=Choice(HashType)) @option("--int-range-param", type=IntRange()) @option("--count-param", count=True) # See issue #170. @option("--float-range-param", type=FloatRange()) @option("--datetime-param", type=DateTime()) @option("--custom-param", type=Custom()) # See issue #721. @option("--tuple1", nargs=2, type=Tuple([str, int])) @option("--list1", multiple=True) @option("--hidden-param", hidden=True) # See issue #689. @argument("file_arg1", type=File("w")) @argument("file_arg2", type=File("w"), nargs=-1) @option_decorator def params_introspection( flag1, flag2, str_param1, str_param2, int_param1, int_param2, float_param1, float_param2, bool_param1, bool_param2, uuid_param, unprocessed_param, file_param, path_param, choice_param, number_choice, hash_type, int_range_param, count_param, float_range_param, datetime_param, custom_param, tuple1, list1, hidden_param, file_arg1, file_arg2, ): echo("Works!") # Invoke the --show-params option to trigger the introspection. result = invoke( params_introspection, "--show-params", "random_file1", "random_file2", color=False, ) assert result.stdout != "Works!\n" assert result.exit_code == 0 show_param_option = search_params(params_introspection.params, ShowParamsOption) assert show_param_option.params_template == { "params-introspection": { "flag1": None, "flag2": None, "str_param1": None, "str_param2": None, "int_param1": None, "int_param2": None, "float_param1": None, "float_param2": None, "bool_param1": None, "bool_param2": None, "uuid_param": None, "unprocessed_param": None, "file_param": None, "path_param": None, "show_params": None, "choice_param": None, "number_choice": None, "hash_type": None, "int_range_param": None, "count_param": None, "float_range_param": None, "datetime_param": None, "custom_param": None, "tuple1": None, "list1": None, "hidden_param": None, "file_arg1": None, "file_arg2": None, "help": None, }, }
[docs] def assert_table_content( output: str, expected_table: Sequence[Sequence[str]], table_format: TableFormat | None = None, ) -> None: """Helper to assert the content of a rendered table in the output.""" # Crudly parse the rendered table from the output, as if produced with the # default style. extracted_table = [] for line in output.strip().splitlines()[3:-1]: columns = [col.strip() for col in re.split(r"\s*\β”‚\s*", line[1:-1])] assert len(columns) == len(ShowParamsOption.TABLE_HEADERS) extracted_table.append(tuple(columns)) # Compare tables row by row to get cleaner assertion errors. for index in range(len(expected_table)): expected_strings = tuple(map(str, expected_table[index])) assert len(expected_strings) == len(ShowParamsOption.TABLE_HEADERS) assert extracted_table[index] == expected_strings # Check the rendering style of the table. rendered_table = render_table( expected_table, headers=ShowParamsOption.TABLE_HEADERS, table_format=table_format, ) assert output == f"{rendered_table}\n"
# Skip click extra's commands, as show_params option is already part of the default.
[docs] @pytest.mark.parametrize("cmd_decorator", command_decorators(no_extra=True)) @pytest.mark.parametrize("option_decorator", (show_params_option, show_params_option())) def test_standalone_show_params_option( invoke, cmd_decorator, option_decorator, assert_output_regex ): @cmd_decorator @option_decorator def show_params(): echo("It works!") result = invoke(show_params, "--show-params") expected_table = [ ( "show-params.help", "--help", "click.core.Option", "click.types.BoolParamType", "bool", "✘", "✘", "", "", False, "None", "", ), ( "show-params.show_params", "--show-params", "click_extra.parameters.ShowParamsOption", "click.types.BoolParamType", "bool", "✘", "✘", "", "", False, "None", "COMMANDLINE", ), ] assert_table_content(result.stdout, expected_table) if result.stderr: assert_output_regex( result.stderr, r"warning: Cannot extract parameters values: " r"<(Group|Command) show-params> does not inherits from ExtraCommand\.\n", ) assert result.exit_code == 0
[docs] def test_integrated_show_params_option(invoke, create_config): @command @option("--int-param1", type=int, default=10) @option("--int-param2", type=int, default=555) @option("--hidden-param", hidden=True) # See issue #689. @option("--custom-param", type=Custom()) # See issue #721. def show_params_cli(int_param1, int_param2, hidden_param, custom_param): echo(f"int_param1 is {int_param1!r}") echo(f"int_param2 is {int_param2!r}") echo(f"hidden_param is {hidden_param!r}") echo(f"custom_param is {custom_param!r}") conf_file = dedent( """ [show-params-cli] int_param1 = 3 extra_value = "unallowed" """, ) conf_path = create_config("show-params-cli.toml", conf_file) raw_args = [ "--verbosity", "DeBuG", "--config", str(conf_path), "--int-param1", "9999", "--show-params", "--help", ] result = invoke(show_params_cli, *raw_args, color=False) expected_table = [ ( "show-params-cli.color", "--color, --ansi / --no-color, --no-ansi", "click_extra.colorize.ColorOption", "click.types.BoolParamType", "bool", "✘", "✘", "βœ“", "SHOW_PARAMS_CLI_COLOR", True, True, "DEFAULT", ), ( "show-params-cli.config", "--config CONFIG_PATH", "click_extra.config.ConfigOption", "click.types.UnprocessedParamType", "str", "✘", "✘", "✘", "SHOW_PARAMS_CLI_CONFIG", # On Windows, backslashes are double-escaped in Path string repr. ( f"'{Path(get_app_dir('show-params-cli')).resolve()}{sep}" f"{{{','.join(unique(flatten(f.patterns for f in ConfigFormat if f.enabled)))}}}'" ).replace("\\", "\\\\") if is_windows else ( f"'{Path(get_app_dir('show-params-cli')).resolve()}{sep}" f"{{{','.join(unique(flatten(f.patterns for f in ConfigFormat if f.enabled)))}}}'" ), repr(str(conf_path)), "COMMANDLINE", ), ( "show-params-cli.config", "--no-config", "click_extra.config.NoConfigOption", "click.types.UnprocessedParamType", "str", "✘", "✘", "✘", "SHOW_PARAMS_CLI_CONFIG", UNSET, repr(str(conf_path)), "COMMANDLINE", ), ( "show-params-cli.custom_param", "--custom-param CUSTOM", "click_extra.parameters.Option", "tests.test_parameters.Custom", "str", "✘", "βœ“", "βœ“", "SHOW_PARAMS_CLI_CUSTOM_PARAM", UNSET, UNSET, "DEFAULT", ), ( "show-params-cli.help", "-h, --help", "click.core.Option", "click.types.BoolParamType", "bool", "✘", "✘", "✘", "SHOW_PARAMS_CLI_HELP", False, True, "COMMANDLINE", ), ( "show-params-cli.hidden_param", "--hidden-param TEXT", "click_extra.parameters.Option", "click.types.StringParamType", "str", "βœ“", "βœ“", "βœ“", "SHOW_PARAMS_CLI_HIDDEN_PARAM", UNSET, UNSET, "DEFAULT", ), ( "show-params-cli.int_param1", "--int-param1 INTEGER", "click_extra.parameters.Option", "click.types.IntParamType", "int", "✘", "βœ“", "βœ“", "SHOW_PARAMS_CLI_INT_PARAM1", 3, "'9999'", "COMMANDLINE", ), ( "show-params-cli.int_param2", "--int-param2 INTEGER", "click_extra.parameters.Option", "click.types.IntParamType", "int", "✘", "βœ“", "βœ“", "SHOW_PARAMS_CLI_INT_PARAM2", 555, 555, "DEFAULT", ), ( "show-params-cli.show_params", "--show-params", "click_extra.parameters.ShowParamsOption", "click.types.BoolParamType", "bool", "✘", "✘", "✘", "SHOW_PARAMS_CLI_SHOW_PARAMS", False, True, "COMMANDLINE", ), ( "show-params-cli.table_format", "--table-format [aligned|asciidoc|colon-grid|csv|csv-excel|csv-excel-tab|csv-unix|double-grid|double-outline|fancy-grid|fancy-outline|github|grid|heavy-grid|heavy-outline|hjson|html|jira|json|json5|jsonc|latex|latex-booktabs|latex-longtable|latex-raw|mediawiki|mixed-grid|mixed-outline|moinmoin|orgtbl|outline|pipe|plain|presto|pretty|psql|rounded-grid|rounded-outline|rst|simple|simple-grid|simple-outline|textile|toml|tsv|unsafehtml|vertical|xml|yaml|youtrack]", "click_extra.table.TableFormatOption", "click_extra.types.EnumChoice", "str", "✘", "✘", "βœ“", "SHOW_PARAMS_CLI_TABLE_FORMAT", "'rounded-outline'", "'rounded-outline'", "DEFAULT", ), ( "show-params-cli.time", "--time / --no-time", "click_extra.timer.TimerOption", "click.types.BoolParamType", "bool", "✘", "✘", "βœ“", "SHOW_PARAMS_CLI_TIME", False, False, "DEFAULT", ), ( "show-params-cli.validate_config", "--validate-config FILE", "click_extra.config.ValidateConfigOption", "click.types.Path", "str", "✘", "✘", "βœ“", "SHOW_PARAMS_CLI_VALIDATE_CONFIG", UNSET, UNSET, "DEFAULT", ), ( "show-params-cli.verbose", "-v, --verbose", "click_extra.logging.VerboseOption", "click.types.IntRange", "int", "✘", "✘", "βœ“", "SHOW_PARAMS_CLI_VERBOSE", 0, 0, "DEFAULT", ), ( "show-params-cli.verbosity", "--verbosity LEVEL", "click_extra.logging.VerbosityOption", "click_extra.types.EnumChoice", "str", "✘", "✘", "βœ“", "SHOW_PARAMS_CLI_VERBOSITY", "'WARNING'", "'DeBuG'", "COMMANDLINE", ), ( "show-params-cli.version", "--version", "click_extra.version.ExtraVersionOption", "click.types.BoolParamType", "bool", "✘", "✘", "✘", "SHOW_PARAMS_CLI_VERSION", False, False, "DEFAULT", ), ] assert_table_content(result.stdout, expected_table) assert f"debug: click_extra.raw_args: {raw_args!r}\n" in result.stderr assert result.exit_code == 0
[docs] @pytest.mark.parametrize( "args_order", permutations(("--show-params", "--table-format=csv", "--no-color")), ids=lambda p: " ".join(p), ) def test_show_params_table_format_ordering(invoke, args_order): """``--show-params`` respects ``--table-format`` regardless of CLI order.""" @command @option("--name", default="world") def ordering_cli(name): echo(f"Hello, {name}!") result = invoke(ordering_cli, *args_order, color=False) assert result.exit_code == 0 # CSV format: first line is the header row, comma-separated. lines = result.stdout.strip().splitlines() assert lines[0] == ",".join(ShowParamsOption.TABLE_HEADERS) # All data rows must have the same number of columns as the header. for line in lines[1:]: assert line.count(",") >= len(ShowParamsOption.TABLE_HEADERS) - 1
[docs] @pytest.mark.parametrize( "table_format", ("json", "yaml", "toml", "hjson"), ) def test_show_params_native_types(invoke, table_format): """Serialization formats emit native types instead of styled glyphs.""" @command @option("--flag/--no-flag", default=True) def typed_cli(flag): echo(f"flag is {flag!r}") result = invoke( typed_cli, "--show-params", f"--table-format={table_format}", color=False, ) assert result.exit_code == 0 output = result.stdout # Glyphs must not appear in structured output. assert "βœ“" not in output assert "✘" not in output # Boolean values must be native, not stringified repr(). assert "'True'" not in output assert "'False'" not in output # Check format-specific native boolean representations. if table_format == "json": assert '"Hidden": false' in output assert '"Exposed": false' in output elif table_format in ("yaml", "hjson"): assert "Hidden: false" in output assert "Exposed: false" in output elif table_format == "toml": assert "Hidden = false" in output assert "Exposed = false" in output
[docs] def test_recurse_subcommands(invoke): @group(params=[ShowParamsOption()]) def show_params_cli_main(): echo("main cmd") @show_params_cli_main.group(params=[]) def show_params_sub_cmd(): echo("subcommand") @show_params_sub_cmd.command() @option("--int-param", type=int, default=10) def show_params_sub_sub_cmd(int_param): echo(f"subsubcommand int_param is {int_param!r}") result = invoke(show_params_cli_main, "--show-params", color=False) expected_table = [ ( "show-params-cli-main.help", "-h, --help", "click.core.Option", "click.types.BoolParamType", "bool", "✘", "✘", "", "SHOW_PARAMS_CLI_MAIN_HELP", False, False, "DEFAULT", ), ( "show-params-cli-main.show_params", "--show-params", "click_extra.parameters.ShowParamsOption", "click.types.BoolParamType", "bool", "✘", "✘", "", "SHOW_PARAMS_CLI_MAIN_SHOW_PARAMS", False, True, "COMMANDLINE", ), ( "show-params-cli-main.show-params-sub.help", "-h, --help", "click.core.Option", "click.types.BoolParamType", "bool", "✘", "✘", "", "SHOW_PARAMS_CLI_MAIN_HELP", # XXX Should be SHOW_PARAMS_SUB_HELP False, False, "DEFAULT", ), ( "show-params-cli-main.show-params-sub.show-params-sub-sub.help", "-h, --help", "click.core.Option", "click.types.BoolParamType", "bool", "✘", "✘", "", "SHOW_PARAMS_CLI_MAIN_HELP", # XXX Should be SHOW_PARAMS_SUB_SUB_HELP False, False, "DEFAULT", ), ( "show-params-cli-main.show-params-sub.show-params-sub-sub.int_param", "--int-param INTEGER", "click_extra.parameters.Option", "click.types.IntParamType", "int", "✘", "βœ“", "", "SHOW_PARAMS_SUB_SUB_INT_PARAM, SHOW_PARAMS_CLI_MAIN_INT_PARAM", 10, 10, "DEFAULT", ), ] assert_table_content(result.stdout, expected_table) assert result.exit_code == 0
[docs] def test_subcommand_conflicts_with_parent_param(invoke): """A subcommand whose name matches its direct parent's param is a real conflict. In a config file, the key would be ambiguous: is it the param value or the subcommand section? .. code-block:: toml [root.alpha] # "foo" is ambiguous: is it the --foo param or the [root.alpha.foo] subcommand? foo = ??? See: https://github.com/kdeldycke/click-extra/pull/1286 """ @click.group(params=[ShowParamsOption()]) def root(): pass @root.group() @option("--foo") def alpha(foo): pass @alpha.command("foo") def alpha_foo_cmd(): """Subcommand named 'foo', same as alpha's --foo param.""" result = invoke(root, "--show-params", color=False) assert result.exit_code == 1 assert "alpha.foo subcommand conflicts with" in str(result.exception)
[docs] def test_nested_subcommand_no_false_conflict_with_root_param(invoke): """A nested subcommand can share a name with a root-level param without conflict. The config paths are distinct (``root.verbose`` vs ``root.alpha.verbose``), so there is no ambiguity. See: https://github.com/kdeldycke/click-extra/pull/1286 """ @click.group(params=[ShowParamsOption()]) @option("--verbose", is_flag=True) def root(verbose): pass @root.group() def alpha(): pass @alpha.command("verbose") def alpha_verbose_cmd(): """Subcommand named 'verbose', same as root's --verbose param.""" result = invoke(root, "--show-params", color=False) assert result.exit_code == 0
# Shuffle the order of declaration to ensure behavior stability.
[docs] @pytest.mark.parametrize( ("opt1", "opt2"), permutations((show_params_option, table_format_option)), ) @pytest.mark.parametrize("table_format", TableFormat) def test_standalone_table_rendering(invoke, opt1, opt2, table_format): """Check all rendering styles of the table with standalone ``--show-params`` and ``--table-format`` option. """ @click.command @opt1 @opt2 def show_params(): echo("It works!") expected_table = [ [ "show-params.help", "--help", "click.core.Option", "click.types.BoolParamType", "bool", "✘", "✘", "", "", False, "None", "", ], [ "show-params.show_params", "--show-params", "click_extra.parameters.ShowParamsOption", "click.types.BoolParamType", "bool", "✘", "✘", "", "", False, "None", "COMMANDLINE", ], [ "show-params.table_format", "--table-format [aligned|asciidoc|colon-grid|csv|csv-excel|csv-excel-tab|csv-unix|double-grid|double-outline|fancy-grid|fancy-outline|github|grid|heavy-grid|heavy-outline|hjson|html|jira|json|json5|jsonc|latex|latex-booktabs|latex-longtable|latex-raw|mediawiki|mixed-grid|mixed-outline|moinmoin|orgtbl|outline|pipe|plain|presto|pretty|psql|rounded-grid|rounded-outline|rst|simple|simple-grid|simple-outline|textile|toml|tsv|unsafehtml|vertical|xml|yaml|youtrack]", "click_extra.table.TableFormatOption", "click_extra.types.EnumChoice", "str", "✘", "✘", "", "", "'rounded-outline'", "None", "", ], ] # Check the default rendering style. for args in ( # There is no impact with just the presence of @table_format_option decorator. ("--show-params",), # Both options are eager, so passing --table-format after --show-params makes # it too late to have an effect. ("--show-params", "--table-format", table_format), ): result = invoke(show_params, args) assert_table_content(result.stdout, expected_table) assert result.exit_code == 0 # --table-format is explicitly set from now on, so its source is COMMANDLINE. expected_table[2][11] = "COMMANDLINE" # Serialization formats emit native types instead of styled glyphs. from click_extra.table import SERIALIZATION_FORMATS if table_format in SERIALIZATION_FORMATS: for row in expected_table: for i, cell in enumerate(row): if cell == "✘": row[i] = False elif cell == "βœ“": row[i] = True if row[5] == "": row[5] = None if row[7] == "": row[7] = None row[8] = [row[8]] if row[8] else [] if ( isinstance(row[9], str) and row[9].startswith("'") and row[9].endswith("'") ): row[9] = row[9][1:-1] if row[10] == "None": row[10] = None if row[11] == "": row[11] = None # Check the explicit rendering style of the table. Ignore colors, they'll be # checked in the next test. result = invoke( show_params, "--table-format", table_format, "--show-params", color=False ) rendered = render_table( expected_table, headers=ShowParamsOption.TABLE_HEADERS, table_format=table_format, ) rendered_table = rendered if rendered.endswith("\n") else rendered + "\n" # Compare content line by line to simplify reporting of differences. output_lines = result.stdout.strip().splitlines() expected_lines = rendered_table.strip().splitlines() assert len(output_lines) == len(expected_lines) for index in range(len(expected_lines)): assert output_lines[index] == expected_lines[index] # XXX Click.testing.Result always uses \n line endings, even on Windows. # "\r\n" are replaced by "\n" since the beginning of time: # https://github.com/pallets/click/commit/7360097ec25e89730f46b840aa050467c5a80e9e#diff-e52e4ddd58b7ef887ab03c04116e676f6280b824ab7469d5d3080e5cba4f2128R120 # So we can't test the CSV dialects properly. if table_format not in ( TableFormat.CSV, TableFormat.CSV_EXCEL, TableFormat.CSV_EXCEL_TAB, TableFormat.CSV_UNIX, ): assert result.stdout == rendered_table assert result.exit_code == 0
# Shuffle the order of declaration to ensure behavior stability.
[docs] @pytest.mark.parametrize( ("opt1", "opt2", "opt3"), permutations((show_params_option, table_format_option, color_option)), ) @pytest.mark.parametrize("table_format", TableFormat) def test_standalone_no_color_rendering(invoke, opt1, opt2, opt3, table_format): """Check that all rendering styles are responding to the ``--color``/``--no-color`` option. """ @click.command @opt1 @opt2 @opt3 def show_params(): echo("It works!") expected_table = [ [ "show-params.color", "--color, --ansi / --no-color, --no-ansi", "click_extra.colorize.ColorOption", "click.types.BoolParamType", "bool", "✘", "✘", "", "", True, "None", "", ], [ "show-params.help", "--help", "click.core.Option", "click.types.BoolParamType", "bool", "✘", "✘", "", "", False, "None", "", ], [ "show-params.show_params", "--show-params", "click_extra.parameters.ShowParamsOption", "click.types.BoolParamType", "bool", "✘", "✘", "", "", False, "None", "COMMANDLINE", ], [ "show-params.table_format", "--table-format [aligned|asciidoc|colon-grid|csv|csv-excel|csv-excel-tab|csv-unix|double-grid|double-outline|fancy-grid|fancy-outline|github|grid|heavy-grid|heavy-outline|hjson|html|jira|json|json5|jsonc|latex|latex-booktabs|latex-longtable|latex-raw|mediawiki|mixed-grid|mixed-outline|moinmoin|orgtbl|outline|pipe|plain|presto|pretty|psql|rounded-grid|rounded-outline|rst|simple|simple-grid|simple-outline|textile|toml|tsv|unsafehtml|vertical|xml|yaml|youtrack]", "click_extra.table.TableFormatOption", "click_extra.types.EnumChoice", "str", "✘", "✘", "", "", "'rounded-outline'", "None", "", ], ] # Check the default rendering style. for args in ( # There is no impact with just the presence of @color_option decorator. ("--show-params",), # Both options are eager, so passing --color/--no-color after --show-params # makes it too late to have an effect. ("--show-params", "--color"), ("--show-params", "--no-color"), ): result = invoke(show_params, args) assert_table_content(result.stdout, expected_table) assert result.exit_code == 0 # --color/--no-color is explicitly set from now on, so its source is COMMANDLINE. expected_table[0][11] = "COMMANDLINE" # Force --color. result = invoke(show_params, "--color", "--show-params") # --color is forced, so the table is colorized and doesn't match expected_table, unless we # strip all ANSI escape sequences. with pytest.raises(AssertionError): assert_table_content(result.stdout, expected_table) assert_table_content(strip_ansi(result.stdout), expected_table) assert result.exit_code == 0 # Force --no-color. result = invoke(show_params, "--no-color", "--show-params") assert_table_content(result.stdout, expected_table) assert result.exit_code == 0 # --table-format is explicitly set from now on, so its source is COMMANDLINE. expected_table[3][11] = "COMMANDLINE" # Serialization formats emit native types instead of styled glyphs. from click_extra.table import SERIALIZATION_FORMATS if table_format in SERIALIZATION_FORMATS: for row in expected_table: # Replace glyph strings with native booleans. for i, cell in enumerate(row): if cell == "✘": row[i] = False elif cell == "βœ“": row[i] = True # Hidden: empty string means None (Arguments have no hidden attr). if row[5] == "": row[5] = None # Allowed in conf?: empty string means no config option (None). if row[7] == "": row[7] = None # Env. vars. become a list (index 8). row[8] = [row[8]] if row[8] else [] # Default value: strip repr quotes (index 9). if ( isinstance(row[9], str) and row[9].startswith("'") and row[9].endswith("'") ): row[9] = row[9][1:-1] # Value: "None" string becomes None (index 10). if row[10] == "None": row[10] = None # Source: empty string means None. if row[11] == "": row[11] = None # Check the explicit rendering style of the table. result = invoke( show_params, "--no-color", "--table-format", table_format, "--show-params" ) rendered = render_table( expected_table, headers=ShowParamsOption.TABLE_HEADERS, table_format=table_format, ) # Tabulate-based formats don't end with a newline; echo() adds one. # Serialization/CSV/vertical formats already include a trailing newline. rendered_table = rendered if rendered.endswith("\n") else rendered + "\n" # Compare content line by line to simplify reporting of differences. output_lines = result.stdout.strip().splitlines() expected_lines = rendered_table.strip().splitlines() assert len(output_lines) == len(expected_lines) for index in range(len(expected_lines)): line = output_lines[index] # XXX CSV dialects are not rendered with echo but with print and are not # sensitive to Click colorization settings. if table_format in ( TableFormat.CSV, TableFormat.CSV_EXCEL, TableFormat.CSV_EXCEL_TAB, TableFormat.CSV_UNIX, ): line = strip_ansi(line) assert line == expected_lines[index] # XXX Click.testing.Result always uses \n line endings, even on Windows. # "\r\n" are replaced by "\n" since the beginning of time: # https://github.com/pallets/click/commit/7360097ec25e89730f46b840aa050467c5a80e9e#diff-e52e4ddd58b7ef887ab03c04116e676f6280b824ab7469d5d3080e5cba4f2128R120 # So we can't test the CSV dialects properly. if table_format not in ( TableFormat.CSV, TableFormat.CSV_EXCEL, TableFormat.CSV_EXCEL_TAB, TableFormat.CSV_UNIX, ): assert result.stdout == rendered_table assert result.exit_code == 0