Repository: pallets/click
Pull request #807 (2017-06-03 02:59:50)

Escape Sequences with r''
- Add raw strings to files that have the need so correct escaping occurs


Patch:
diff --git a/.coveragerc b/.coveragerc
new file mode 100644
index 0000000..2876fb5
--- /dev/null
+++ b/.coveragerc
@@ -0,0 +1,3 @@
+[run]
+branch = true
+source = click,tests
diff --git a/.travis-colorama-requirements.txt b/.travis-colorama-requirements.txt
deleted file mode 100644
index 3fcfb51..0000000
--- a/.travis-colorama-requirements.txt
+++ /dev/null
@@ -1,1 +0,0 @@
-colorama
diff --git a/.travis-default-requirements.txt b/.travis-default-requirements.txt
deleted file mode 100644
index e69de29..0000000
diff --git a/.travis.yml b/.travis.yml
index 8e976dc..a592c8f 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,3 +1,4 @@
+sudo: false
 language: python
 python:
   - "2.6"
@@ -9,14 +10,26 @@ python:
   - "pypy"
 
 env:
-  - REQUIREMENTS=colorama
-  - REQUIREMENTS=default
+  - TEST_EXTRA=
+  - TEST_EXTRA=colorama
 
 install:
-  - pip install -r .travis-$REQUIREMENTS-requirements.txt
-  - pip install --editable .
+  - pip install tox
 
-script: make test
+script:
+  - |
+    set -ex
+    if [[ $TRAVIS_PYTHON_VERSION == pypy ]]; then
+      TOX_PY=pypy
+    else
+      TOX_PY="py${TRAVIS_PYTHON_VERSION/./}"
+    fi
+    export TOXENV="${TOX_PY}-coverage"
+    if [[ $TEST_EXTRA == colorama ]]; then
+      TOXENV="$TOXENV-colorama"
+    fi
+    tox
+    set +x
 
 branches:
   only:
@@ -26,3 +39,7 @@ branches:
 
 notifications:
   email: false
+
+after_success:
+  - pip install codecov
+  - travis_retry codecov --env TOXENV -X fix search gcov --required --flags $TOX_PY $TEST_EXTRA
diff --git a/CHANGES b/CHANGES
index c3928da..5e8c57c 100644
--- a/CHANGES
+++ b/CHANGES
@@ -23,8 +23,14 @@ Version 7.0
 - ``launch`` now works properly under Cygwin. See #650.
 - `CliRunner.invoke` now may receive `args` as a string representing
   a Unix shell command. See #664.
-- Fix bug that caused bashcompletion to give inproper completions on 
+- Fix bug that caused bashcompletion to give improper completions on 
   chained commands. See #774.
+- 't' and 'f' are now converted to True and False. 
+- Fix bug that caused bashcompletion to give improper completions on
+  chained commands when a required option/argument was being completed.
+  See #790.
+- Allow autocompletion function to determine whether or not to return
+  completions that start with the incomplete argument.
 
 Version 6.8
 -----------
@@ -37,6 +43,9 @@ Version 6.8
 - Fix crash on Windows console, see #744.
 - Fix bashcompletion on chained commands. See #754.
 - Fix option naming routine to match documentation.  See #793
+- Fixed the behavior of click error messages with regards to unicode on 2.x
+  and 3.x respectively.  Message is now always unicode and the str and unicode
+  special methods work as you expect on that platform.
 
 Version 6.7
 -----------
diff --git a/Makefile b/Makefile
index 6927e4c..971e401 100644
--- a/Makefile
+++ b/Makefile
@@ -1,5 +1,5 @@
 test:
-	@cd tests; PYTHONPATH=.. py.test --tb=short
+	@cd tests; PYTHONPATH=.. pytest --tb=short
 
 upload-docs:
 	$(MAKE) -C docs dirhtml
diff --git a/click/_bashcomplete.py b/click/_bashcomplete.py
index 536b5d7..e0e7395 100644
--- a/click/_bashcomplete.py
+++ b/click/_bashcomplete.py
@@ -45,17 +45,19 @@ def resolve_ctx(cli, prog_name, args):
     ctx = cli.make_context(prog_name, args, resilient_parsing=True)
     args_remaining = ctx.protected_args + ctx.args
     while ctx is not None and args_remaining:
