gh-103558: Add coverage tests for argparse (#103570)

Co-authored-by: Shantanu <12621235+hauntsaninja@users.noreply.github.com>
Co-authored-by: hauntsaninja <hauntsaninja@gmail.com>
This commit is contained in:
Tian Gao 2023-06-05 00:14:00 -07:00 committed by GitHub
parent 418befd75d
commit 9efaff5fd3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 92 additions and 7 deletions

View File

@ -1528,6 +1528,8 @@ class _ActionsContainer(object):
title_group_map = {} title_group_map = {}
for group in self._action_groups: for group in self._action_groups:
if group.title in title_group_map: if group.title in title_group_map:
# This branch could happen if a derived class added
# groups with duplicated titles in __init__
msg = _('cannot merge actions - two groups are named %r') msg = _('cannot merge actions - two groups are named %r')
raise ValueError(msg % (group.title)) raise ValueError(msg % (group.title))
title_group_map[group.title] = group title_group_map[group.title] = group
@ -1811,13 +1813,11 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer):
# add parent arguments and defaults # add parent arguments and defaults
for parent in parents: for parent in parents:
if not isinstance(parent, ArgumentParser):
raise TypeError('parents must be a list of ArgumentParser')
self._add_container_actions(parent) self._add_container_actions(parent)
try: defaults = parent._defaults
defaults = parent._defaults self._defaults.update(defaults)
except AttributeError:
pass
else:
self._defaults.update(defaults)
# ======================= # =======================
# Pretty __repr__ methods # Pretty __repr__ methods

View File

@ -15,7 +15,7 @@ import unittest
import argparse import argparse
import warnings import warnings
from test.support import os_helper from test.support import os_helper, captured_stderr
from unittest import mock from unittest import mock
@ -1382,6 +1382,19 @@ class TestPositionalsActionAppend(ParserTestCase):
('a b c', NS(spam=['a', ['b', 'c']])), ('a b c', NS(spam=['a', ['b', 'c']])),
] ]
class TestPositionalsActionExtend(ParserTestCase):
"""Test the 'extend' action"""
argument_signatures = [
Sig('spam', action='extend'),
Sig('spam', action='extend', nargs=2),
]
failures = ['', '--foo', 'a', 'a b', 'a b c d']
successes = [
('a b c', NS(spam=['a', 'b', 'c'])),
]
# ======================================== # ========================================
# Combined optionals and positionals tests # Combined optionals and positionals tests
# ======================================== # ========================================
@ -1419,6 +1432,32 @@ class TestOptionalsAlmostNumericAndPositionals(ParserTestCase):
] ]
class TestOptionalsAndPositionalsAppend(ParserTestCase):
argument_signatures = [
Sig('foo', nargs='*', action='append'),
Sig('--bar'),
]
failures = ['-foo']
successes = [
('a b', NS(foo=[['a', 'b']], bar=None)),
('--bar a b', NS(foo=[['b']], bar='a')),
('a b --bar c', NS(foo=[['a', 'b']], bar='c')),
]
class TestOptionalsAndPositionalsExtend(ParserTestCase):
argument_signatures = [
Sig('foo', nargs='*', action='extend'),
Sig('--bar'),
]
failures = ['-foo']
successes = [
('a b', NS(foo=['a', 'b'], bar=None)),
('--bar a b', NS(foo=['b'], bar='a')),
('a b --bar c', NS(foo=['a', 'b'], bar='c')),
]
class TestEmptyAndSpaceContainingArguments(ParserTestCase): class TestEmptyAndSpaceContainingArguments(ParserTestCase):
argument_signatures = [ argument_signatures = [
@ -1899,6 +1938,10 @@ class TestFileTypeOpenArgs(TestCase):
type('foo') type('foo')
m.assert_called_with('foo', *args) m.assert_called_with('foo', *args)
def test_invalid_file_type(self):
with self.assertRaises(ValueError):
argparse.FileType('b')('-test')
class TestFileTypeMissingInitialization(TestCase): class TestFileTypeMissingInitialization(TestCase):
""" """
@ -2092,6 +2135,27 @@ class TestActionExtend(ParserTestCase):
('--foo f1 --foo f2 f3 f4', NS(foo=['f1', 'f2', 'f3', 'f4'])), ('--foo f1 --foo f2 f3 f4', NS(foo=['f1', 'f2', 'f3', 'f4'])),
] ]
class TestInvalidAction(TestCase):
"""Test invalid user defined Action"""
class ActionWithoutCall(argparse.Action):
pass
def test_invalid_type(self):
parser = argparse.ArgumentParser()
parser.add_argument('--foo', action=self.ActionWithoutCall)
self.assertRaises(NotImplementedError, parser.parse_args, ['--foo', 'bar'])
def test_modified_invalid_action(self):
parser = ErrorRaisingArgumentParser()
action = parser.add_argument('--foo')
# Someone got crazy and did this
action.type = 1
self.assertRaises(ArgumentParserError, parser.parse_args, ['--foo', 'bar'])
# ================ # ================
# Subparsers tests # Subparsers tests
# ================ # ================
@ -2727,6 +2791,9 @@ class TestParentParsers(TestCase):
-x X -x X
'''.format(progname, ' ' if progname else '' ))) '''.format(progname, ' ' if progname else '' )))
def test_wrong_type_parents(self):
self.assertRaises(TypeError, ErrorRaisingArgumentParser, parents=[1])
# ============================== # ==============================
# Mutually exclusive group tests # Mutually exclusive group tests
# ============================== # ==============================
@ -4743,6 +4810,9 @@ class TestInvalidArgumentConstructors(TestCase):
self.assertValueError('--') self.assertValueError('--')
self.assertValueError('---') self.assertValueError('---')
def test_invalid_prefix(self):
self.assertValueError('--foo', '+foo')
def test_invalid_type(self): def test_invalid_type(self):
self.assertValueError('--foo', type='int') self.assertValueError('--foo', type='int')
self.assertValueError('--foo', type=(int, float)) self.assertValueError('--foo', type=(int, float))
@ -4807,6 +4877,9 @@ class TestInvalidArgumentConstructors(TestCase):
self.assertTypeError('command', action='parsers', self.assertTypeError('command', action='parsers',
parser_class=argparse.ArgumentParser) parser_class=argparse.ArgumentParser)
def test_version_missing_params(self):
self.assertTypeError('command', action='version')
def test_required_positional(self): def test_required_positional(self):
self.assertTypeError('foo', required=True) self.assertTypeError('foo', required=True)
@ -5400,6 +5473,17 @@ class TestIntermixedArgs(TestCase):
self.assertRaises(TypeError, parser.parse_intermixed_args, []) self.assertRaises(TypeError, parser.parse_intermixed_args, [])
self.assertEqual(group.required, True) self.assertEqual(group.required, True)
def test_invalid_args(self):
parser = ErrorRaisingArgumentParser(prog='PROG')
self.assertRaises(ArgumentParserError, parser.parse_intermixed_args, ['a'])
parser = ErrorRaisingArgumentParser(prog='PROG')
parser.add_argument('--foo', nargs="*")
parser.add_argument('foo')
with captured_stderr() as stderr:
parser.parse_intermixed_args(['hello', '--foo'])
self.assertIn("UserWarning", stderr.getvalue())
class TestIntermixedMessageContentError(TestCase): class TestIntermixedMessageContentError(TestCase):
# case where Intermixed gives different error message # case where Intermixed gives different error message
# error is raised by 1st parsing step # error is raised by 1st parsing step

View File

@ -0,0 +1 @@
Fixed ``parent`` argument validation mechanism of :mod:`argparse`. Improved test coverage.