Skip to content

Commit 00b735f

Browse files
committed
Merge branch 'small_int_immortal' of github.com:eendebakpt/cpython into small_int_immortal
2 parents 2c23541 + 7d3a012 commit 00b735f

File tree

18 files changed

+371
-103
lines changed

18 files changed

+371
-103
lines changed

Doc/library/argparse.rst

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,12 @@ arguments it contains. The default message can be overridden with the
192192
The ``%(prog)s`` format specifier is available to fill in the program name in
193193
your usage messages.
194194

195+
When a custom usage message is specified for the main parser, you may also want to
196+
consider passing the ``prog`` argument to :meth:`~ArgumentParser.add_subparsers`
197+
or the ``prog`` and the ``usage`` arguments to
198+
:meth:`~_SubParsersAction.add_parser`, to ensure consistent command prefixes and
199+
usage information across subparsers.
200+
195201

196202
.. _description:
197203

@@ -1810,6 +1816,10 @@ Sub-commands
18101816
.. versionchanged:: 3.7
18111817
New *required* keyword-only parameter.
18121818

1819+
.. versionchanged:: 3.14
1820+
Subparser's *prog* is no longer affected by a custom usage message in
1821+
the main parser.
1822+
18131823

18141824
FileType objects
18151825
^^^^^^^^^^^^^^^^

Lib/argparse.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1889,7 +1889,7 @@ def add_subparsers(self, **kwargs):
18891889
formatter = self._get_formatter()
18901890
positionals = self._get_positional_actions()
18911891
groups = self._mutually_exclusive_groups
1892-
formatter.add_usage(self.usage, positionals, groups, '')
1892+
formatter.add_usage(None, positionals, groups, '')
18931893
kwargs['prog'] = formatter.format_help().strip()
18941894

18951895
# create the parsers action and add it to the positionals list

Lib/test/support/strace_helper.py

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,27 @@ def sections(self):
7171

7272
return sections
7373

74+
def _filter_memory_call(call):
75+
# mmap can operate on a fd or "MAP_ANONYMOUS" which gives a block of memory.
76+
# Ignore "MAP_ANONYMOUS + the "MAP_ANON" alias.
77+
if call.syscall == "mmap" and "MAP_ANON" in call.args[3]:
78+
return True
79+
80+
if call.syscall in ("munmap", "mprotect"):
81+
return True
82+
83+
return False
84+
85+
86+
def filter_memory(syscalls):
87+
"""Filter out memory allocation calls from File I/O calls.
88+
89+
Some calls (mmap, munmap, etc) can be used on files or to just get a block
90+
of memory. Use this function to filter out the memory related calls from
91+
other calls."""
92+
93+
return [call for call in syscalls if not _filter_memory_call(call)]
94+
7495

7596
@support.requires_subprocess()
7697
def strace_python(code, strace_flags, check=True):
@@ -93,8 +114,6 @@ def _make_error(reason, details):
93114
"-c",
94115
textwrap.dedent(code),
95116
__run_using_command=[_strace_binary] + strace_flags,
96-
# Don't want to trace our JIT's own mmap and mprotect calls:
97-
PYTHON_JIT="0",
98117
)
99118
except OSError as err:
100119
return _make_error("Caught OSError", err)
@@ -145,9 +164,14 @@ def get_events(code, strace_flags, prelude, cleanup):
145164
return all_sections['code']
146165

147166

148-
def get_syscalls(code, strace_flags, prelude="", cleanup=""):
167+
def get_syscalls(code, strace_flags, prelude="", cleanup="",
168+
ignore_memory=True):
149169
"""Get the syscalls which a given chunk of python code generates"""
150170
events = get_events(code, strace_flags, prelude=prelude, cleanup=cleanup)
171+
172+
if ignore_memory:
173+
events = filter_memory(events)
174+
151175
return [ev.syscall for ev in events]
152176

153177

@@ -177,5 +201,5 @@ def requires_strace():
177201
return unittest.skipUnless(_can_strace(), "Requires working strace")
178202