-      if isinstance(ctx.command, MultiCommand):
-        cmd = ctx.command.get_command(ctx, args_remaining[0])
-        if cmd is None:
-            return None
-        ctx = cmd.make_context(args_remaining[0], args_remaining[1:], parent=ctx, resilient_parsing=True)
-        args_remaining = ctx.protected_args + ctx.args
-      else:
-        ctx = ctx.parent
+        if isinstance(ctx.command, MultiCommand):
+            cmd = ctx.command.get_command(ctx, args_remaining[0])
+            if cmd is None:
+                return None
+            ctx = cmd.make_context(
+                args_remaining[0], args_remaining[1:], parent=ctx, resilient_parsing=True)
+            args_remaining = ctx.protected_args + ctx.args
+        else:
+            ctx = ctx.parent
 
     return ctx
 
+
 def start_of_option(param_str):
     """
     :param param_str: param_str to check
@@ -72,6 +74,8 @@ def is_incomplete_option(all_args, cmd_param):
     corresponds to this cmd_param. In other words whether this cmd_param option can still accept
     values
     """
+    if not isinstance(cmd_param, Option):
+        return False
     if cmd_param.is_flag:
         return False
     last_option = None
@@ -91,6 +95,8 @@ def is_incomplete_argument(current_params, cmd_param):
     :return: whether or not the last argument is incomplete and corresponds to this cmd_param. In
     other words whether or not the this cmd_param argument can still accept values
     """
+    if not isinstance(cmd_param, Argument):
+        return False
     current_param_values = current_params[cmd_param.name]
     if current_param_values is None:
         return True
@@ -101,6 +107,7 @@ def is_incomplete_argument(current_params, cmd_param):
         return True
     return False
 
+
 def get_user_autocompletions(ctx, args, incomplete, cmd_param):
     """
     :param ctx: context associated with the parsed command
@@ -110,7 +117,7 @@ def get_user_autocompletions(ctx, args, incomplete, cmd_param):
     :return: all the possible user-specified completions for the param
     """
     if isinstance(cmd_param.type, Choice):
-        return cmd_param.type.choices
+        return [c for c in cmd_param.type.choices if c.startswith(incomplete)]
     elif cmd_param.autocompletion is not None:
         return cmd_param.autocompletion(ctx=ctx,
                                         args=args,
@@ -118,6 +125,23 @@ def get_user_autocompletions(ctx, args, incomplete, cmd_param):
     else:
         return []
 
+
+def add_subcommand_completions(ctx, incomplete, completions_out):
+    # Add subcommand completions.
+    if isinstance(ctx.command, MultiCommand):
+        completions_out.extend(
+            [c for c in ctx.command.list_commands(ctx) if c.startswith(incomplete)])
+
+    # Walk up the context list and add any other completion possibilities from chained commands
+    while ctx.parent is not None:
+        ctx = ctx.parent
+        if isinstance(ctx.command, MultiCommand) and ctx.command.chain:
+            remaining_commands = sorted(
+                set(ctx.command.list_commands(ctx)) - set(ctx.protected_args))
+            completions_out.extend(
+                [c for c in remaining_commands if c.startswith(incomplete)])
+
+
 def get_choices(cli, prog_name, args, incomplete):
     """
     :param cli: command definition
@@ -130,7 +154,7 @@ def get_choices(cli, prog_name, args, incomplete):
 
     ctx = resolve_ctx(cli, prog_name, args)
     if ctx is None:
-        return
+        return []
 
     # In newer versions of bash long opts with '='s are partitioned, but it's easier to parse
     # without the '='
@@ -141,42 +165,32 @@ def get_choices(cli, prog_name, args, incomplete):
     elif incomplete == WORDBREAK:
         incomplete = ''
 
-    choices = []
-    found_param = False
+    completions = []
     if start_of_option(incomplete):
-        # completions for options
+        # completions for partial options
         for param in ctx.command.params:
             if isinstance(param, Option):
