Skip to content

Commit 9efaff5

Browse files
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>
1 parent 418befd commit 9efaff5

File tree

3 files changed

+92
-7
lines changed

3 files changed

+92
-7
lines changed

Lib/argparse.py

+6-6
Original file line numberDiff line numberDiff line change
@@ -1528,6 +1528,8 @@ def _add_container_actions(self, container):
15281528
title_group_map = {}
15291529
for group in self._action_groups:
15301530
if group.title in title_group_map:
1531+
# This branch could happen if a derived class added
1532+
# groups with duplicated titles in __init__
15311533
msg = _('cannot merge actions - two groups are named %r')
15321534
raise ValueError(msg % (group.title))
15331535
title_group_map[group.title] = group
@@ -1811,13 +1813,11 @@ def identity(string):
18111813

18121814
# add parent arguments and defaults
18131815
for parent in parents:
1816+
if not isinstance(parent, ArgumentParser):
1817+
raise TypeError('parents must be a list of ArgumentParser')
18141818
self._add_container_actions(parent)
1815-
try:
1816-
defaults = parent._defaults
1817-
except AttributeError:
1818-
pass
1819-
else:
1820-
self._defaults.update(defaults)
1819+
defaults = parent._defaults
1820+
self._defaults.update(defaults)
18211821

18221822
# =======================
18231823
# Pretty __repr__ methods

Lib/test/test_argparse.py

+85-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
import argparse
1616
import warnings
1717

18-
from test.support import os_helper
18+
from test.support import os_helper, captured_stderr
1919
from unittest import mock
2020

2121

@@ -1382,6 +1382,19 @@ class TestPositionalsActionAppend(ParserTestCase):
13821382
('a b c', NS(spam=['a', ['b', 'c']])),
13831383
]
13841384

1385+
1386+
class TestPositionalsActionExtend(ParserTestCase):
1387+
"""Test the 'extend' action"""
1388+
1389+
argument_signatures = [
1390+
Sig('spam', action='extend'),
1391+
Sig('spam', action='extend', nargs=2),
1392+
]
1393+
failures = ['', '--foo', 'a', 'a b', 'a b c d']
1394+
successes = [
1395+
('a b c', NS(spam=['a', 'b', 'c'])),
1396+
]
1397+
13851398
# ========================================
13861399
# Combined optionals and positionals tests
13871400
# ========================================
@@ -1419,6 +1432,32 @@ class TestOptionalsAlmostNumericAndPositionals(ParserTestCase):
14191432
]
14201433

14211434

1435+
class TestOptionalsAndPositionalsAppend(ParserTestCase):
1436+
argument_signatures = [
1437+
Sig('foo', nargs='*', action='append'),
1438+
Sig('--bar'),
1439+
]
1440+
failures = ['-foo']
1441+
successes = [
1442+
('a b', NS(foo=[['a', 'b']], bar=None)),
1443+
('--bar a b', NS(foo=[['b']], bar='a')),
1444+
('a b --bar c', NS(foo=[['a', 'b']], bar='c')),
1445+
]
1446+
1447+
1448+
class TestOptionalsAndPositionalsExtend(ParserTestCase):
1449+
argument_signatures = [
1450+
Sig('foo', nargs='*', action='extend'),
1451+
Sig('--bar'),
1452+
]
1453+
failures = ['-foo']
1454+
successes = [
1455+
('a b', NS(foo=['a', 'b'], bar=None)),
1456+
('--bar a b', NS(foo=['b'], bar='a')),
1457+
('a b --bar c', NS(foo=['a', 'b'], bar='c')),
1458+
]
1459+
1460+
14221461
class TestEmptyAndSpaceContainingArguments(ParserTestCase):
14231462

14241463
argument_signatures = [
@@ -1899,6 +1938,10 @@ def test_open_args(self):
18991938
type('foo')
19001939
m.assert_called_with('foo', *args)
19011940

1941+
def test_invalid_file_type(self):
1942+
with self.assertRaises(ValueError):
1943+
argparse.FileType('b')('-test')
1944+
19021945

19031946
class TestFileTypeMissingInitialization(TestCase):
19041947
"""
@@ -2092,6 +2135,27 @@ class TestActionExtend(ParserTestCase):
20922135
('--foo f1 --foo f2 f3 f4', NS(foo=['f1', 'f2', 'f3', 'f4'])),
20932136
]
20942137

2138+
2139+
class TestInvalidAction(TestCase):
2140+
"""Test invalid user defined Action"""
2141+
2142+
class ActionWithoutCall(argparse.Action):
2143+
pass
2144+
2145+
def test_invalid_type(self):
2146+
parser = argparse.ArgumentParser()
2147+
2148+
parser.add_argument('--foo', action=self.ActionWithoutCall)
2149+
self.assertRaises(NotImplementedError, parser.parse_args, ['--foo', 'bar'])
2150+
2151+
def test_modified_invalid_action(self):
2152+
parser = ErrorRaisingArgumentParser()
2153+
action = parser.add_argument('--foo')
2154+
# Someone got crazy and did this
2155+
action.type = 1
2156+
self.assertRaises(ArgumentParserError, parser.parse_args, ['--foo', 'bar'])
2157+
2158+
20952159
# ================
20962160
# Subparsers tests
20972161
# ================
@@ -2727,6 +2791,9 @@ def test_groups_parents(self):
27272791
-x X
27282792
'''.format(progname, ' ' if progname else '' )))
27292793

2794+
def test_wrong_type_parents(self):
2795+
self.assertRaises(TypeError, ErrorRaisingArgumentParser, parents=[1])
2796+
27302797
# ==============================
27312798
# Mutually exclusive group tests
27322799
# ==============================
@@ -4743,6 +4810,9 @@ def test_invalid_option_strings(self):
47434810
self.assertValueError('--')
47444811
self.assertValueError('---')
47454812

4813+
def test_invalid_prefix(self):
4814+
self.assertValueError('--foo', '+foo')
4815+
47464816
def test_invalid_type(self):
47474817
self.assertValueError('--foo', type='int')
47484818
self.assertValueError('--foo', type=(int, float))
@@ -4807,6 +4877,9 @@ def test_parsers_action_missing_params(self):
48074877
self.assertTypeError('command', action='parsers',
48084878
parser_class=argparse.ArgumentParser)
48094879

4880+
def test_version_missing_params(self):
4881+
self.assertTypeError('command', action='version')
4882+
48104883
def test_required_positional(self):
48114884
self.assertTypeError('foo', required=True)
48124885

@@ -5400,6 +5473,17 @@ def test_exclusive_incompatible(self):
54005473
self.assertRaises(TypeError, parser.parse_intermixed_args, [])
54015474
self.assertEqual(group.required, True)
54025475

5476+
def test_invalid_args(self):
5477+
parser = ErrorRaisingArgumentParser(prog='PROG')
5478+
self.assertRaises(ArgumentParserError, parser.parse_intermixed_args, ['a'])
5479+
5480+
parser = ErrorRaisingArgumentParser(prog='PROG')
5481+
parser.add_argument('--foo', nargs="*")
5482+
parser.add_argument('foo')
5483+
with captured_stderr() as stderr:
5484+
parser.parse_intermixed_args(['hello', '--foo'])
5485+
self.assertIn("UserWarning", stderr.getvalue())
5486+
54035487
class TestIntermixedMessageContentError(TestCase):
54045488
# case where Intermixed gives different error message
54055489
# error is raised by 1st parsing step
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fixed ``parent`` argument validation mechanism of :mod:`argparse`. Improved test coverage.

0 commit comments

Comments
 (0)