179203

180-
__all__ = ["get_events", "get_syscalls", "requires_strace", "strace_python",
181-
"StraceEvent", "StraceResult"]
204+
__all__ = ["filter_memory", "get_events", "get_syscalls", "requires_strace",
205+
"strace_python", "StraceEvent", "StraceResult"]

Lib/test/test_argparse.py

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2409,16 +2409,17 @@ def assertArgumentParserError(self, *args, **kwargs):
24092409
self.assertRaises(ArgumentParserError, *args, **kwargs)
24102410

24112411
def _get_parser(self, subparser_help=False, prefix_chars=None,
2412-
aliases=False):
2412+
aliases=False, usage=None):
24132413
# create a parser with a subparsers argument
24142414
if prefix_chars:
24152415
parser = ErrorRaisingArgumentParser(
2416-
prog='PROG', description='main description', prefix_chars=prefix_chars)
2416+
prog='PROG', description='main description', usage=usage,
2417+
prefix_chars=prefix_chars)
24172418
parser.add_argument(
24182419
prefix_chars[0] * 2 + 'foo', action='store_true', help='foo help')
24192420
else:
24202421
parser = ErrorRaisingArgumentParser(
2421-
prog='PROG', description='main description')
2422+
prog='PROG', description='main description', usage=usage)
24222423
parser.add_argument(
24232424
'--foo', action='store_true', help='foo help')
24242425
parser.add_argument(
@@ -2455,7 +2456,8 @@ def _get_parser(self, subparser_help=False, prefix_chars=None,
24552456
parser2.add_argument('z', type=complex, nargs='*', help='z help')
24562457

24572458
# add third sub-parser
2458-
parser3_kwargs = dict(description='3 description')
2459+
parser3_kwargs = dict(description='3 description',
2460+
usage='PROG --foo bar 3 t ...')
24592461
if subparser_help:
24602462
parser3_kwargs['help'] = '3 help'
24612463
parser3 = subparsers.add_parser('3', **parser3_kwargs)
@@ -2477,6 +2479,47 @@ def test_parse_args_failures(self):
24772479
args = args_str.split()
24782480
self.assertArgumentParserError(self.parser.parse_args, args)
24792481

2482+
def test_parse_args_failures_details(self):
2483+
for args_str, usage_str, error_str in [
2484+
('',
2485+
'usage: PROG [-h] [--foo] bar {1,2,3} ...',
2486+
'PROG: error: the following arguments are required: bar'),
2487+
('0.5 1 -y',
2488+
'usage: PROG bar 1 [-h] [-w W] {a,b,c}',
2489+
'PROG bar 1: error: the following arguments are required: x'),
2490+
('0.5 3',
2491+
'usage: PROG --foo bar 3 t ...',
2492+
'PROG bar 3: error: the following arguments are required: t'),
2493+
]:
2494+
with self.subTest(args_str):
2495+
args = args_str.split()
2496+
with self.assertRaises(ArgumentParserError) as cm:
2497+
self.parser.parse_args(args)
2498+
self.assertEqual(cm.exception.args[0], 'SystemExit')
2499+
self.assertEqual(cm.exception.args[2], f'{usage_str}\n{error_str}\n')
2500+
2501+
def test_parse_args_failures_details_custom_usage(self):
2502+
parser = self._get_parser(usage='PROG [--foo] bar 1 [-w W] {a,b,c}\n'
2503+
' PROG --foo bar 3 t ...')
2504+
for args_str, usage_str, error_str in [
2505+
('',
2506+
'usage: PROG [--foo] bar 1 [-w W] {a,b,c}\n'
2507+
' PROG --foo bar 3 t ...',
2508+
'PROG: error: the following arguments are required: bar'),
2509+
('0.5 1 -y',
2510+
'usage: PROG bar 1 [-h] [-w W] {a,b,c}',
2511+
'PROG bar 1: error: the following arguments are required: x'),
2512+
('0.5 3',
2513+
'usage: PROG --foo bar 3 t ...',
2514+
'PROG bar 3: error: the following arguments are required: t'),
2515+
]:
2516+
with self.subTest(args_str):
2517+
args = args_str.split()
2518+
with self.assertRaises(ArgumentParserError) as cm:
2519+
parser.parse_args(args)
2520+
self.assertEqual(cm.exception.args[0], 'SystemExit')
2521+
self.assertEqual(cm.exception.args[2], f'{usage_str}\n{error_str}\n')
2522+
24802523
def test_parse_args(self):
24812524
# check some non-failure cases:
24822525
self.assertEqual(

Lib/test/test_fileio.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -364,8 +364,7 @@ def testErrnoOnClosedReadinto(self, f):
364364

365365
@strace_helper.requires_strace()
366366
def test_syscalls_read(self):
367-
"""Check that the set of system calls produced by the I/O stack is what
368-
is expected for various read cases.
367+
"""Check set of system calls during common I/O patterns
369368
370369
It's expected as bits of the I/O implementation change, this will need
371370
to change. The goal is to catch changes that unintentionally add
@@ -383,6 +382,11 @@ def check_readall(name, code, prelude="", cleanup="",
383382
prelude=prelude,
384383
cleanup=cleanup)
385384

385+
# Some system calls (ex. mmap) can be used for both File I/O and
386+
# memory allocation. Filter out the ones used for memory
387+
# allocation.
388+
syscalls = strace_helper.filter_memory(syscalls)
389+
386390
# The first call should be an open that returns a
387391
# file descriptor (fd). Afer that calls may vary. Once the file
388392
# is opened, check calls refer to it by fd as the filename

Lib/test/test_tools/i18n_data/messages.pot

Lines changed: 34 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,53 +15,75 @@ msgstr ""
1515
"Generated-By: pygettext.py 1.5\n"
1616

1717

18-
#: messages.py:5
18+
#: messages.py:16
1919
msgid ""
2020
msgstr ""
2121

22-
#: messages.py:8 messages.py:9
22+
#: messages.py:19 messages.py:20
2323
msgid "parentheses"
2424
msgstr ""
2525

26-
#: messages.py:12
26+
#: messages.py:23
2727
msgid "Hello, world!"
2828
msgstr ""
2929

30-
#: messages.py:15
30+
#: messages.py:26
3131
msgid ""
3232
"Hello,\n"
3333
" multiline!\n"
3434
msgstr ""
3535

36-
#: messages.py:29
36+
#: messages.py:46 messages.py:89 messages.py:90 messages.py:93 messages.py:94
37+
#: messages.py:99
38+
msgid "foo"
39+
msgid_plural "foos"
40+
msgstr[0] ""
41+
msgstr[1] ""
42+
43+
#: messages.py:47
44+
msgid "something"
45+
msgstr ""
46+
47+
#: messages.py:50
3748
msgid "Hello, {}!"
3849
msgstr ""
3950

40-
#: messages.py:33
51+
#: messages.py:54
4152
msgid "1"
4253
msgstr ""
4354

44-
#: messages.py:33
55+
#: messages.py:54
4556
msgid "2"
4657
msgstr ""
4758

48-
#: messages.py:34 messages.py:35
59+
#: messages.py:55 messages.py:56
4960
msgid "A"
5061
msgstr ""
5162

52-
#: messages.py:34 messages.py:35
63+
#: messages.py:55 messages.py:56
5364
msgid "B"
5465
msgstr ""
5566

56-
#: messages.py:36
67+
#: messages.py:57
5768
msgid "set"
5869
msgstr ""
5970

60-
#: messages.py:42
71+
#: messages.py:63
6172
msgid "nested string"
6273
msgstr ""
6374

64-
#: messages.py:47
75+
#: messages.py:68
6576
msgid "baz"
6677
msgstr ""
6778

79+
#: messages.py:91 messages.py:92 messages.py:95 messages.py:96
80+
msgctxt "context"
81+
msgid "foo"
82+
msgid_plural "foos"
83+
msgstr[0] ""
84+
msgstr[1] ""
85+
86+
#: messages.py:100
87+
msgid "domain foo"
88+
msgstr ""
89+

Lib/test/test_tools/i18n_data/messages.py

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,16 @@
11
# Test message extraction
2-
from gettext import gettext as _
2+
from gettext import (
3+
gettext,
4+
ngettext,
5+
pgettext,
6+
npgettext,
7+
dgettext,
8+
dngettext,
9+
dpgettext,
10+
dnpgettext
11+
)
12+
13+
_ = gettext
314

415
# Empty string
516
_("")
@@ -21,13 +32,23 @@
2132
_(None)
2233
_(1)
2334
_(False)
24-
_(x="kwargs are not allowed")
35+
_(("invalid"))
36+
_(["invalid"])
37+
_({"invalid"})
38+
_("string"[3])
39+
_("string"[:3])
40+
_({"string": "foo"})
41+
42+
# pygettext does not allow keyword arguments, but both xgettext and pybabel do
43+
_(x="kwargs work!")
44+
45+
# Unusual, but valid arguments
2546
_("foo", "bar")
2647
_("something", x="something else")
2748

2849
# .format()
2950
_("Hello, {}!").format("world") # valid
30-
_("Hello, {}!".format("world")) # invalid
51+
_("Hello, {}!".format("world")) # invalid, but xgettext and pybabel extract the first string
3152

3253
# Nested structures
3354
_("1"), _("2")
@@ -62,3 +83,28 @@ def _(x):
6283

6384
def _(x="don't extract me"):
6485
pass
86+
87+
88+
# Other gettext functions
89+
gettext("foo")
90+
ngettext("foo", "foos", 1)
91+
pgettext("context", "foo")
92+
npgettext("context", "foo", "foos", 1)
93+
dgettext("domain", "foo")
94+
dngettext("domain", "foo", "foos", 1)
95+
dpgettext("domain", "context", "foo")
96+
dnpgettext("domain", "context", "foo", "foos", 1)
97+
98+
# Complex arguments
99+
ngettext("foo", "foos", 42 + (10 - 20))
100+
dgettext(["some", {"complex"}, ("argument",)], "domain foo")
101+
102+
# Invalid calls which are not extracted
103+
gettext()
104+
ngettext('foo')
105+
pgettext('context')
106+
npgettext('context', 'foo')
107+
dgettext('domain')
108+
dngettext('domain', 'foo')
109+
dpgettext('domain', 'context')
110+
dnpgettext('domain', 'context', 'foo')

Lib/test/test_tools/test_i18n.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -332,14 +332,14 @@ def test_calls_in_fstring_with_multiple_args(self):
332332
msgids = self.extract_docstrings_from_str(dedent('''\
333333
f"{_('foo', 'bar')}"
334334
'''))
335-
self.assertNotIn('foo', msgids)
335+
self.assertIn('foo', msgids)
336336
self.assertNotIn('bar', msgids)
337337

338338
def test_calls_in_fstring_with_keyword_args(self):
339339
msgids = self.extract_docstrings_from_str(dedent('''\
340340
f"{_('foo', bar='baz')}"
341341
'''))
342-
self.assertNotIn('foo', msgids)
342+
self.assertIn('foo', msgids)
343343
self.assertNotIn('bar', msgids)
344344
self.assertNotIn('baz', msgids)
345345

Lib/test/translationdata/argparse/msgids.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ argument %(argument_name)s: %(message)s
88
argument '%(argument_name)s' is deprecated
99
can't open '%(filename)s': %(error)s
1010
command '%(parser_name)s' is deprecated
11+
conflicting option string: %s
12+
expected %s argument
1113
expected at least one argument
1214
expected at most one argument
1315
expected one argument

Lib/test/translationdata/optparse/msgids.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
%(option)s option requires %(number)d argument
12
%prog [options]
23
%s option does not take a value
34
Options

0 commit comments

Comments
 (0)