-                choices.extend([param_opt for param_opt in param.opts + param.secondary_opts
-                                if param_opt not in all_args or param.multiple])
-        found_param = True
-    if not found_param:
-        # completion for option values by choices
-        for cmd_param in ctx.command.params:
-            if isinstance(cmd_param, Option) and is_incomplete_option(all_args, cmd_param):
-                choices.extend(get_user_autocompletions(ctx, all_args, incomplete, cmd_param))
-                found_param = True
-                break
-    if not found_param:
-        # completion for argument values by choices
-        for cmd_param in ctx.command.params:
-            if isinstance(cmd_param, Argument) and is_incomplete_argument(ctx.params, cmd_param):
-                choices.extend(get_user_autocompletions(ctx, all_args, incomplete, cmd_param))
-                found_param = True
-                break
-
-    if not found_param and isinstance(ctx.command, MultiCommand):
-        # completion for any subcommands
-        choices.extend(ctx.command.list_commands(ctx))
-
-    if not start_of_option(incomplete) and ctx.parent is not None and isinstance(ctx.parent.command, MultiCommand) and ctx.parent.command.chain:
-        # completion for chained commands
-        remaining_comands = set(ctx.parent.command.list_commands(ctx.parent))-set(ctx.parent.protected_args)
-        choices.extend(remaining_comands)
-
-    for item in choices:
-        if item.startswith(incomplete):
-            yield item
+                param_opts = [param_opt for param_opt in param.opts +
+                              param.secondary_opts if param_opt not in all_args or param.multiple]
+                completions.extend(
+                    [c for c in param_opts if c.startswith(incomplete)])
+        return completions
+    # completion for option values from user supplied values
+    for param in ctx.command.params:
+        if is_incomplete_option(all_args, param):
+            return get_user_autocompletions(ctx, all_args, incomplete, param)
+    # completion for argument values from user supplied values
+    for param in ctx.command.params:
+        if is_incomplete_argument(ctx.params, param):
+            completions.extend(get_user_autocompletions(
+                ctx, all_args, incomplete, param))
+            # Stop looking for other completions only if this argument is required.
+            if param.required:
+                return completions
+            break
+
+    add_subcommand_completions(ctx, incomplete, completions)
+    return completions
 
 
 def do_complete(cli, prog_name):
diff --git a/click/_compat.py b/click/_compat.py
index 312e729..d072224 100644
--- a/click/_compat.py
+++ b/click/_compat.py
@@ -12,7 +12,7 @@ CYGWIN = sys.platform.startswith('cygwin')
 DEFAULT_COLUMNS = 80
 
 
-_ansi_re = re.compile('\033\[((?:\d|;)*)([a-zA-Z])')
+_ansi_re = re.compile(r'\033\[((?:\d|;)*)([a-zA-Z])')
 
 
 def get_filesystem_encoding():
diff --git a/click/_unicodefun.py b/click/_unicodefun.py
index 998b2cb..e09e18b 100644
--- a/click/_unicodefun.py
+++ b/click/_unicodefun.py
@@ -116,5 +116,5 @@ def _verify_python3_env():
 
     raise RuntimeError('Click will abort further execution because Python 3 '
                        'was configured to use ASCII as encoding for the '
-                       'environment.  Consult http://click.pocoo.org/python3/'
+                       'environment.  Consult http://click.pocoo.org/python3/ '
                        'for mitigation steps.' + extra)
diff --git a/click/core.py b/click/core.py
index b307407..8358172 100644
--- a/click/core.py
+++ b/click/core.py
@@ -1230,7 +1230,7 @@ class CommandCollection(MultiCommand):
 
 
 class Parameter(object):
-    """A parameter to a command comes in two versions: they are either
+    r"""A parameter to a command comes in two versions: they are either
     :class:`Option`\s or :class:`Argument`\s.  Other subclasses are currently
     not supported by design as some of the internals for parsing are
     intentionally not finalized.
diff --git a/click/decorators.py b/click/decorators.py
index 64af015..c0b9e85 100644
--- a/click/decorators.py
+++ b/click/decorators.py
@@ -90,7 +90,7 @@ def _make_command(f, name, attrs, cls):
 
 
 def command(name=None, cls=None, **attrs):
-    """Creates a new :class:`Command` and uses the decorated function as
+    r"""Creates a new :class:`Command` and uses the decorated function as
     callback.  This will also automatically attach all decorated
     :func:`option`\s and :func:`argument`\s as parameters to the command.
 
diff --git a/click/exceptions.py b/click/exceptions.py
index 6f7a536..b04b5d0 100644
--- a/click/exceptions.py
+++ b/click/exceptions.py
@@ -19,11 +19,14 @@ class ClickException(Exception):
     def format_message(self):
         return self.message
 
-    def __unicode__(self):
+    def __str__(self):
         return self.message
 
-    def __str__(self):
-        return self.message.encode('utf-8')
+    if PY2:
+        __unicode__ = __str__
+
+        def __str__(self):
+            return self.message.encode('utf-8')
 
     def show(self, file=None):
         if file is None:
