From c60cdf5161c225b6366eeaba629846ff60bebbc1 Mon Sep 17 00:00:00 2001 From: Hal Blackburn Date: Sun, 28 Aug 2022 07:22:25 +0000 Subject: [PATCH] test: assert the messages of raised errors I'm planning to make the error messages more user-friendly, (especially "Warning: found unmatched (duplicate?) arguments ..."), so I need to verify what the messages currently are so I can ensure that any changes are expected. --- tests/test_docopt.py | 113 ++++++++++++++++++++++++++++------------ tests/test_docopt_ng.py | 13 +++-- 2 files changed, 91 insertions(+), 35 deletions(-) diff --git a/tests/test_docopt.py b/tests/test_docopt.py index 6c267d3..4c81d4e 100644 --- a/tests/test_docopt.py +++ b/tests/test_docopt.py @@ -85,7 +85,10 @@ def test_commands(): assert docopt("Usage: prog (add|rm)", "add") == {"add": True, "rm": False} assert docopt("Usage: prog (add|rm)", "rm") == {"add": False, "rm": True} assert docopt("Usage: prog a b", "a b") == {"a": True, "b": True} - with raises(DocoptExit): + with raises( + DocoptExit, + match=r"Warning: found unmatched \(duplicate\?\) arguments" ".*'b'.*'a'", + ): docopt("Usage: prog a b", "b a") @@ -487,40 +490,53 @@ def test_long_options_error_handling(): # docopt('Usage: prog --non-existent', '--non-existent') # with raises(DocoptLanguageError): # docopt('Usage: prog --non-existent') - with raises(DocoptExit): + with raises( + DocoptExit, + match=r"Warning: found unmatched \(duplicate\?\) arguments.*--non-existent", + ): docopt("Usage: prog", "--non-existent") - with raises(DocoptExit): + with raises( + DocoptExit, match=r"Warning: found unmatched \(duplicate\?\) arguments.*--ver\b" + ): docopt( "Usage: prog [--version --verbose]\n" "Options: --version\n --verbose", "--ver", ) - with raises(DocoptLanguageError): + # --long is missing ARG in usage + with raises(DocoptLanguageError, match=r"unmatched '\('"): docopt("Usage: prog --long\nOptions: --long ARG") - with raises(DocoptExit): + with raises(DocoptExit, match=r"--long requires argument"): docopt("Usage: prog --long ARG\nOptions: --long ARG", "--long") - with raises(DocoptLanguageError): + with raises(DocoptLanguageError, match=r"--long must not have an argument"): docopt("Usage: prog --long=ARG\nOptions: --long") - with raises(DocoptExit): + with raises(DocoptExit, match=r"--long must not have an argument"): docopt("Usage: prog --long\nOptions: --long", "--long=ARG") def test_short_options_error_handling(): - with raises(DocoptLanguageError): + with raises(DocoptLanguageError, match=r"-x is specified ambiguously 2 times"): docopt("Usage: prog -x\nOptions: -x this\n -x that") - with raises(DocoptExit): + with raises( + DocoptExit, match=r"Warning: found unmatched \(duplicate\?\) arguments.*-x" + ): docopt("Usage: prog", "-x") - with raises(DocoptLanguageError): + # -o needs an argument in usage. The "unmatched" error is because `-o` is + # expanded to `( -o )` during parsing, and ')' is consumed as the argument + # of -o. Clearly this isn't the right error, but the current parser isn't + # able to distinguish between a literal ')' value and a ) closing a required + # group. + with raises(DocoptLanguageError, match=r"unmatched '\('"): docopt("Usage: prog -o\nOptions: -o ARG") - with raises(DocoptExit): + with raises(DocoptExit, match=r"-o requires argument"): docopt("Usage: prog -o ARG\nOptions: -o ARG", "-o") def test_matching_paren(): - with raises(DocoptLanguageError): + with raises(DocoptLanguageError, match=r"unmatched '\['"): docopt("Usage: prog [a [b]") - with raises(DocoptLanguageError): + with raises(DocoptLanguageError, match=r"unexpected ending: '\)'"): docopt("Usage: prog [a [b] ] c )") @@ -535,11 +551,13 @@ def test_allow_double_dash(): "": "1", "--": False, } - with raises(DocoptExit): # "--" is not allowed; FIXME? + with raises( + DocoptExit, match=r"Warning: found unmatched \(duplicate\?\) arguments.*-o\b" + ): # "--" is not allowed; FIXME? docopt("usage: prog [-o] \noptions:-o", "-- -o") -def test_docopt(): +def test_docopt(capsys: pytest.CaptureFixture): doc = """Usage: prog [-v] A Options: -v Be verbose.""" @@ -579,14 +597,23 @@ def test_docopt(): "OUTPUT": None, } - with raises(DocoptExit): # does not match + with raises( + DocoptExit, + match=r"Warning: found unmatched \(duplicate\?\) arguments.*output\.py", + ): docopt(doc, "-v input.py output.py") - with raises(DocoptExit): + with raises( + DocoptExit, + match=r"Warning: found unmatched \(duplicate\?\) arguments.*--fake", + ): docopt(doc, "--fake") + capsys.readouterr() # clear any captured output + with raises(SystemExit): docopt(doc, "--hel") + assert capsys.readouterr().out.startswith("Usage: prog") @pytest.mark.parametrize( @@ -604,15 +631,23 @@ def test_docopt_result_dict_repr(items: dict[str, object], expected: str): def test_language_errors(): - with raises(DocoptLanguageError): + with raises( + DocoptLanguageError, + match=r'"usage:" section \(case-insensitive\) not found. ' + r"Perhaps missing indentation\?", + ): docopt("no usage with colon here") - with raises(DocoptLanguageError): + with raises( + DocoptLanguageError, match=r'More than one "usage:" \(case-insensitive\)' + ): docopt("usage: here \n\n and again usage: here") -def test_issue_40(): +def test_issue_40(capsys: pytest.CaptureFixture): with raises(SystemExit): # i.e. shows help docopt("usage: prog --help-commands | --help", "--help") + assert capsys.readouterr().out.startswith("usage: prog --help-commands | --help") + assert docopt("usage: prog --aabb | --aa", "--aa") == { "--aabb": False, "--aa": True, @@ -631,7 +666,9 @@ def test_count_multiple_flags(): assert docopt("usage: prog [-vv]", "") == {"-v": 0} assert docopt("usage: prog [-vv]", "-v") == {"-v": 1} assert docopt("usage: prog [-vv]", "-vv") == {"-v": 2} - with raises(DocoptExit): + with raises( + DocoptExit, match=r"Warning: found unmatched \(duplicate\?\) arguments.*'-v'" + ): docopt("usage: prog [-vv]", "-vvv") assert docopt("usage: prog [-v | -vv | -vvv]", "-vvv") == {"-v": 3} assert docopt("usage: prog -v...", "-vvvvvv") == {"-v": 6} @@ -639,20 +676,36 @@ def test_count_multiple_flags(): def test_any_options_parameter(): - with raises(DocoptExit): + with raises( + DocoptExit, + match=r"Warning: found unmatched \(duplicate\?\) arguments" + r".*-f.*-o.*-o.*--bar.*--spam.*eggs", + ): docopt("usage: prog [options]", "-foo --bar --spam=eggs") # assert docopt('usage: prog [options]', '-foo --bar --spam=eggs', # any_options=True) == {'-f': True, '-o': 2, # '--bar': True, '--spam': 'eggs'} - with raises(DocoptExit): + with raises( + DocoptExit, + match=r"Warning: found unmatched \(duplicate\?\) arguments" + r".*--foo.*--bar.*--bar", + ): docopt("usage: prog [options]", "--foo --bar --bar") # assert docopt('usage: prog [options]', '--foo --bar --bar', # any_options=True) == {'--foo': True, '--bar': 2} - with raises(DocoptExit): + with raises( + DocoptExit, + match=r"Warning: found unmatched \(duplicate\?\) arguments" + r".*--bar.*--bar.*--bar.*-f.*-f.*-f.*-f", + ): docopt("usage: prog [options]", "--bar --bar --bar -ffff") # assert docopt('usage: prog [options]', '--bar --bar --bar -ffff', # any_options=True) == {'--bar': 3, '-f': 4} - with raises(DocoptExit): + with raises( + DocoptExit, + match=r"Warning: found unmatched \(duplicate\?\) arguments" + r".*--long.*arg.*--long.*another", + ): docopt("usage: prog [options]", "--long=arg --long=another") @@ -725,14 +778,10 @@ def test_issue_65_evaluate_argv_when_called_not_when_imported(): def test_issue_71_double_dash_is_not_a_valid_option_argument(): - with raises(DocoptExit): + with raises(DocoptExit, match=r"--log requires argument"): docopt("usage: prog [--log=LEVEL] [--] ...", "--log -- 1 2") - with raises(DocoptExit): - docopt( - """usage: prog [-l LEVEL] [--] ... - options: -l LEVEL""", - "-l -- 1 2", - ) + with raises(DocoptExit, match=r"-l requires argument"): + docopt("usage: prog [-l LEVEL] [--] ...\noptions: -l LEVEL", "-l -- 1 2") usage = """usage: this diff --git a/tests/test_docopt_ng.py b/tests/test_docopt_ng.py index 372a6be..43b1cf3 100644 --- a/tests/test_docopt_ng.py +++ b/tests/test_docopt_ng.py @@ -153,10 +153,16 @@ def test_docopt_ng_more_magic_global_arguments_and_dot_access(): } assert arguments.FILE is None - with raises(DocoptExit): + with raises( + DocoptExit, + match=r"Warning: found unmatched \(duplicate\?\) arguments.*output\.py", + ): docopt.docopt(doc, "-v input.py output.py") - with raises(DocoptExit): + with raises( + DocoptExit, + match=r"Warning: found unmatched \(duplicate\?\) arguments.*--fake", + ): docopt.docopt(doc, "--fake") arguments = None @@ -180,9 +186,10 @@ def test_docopt_ng_negative_float(): assert args == {"--negative_pi": "-3.14", "NEGTAU": "-6.28"} -def test_docopt_ng_doubledash_version(): +def test_docopt_ng_doubledash_version(capsys: pytest.CaptureFixture): with pytest.raises(SystemExit) as pytest_wrapped_e: docopt.docopt("usage: prog", version=1, argv="prog --version") + assert capsys.readouterr().out == "1\n" assert pytest_wrapped_e.type == SystemExit