diff --git a/click/globals.py b/click/globals.py
index 14338e6..843b594 100644
--- a/click/globals.py
+++ b/click/globals.py
@@ -9,7 +9,7 @@ def get_current_context(silent=False):
     access the current context object from anywhere.  This is a more implicit
     alternative to the :func:`pass_context` decorator.  This function is
     primarily useful for helpers such as :func:`echo` which might be
-    interested in changing it's behavior based on the current context.
+    interested in changing its behavior based on the current context.
 
     To push the current context, :meth:`Context.scope` can be used.
 
diff --git a/click/types.py b/click/types.py
index 3eb5fac..fab63e4 100644
--- a/click/types.py
+++ b/click/types.py
@@ -274,9 +274,9 @@ class BoolParamType(ParamType):
         if isinstance(value, bool):
             return bool(value)
         value = value.lower()
-        if value in ('true', '1', 'yes', 'y'):
+        if value in ('true', 't,' '1', 'yes', 'y'):
             return True
-        elif value in ('false', '0', 'no', 'n'):
+        elif value in ('false', 'f', '0', 'no', 'n'):
             return False
         self.fail('%s is not a valid boolean' % value, param, ctx)
 
@@ -402,11 +402,11 @@ class Path(ParamType):
                          supposed to be done by the shell only.
     :param allow_dash: If this is set to `True`, a single dash to indicate
                        standard streams is permitted.
-    :param type: optionally a string type that should be used to
-                 represent the path.  The default is `None` which
-                 means the return value will be either bytes or
-                 unicode depending on what makes most sense given the
-                 input data Click deals with.
+    :param path_type: optionally a string type that should be used to
+                      represent the path.  The default is `None` which
+                      means the return value will be either bytes or
+                      unicode depending on what makes most sense given the
+                      input data Click deals with.
     """
     envvar_list_splitter = os.path.pathsep
 
diff --git a/docs/parameters.rst b/docs/parameters.rst
index e545343..9e5587e 100644
--- a/docs/parameters.rst
+++ b/docs/parameters.rst
@@ -47,8 +47,8 @@ different behavior and some are supported out of the box:
 
 ``bool`` / :data:`click.BOOL`:
     A parameter that accepts boolean values.  This is automatically used
-    for boolean flags.  If used with string values ``1``, ``yes``, ``y``
-    and ``true`` convert to `True` and ``0``, ``no``, ``n`` and ``false``
+    for boolean flags.  If used with string values ``1``, ``yes``, ``y``, ``t``
+    and ``true`` convert to `True` and ``0``, ``no``, ``n``, ``f`` and ``false``
     convert to `False`.
 
 :data:`click.UUID`:
diff --git a/examples/bashcompletion/bashcompletion.py b/examples/bashcompletion/bashcompletion.py
index 8aaf174..c483d79 100644
--- a/examples/bashcompletion/bashcompletion.py
+++ b/examples/bashcompletion/bashcompletion.py
@@ -1,12 +1,17 @@
 import click
 import os
 
+
 @click.group()
 def cli():
     pass
 
+
 def get_env_vars(ctx, args, incomplete):
-    return os.environ.keys()
+    for key in os.environ.keys():
+        if incomplete in key:
+            yield key
+
 
 @cli.command()
 @click.argument("envvar", type=click.STRING, autocompletion=get_env_vars)
@@ -14,14 +19,19 @@ def cmd1(envvar):
     click.echo('Environment variable: %s' % envvar)
     click.echo('Value: %s' % os.environ[envvar])
 
+
 @click.group()
 def group():
     pass
 
+
 def list_users(ctx, args, incomplete):
     # Here you can generate completions dynamically
     users = ['bob', 'alice']
-    return users
+    for user in users:
+        if user.startswith(incomplete):
+            yield user
+
 
 @group.command()
 @click.argument("user", type=click.STRING, autocompletion=list_users)
diff --git a/setup.cfg b/setup.cfg
index 5c6311d..3b0846a 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -3,3 +3,6 @@ universal=1
 
 [metadata]
 license_file = LICENSE
+
+[tool:pytest]
+addopts = -p no:warnings --tb=short
diff --git a/tox.ini b/tox.ini
index 91bef64..9f72ab8 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,10 +1,14 @@
 [tox]
-envlist = py26,py27,py33,py34,pypy
+envlist = py{26,27,33,34,35,36,py}
 
 [testenv]
 passenv = LANG
-commands = make test
+commands = {env:TEST_RUNNER:pytest} {posargs}
 deps =
-    colorama
-    pytest
-whitelist_externals = make
+    {env:_CLICK_TEST_PYTEST_DEP:pytest}
+    colorama: colorama
+    coverage: coverage
+setenv =
+    coverage: TEST_RUNNER=coverage run -m pytest
+    # pytest 3.3+ does not support Python 2.6 and 3.3 anymore.
+    py26,py33: _CLICK_TEST_PYTEST_DEP=pytest<3.3


Test patch:
diff --git a/tests/test_bashcomplete.py b/tests/test_bashcomplete.py
index 268e046..69448e4 100644
--- a/tests/test_bashcomplete.py
+++ b/tests/test_bashcomplete.py
@@ -86,13 +86,20 @@ def test_long_chain():
     COLORS = ['red', 'green', 'blue']
     def get_colors(ctx, args, incomplete):
         for c in COLORS:
-            yield c
+            if c.startswith(incomplete):
+                yield c
+
+    def search_colors(ctx, args, incomplete):
+        for c in COLORS:
+            if incomplete in c:
+                yield c
 
     CSUB_OPT_CHOICES = ['foo', 'bar']
     CSUB_CHOICES = ['bar', 'baz']
     @bsub.command('csub')
     @click.option('--csub-opt', type=click.Choice(CSUB_OPT_CHOICES))
     @click.option('--csub', type=click.Choice(CSUB_CHOICES))
+    @click.option('--search-color', autocompletion=search_colors)
     @click.argument('color', autocompletion=get_colors)
     def csub(csub_opt, color):
         pass
@@ -103,13 +110,14 @@ def test_long_chain():
     assert list(get_choices(cli, 'lol', ['asub'], '')) == ['bsub']
     assert list(get_choices(cli, 'lol', ['asub', 'bsub'], '-')) == ['--bsub-opt']
     assert list(get_choices(cli, 'lol', ['asub', 'bsub'], '')) == ['csub']
-    assert list(get_choices(cli, 'lol', ['asub', 'bsub', 'csub'], '-')) == ['--csub-opt', '--csub']
+    assert list(get_choices(cli, 'lol', ['asub', 'bsub', 'csub'], '-')) == ['--csub-opt', '--csub', '--search-color']
     assert list(get_choices(cli, 'lol', ['asub', 'bsub', 'csub', '--csub-opt'], '')) == CSUB_OPT_CHOICES
     assert list(get_choices(cli, 'lol', ['asub', 'bsub', 'csub'], '--csub')) == ['--csub-opt', '--csub']
     assert list(get_choices(cli, 'lol', ['asub', 'bsub', 'csub', '--csub'], '')) == CSUB_CHOICES
     assert list(get_choices(cli, 'lol', ['asub', 'bsub', 'csub', '--csub-opt'], 'f')) == ['foo']
     assert list(get_choices(cli, 'lol', ['asub', 'bsub', 'csub'], '')) == COLORS
     assert list(get_choices(cli, 'lol', ['asub', 'bsub', 'csub'], 'b')) == ['blue']
+    assert list(get_choices(cli, 'lol', ['asub', 'bsub', 'csub', '--search-color'], 'een')) == ['green']
 
 
 def test_chaining():
@@ -125,17 +133,26 @@ def test_chaining():
 
     @cli.command('bsub')
     @click.option('--bsub-opt')
-    @click.argument('arg', type=click.Choice(['arg1', 'arg2']))
+    @click.argument('arg', type=click.Choice(['arg1', 'arg2']), required=True)
     def bsub(bsub_opt, arg):
         pass
 
+    @cli.command('csub')
+    @click.option('--csub-opt')
+    @click.argument('arg', type=click.Choice(['carg1', 'carg2']), required=False)
+    def csub(csub_opt, arg):
+        pass
+
     assert list(get_choices(cli, 'lol', [], '-')) == ['--cli-opt']
-    assert list(get_choices(cli, 'lol', [], '')) == ['asub', 'bsub']
+    assert list(get_choices(cli, 'lol', [], '')) == ['asub', 'bsub', 'csub']
     assert list(get_choices(cli, 'lol', ['asub'], '-')) == ['--asub-opt']
-    assert list(get_choices(cli, 'lol', ['asub'], '')) == ['bsub']
-    assert list(get_choices(cli, 'lol', ['bsub'], '')) == ['arg1', 'arg2', 'asub']
+    assert list(get_choices(cli, 'lol', ['asub'], '')) == ['bsub', 'csub']
+    assert list(get_choices(cli, 'lol', ['bsub'], '')) == ['arg1', 'arg2']
+    assert list(get_choices(cli, 'lol', ['asub', '--asub-opt'], '')) == []
     assert list(get_choices(cli, 'lol', ['asub', '--asub-opt', '5', 'bsub'], '-')) == ['--bsub-opt']
     assert list(get_choices(cli, 'lol', ['asub', 'bsub'], '-')) == ['--bsub-opt']
+    assert list(get_choices(cli, 'lol', ['asub', 'csub'], '')) == ['carg1', 'carg2', 'bsub']
+    assert list(get_choices(cli, 'lol', ['asub', 'csub'], '-')) == ['--csub-opt']
 
 
 def test_argument_choice():
@@ -255,10 +272,10 @@ def test_long_chain_choice():
     def bsub(bsub_opt):
         pass
 
-    assert list(get_choices(cli, 'lol', ['sub'], '')) == ['subarg1', 'subarg2']
+    assert list(get_choices(cli, 'lol', ['sub'], '')) == ['subarg1', 'subarg2', 'bsub']
     assert list(get_choices(cli, 'lol', ['sub', '--sub-opt'], '')) == ['subopt1', 'subopt2']
     assert list(get_choices(cli, 'lol', ['sub', '--sub-opt', 'subopt1'], '')) == \
-        ['subarg1', 'subarg2']
+        ['subarg1', 'subarg2', 'bsub']
     assert list(get_choices(cli, 'lol',
         ['sub', '--sub-opt', 'subopt1', 'subarg1', 'bsub'], '-')) == ['--bsub-opt']
     assert list(get_choices(cli, 'lol',
diff --git a/tests/test_basic.py b/tests/test_basic.py
index ec4df7d..94e0aae 100644
--- a/tests/test_basic.py
+++ b/tests/test_basic.py
@@ -180,6 +180,28 @@ def test_boolean_option(runner):
         assert result.output == '%s\n' % (default)
 
 
+def test_boolean_conversion(runner):
+    for default in True, False:
+        @click.command()
+        @click.option('--flag', default=default, type=bool)
+        def cli(flag):
+            click.echo(flag)
+
+        for value in 'true', 't,' '1', 'yes', 'y':
+            result = runner.invoke(cli, ['--flag', value])
+            assert not result.exception
+            assert result.output == 'True\n'
+
+        for value in 'false', 'f', '0', 'no', 'n':
+            result = runner.invoke(cli, ['--flag', value])
+            assert not result.exception
+            assert result.output == 'False\n'
+
+        result = runner.invoke(cli, [])
+        assert not result.exception
+        assert result.output == '%s\n' % default
+
+
 def test_file_option(runner):
     @click.command()
     @click.option('--file', type=click.File('w'))
@@ -390,7 +412,7 @@ def test_required_option(runner):
 def test_evaluation_order(runner):
     called = []
 
-    def memo(ctx, value):
+    def memo(ctx, param, value):
         called.append(value)
         return value
 
diff --git a/tests/test_compat.py b/tests/test_compat.py
index e4ecdc8..9dacc21 100644
--- a/tests/test_compat.py
+++ b/tests/test_compat.py
@@ -1,4 +1,5 @@
 import click
+import pytest
 
 
 if click.__version__ >= '3.0':
@@ -11,10 +12,10 @@ if click.__version__ >= '3.0':
         def cli(foo):
             click.echo(foo)
 
-        result = runner.invoke(cli, ['--foo', 'wat'])
-        assert result.exit_code == 0
-        assert 'WAT' in result.output
-        assert 'Invoked legacy parameter callback' in result.output
+        with pytest.warns(Warning, match='Invoked legacy parameter callback'):
+            result = runner.invoke(cli, ['--foo', 'wat'])
+            assert result.exit_code == 0
+            assert 'WAT' in result.output
 
 
 def test_bash_func_name():
diff --git a/tests/test_options.py b/tests/test_options.py
index 44a1097..537fbd4 100644
--- a/tests/test_options.py
+++ b/tests/test_options.py
@@ -199,7 +199,7 @@ def test_nargs_envvar(runner):
 
 
 def test_custom_validation(runner):
-    def validate_pos_int(ctx, value):
+    def validate_pos_int(ctx, param, value):
         if value < 0:
             raise click.BadParameter('Value needs to be positive')
         return value