Skip to content

Commit fef82c9

Browse files
authored
Merge branch 'main' into gh-127541-os-walk
2 parents d14831c + 5c89adf commit fef82c9

6 files changed

Lines changed: 155 additions & 111 deletions

File tree

Lib/dis.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1115,7 +1115,7 @@ def dis(self):
11151115
return output.getvalue()
11161116

11171117

1118-
def main():
1118+
def main(args=None):
11191119
import argparse
11201120

11211121
parser = argparse.ArgumentParser()
@@ -1128,7 +1128,7 @@ def main():
11281128
parser.add_argument('-S', '--specialized', action='store_true',
11291129
help='show specialized bytecode')
11301130
parser.add_argument('infile', nargs='?', default='-')
1131-
args = parser.parse_args()
1131+
args = parser.parse_args(args=args)
11321132
if args.infile == '-':
11331133
name = '<stdin>'
11341134
source = sys.stdin.buffer.read()

Lib/pathlib/_abc.py

Lines changed: 2 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
import functools
1515
import operator
16+
import posixpath
1617
from errno import EINVAL
1718
from glob import _GlobberBase, _no_recurse_symlinks
1819
from stat import S_ISDIR, S_ISLNK, S_ISREG, S_ISSOCK, S_ISBLK, S_ISCHR, S_ISFIFO
@@ -33,59 +34,6 @@ def _is_case_sensitive(parser):
3334
return parser.normcase('Aa') == 'Aa'
3435

3536

36-
37-
class ParserBase:
38-
"""Base class for path parsers, which do low-level path manipulation.
39-
40-
Path parsers provide a subset of the os.path API, specifically those
41-
functions needed to provide PurePathBase functionality. Each PurePathBase
42-
subclass references its path parser via a 'parser' class attribute.
43-
44-
Every method in this base class raises an UnsupportedOperation exception.
45-
"""
46-
47-
@classmethod
48-
def _unsupported_msg(cls, attribute):
49-
return f"{cls.__name__}.{attribute} is unsupported"
50-
51-
@property
52-
def sep(self):
53-
"""The character used to separate path components."""
54-
raise UnsupportedOperation(self._unsupported_msg('sep'))
55-
56-
def join(self, path, *paths):
57-
"""Join path segments."""
58-
raise UnsupportedOperation(self._unsupported_msg('join()'))
59-
60-
def split(self, path):
61-
"""Split the path into a pair (head, tail), where *head* is everything
62-
before the final path separator, and *tail* is everything after.
63-
Either part may be empty.
64-
"""
65-
raise UnsupportedOperation(self._unsupported_msg('split()'))
66-
67-
def splitdrive(self, path):
68-
"""Split the path into a 2-item tuple (drive, tail), where *drive* is
69-
a device name or mount point, and *tail* is everything after the
70-
drive. Either part may be empty."""
71-
raise UnsupportedOperation(self._unsupported_msg('splitdrive()'))
72-
73-
def splitext(self, path):
74-
"""Split the path into a pair (root, ext), where *ext* is empty or
75-
begins with a period and contains at most one period,
76-
and *root* is everything before the extension."""
77-
raise UnsupportedOperation(self._unsupported_msg('splitext()'))
78-
79-
def normcase(self, path):
80-
"""Normalize the case of the path."""
81-
raise UnsupportedOperation(self._unsupported_msg('normcase()'))
82-
83-
def isabs(self, path):
84-
"""Returns whether the path is absolute, i.e. unaffected by the
85-
current directory or drive."""
86-
raise UnsupportedOperation(self._unsupported_msg('isabs()'))
87-
88-
8937
class PathGlobber(_GlobberBase):
9038
"""
9139
Class providing shell-style globbing for path objects.
@@ -115,7 +63,7 @@ class PurePathBase:
11563
# the `__init__()` method.
11664
'_raw_paths',
11765
)
118-
parser = ParserBase()
66+
parser = posixpath
11967
_globber = PathGlobber
12068

12169
def __init__(self, *args):

Lib/pathlib/_types.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
"""
2+
Protocols for supporting classes in pathlib.
3+
"""
4+
from typing import Protocol, runtime_checkable
5+
6+
7+
@runtime_checkable
8+
class Parser(Protocol):
9+
"""Protocol for path parsers, which do low-level path manipulation.
10+
11+
Path parsers provide a subset of the os.path API, specifically those
12+
functions needed to provide PurePathBase functionality. Each PurePathBase
13+
subclass references its path parser via a 'parser' class attribute.
14+
"""
15+
16+
sep: str
17+
def join(self, path: str, *paths: str) -> str: ...
18+
def split(self, path: str) -> tuple[str, str]: ...
19+
def splitdrive(self, path: str) -> tuple[str, str]: ...
20+
def splitext(self, path: str) -> tuple[str, str]: ...
21+
def normcase(self, path: str) -> str: ...
22+
def isabs(self, path: str) -> bool: ...

Lib/test/test_dis.py

Lines changed: 120 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,19 @@
55
import dis
66
import functools
77
import io
8+
import itertools
9+
import opcode
810
import re
911
import sys
12+
import tempfile
13+
import textwrap
1014
import types
1115
import unittest
1216
from test.support import (captured_stdout, requires_debug_ranges,
13-
requires_specialization, cpython_only)
17+
requires_specialization, cpython_only,
18+
os_helper)
1419
from test.support.bytecode_helper import BytecodeTestCase
1520

16-
import opcode
1721

1822
CACHE = dis.opmap["CACHE"]
1923

@@ -2426,5 +2430,119 @@ def _unroll_caches_as_Instructions(instrs, show_caches=False):
24262430
False, None, None, instr.positions)
24272431

24282432

2433+
class TestDisCLI(unittest.TestCase):
2434+
2435+
def setUp(self):
2436+
self.filename = tempfile.mktemp()
2437+
self.addCleanup(os_helper.unlink, self.filename)
2438+
2439+
@staticmethod
2440+
def text_normalize(string):
2441+
"""Dedent *string* and strip it from its surrounding whitespaces.
2442+
2443+
This method is used by the other utility functions so that any
2444+
string to write or to match against can be freely indented.
2445+
"""
2446+
return textwrap.dedent(string).strip()
2447+
2448+
def set_source(self, content):
2449+
with open(self.filename, 'w') as fp:
2450+
fp.write(self.text_normalize(content))
2451+
2452+
def invoke_dis(self, *flags):
2453+
output = io.StringIO()
2454+
with contextlib.redirect_stdout(output):
2455+
dis.main(args=[*flags, self.filename])
2456+
return self.text_normalize(output.getvalue())
2457+
2458+
def check_output(self, source, expect, *flags):
2459+
with self.subTest(source=source, flags=flags):
2460+
self.set_source(source)
2461+
res = self.invoke_dis(*flags)
2462+
expect = self.text_normalize(expect)
2463+
self.assertListEqual(res.splitlines(), expect.splitlines())
2464+
2465+
def test_invocation(self):
2466+
# test various combinations of parameters
2467+
base_flags = [
2468+
('-C', '--show-caches'),
2469+
('-O', '--show-offsets'),
2470+
('-P', '--show-positions'),
2471+
('-S', '--specialized'),
2472+
]
2473+
2474+
self.set_source('''
2475+
def f():
2476+
print(x)
2477+
return None
2478+
''')
2479+
2480+
for r in range(1, len(base_flags) + 1):
2481+
for choices in itertools.combinations(base_flags, r=r):
2482+
for args in itertools.product(*choices):
2483+
with self.subTest(args=args[1:]):
2484+
_ = self.invoke_dis(*args)
2485+
2486+
with self.assertRaises(SystemExit):
2487+
# suppress argparse error message
2488+
with contextlib.redirect_stderr(io.StringIO()):
2489+
_ = self.invoke_dis('--unknown')
2490+
2491+
def test_show_cache(self):
2492+
# test 'python -m dis -C/--show-caches'
2493+
source = 'print()'
2494+
expect = '''
2495+
0 RESUME 0
2496+
2497+
1 LOAD_NAME 0 (print)
2498+
PUSH_NULL
2499+
CALL 0
2500+
CACHE 0 (counter: 0)
2501+
CACHE 0 (func_version: 0)
2502+
CACHE 0
2503+
POP_TOP
2504+
LOAD_CONST 0 (None)
2505+
RETURN_VALUE
2506+
'''
2507+
for flag in ['-C', '--show-caches']:
2508+
self.check_output(source, expect, flag)
2509+
2510+
def test_show_offsets(self):
2511+
# test 'python -m dis -O/--show-offsets'
2512+
source = 'pass'
2513+
expect = '''
2514+
0 0 RESUME 0
2515+
2516+
1 2 LOAD_CONST 0 (None)
2517+
4 RETURN_VALUE
2518+
'''
2519+
for flag in ['-O', '--show-offsets']:
2520+
self.check_output(source, expect, flag)
2521+
2522+
def test_show_positions(self):
2523+
# test 'python -m dis -P/--show-positions'
2524+
source = 'pass'
2525+
expect = '''
2526+
0:0-1:0 RESUME 0
2527+
2528+
1:0-1:4 LOAD_CONST 0 (None)
2529+
1:0-1:4 RETURN_VALUE
2530+
'''
2531+
for flag in ['-P', '--show-positions']:
2532+
self.check_output(source, expect, flag)
2533+
2534+
def test_specialized_code(self):
2535+
# test 'python -m dis -S/--specialized'
2536+
source = 'pass'
2537+
expect = '''
2538+
0 RESUME 0
2539+
2540+
1 LOAD_CONST_IMMORTAL 0 (None)
2541+
RETURN_VALUE
2542+
'''
2543+
for flag in ['-S', '--specialized']:
2544+
self.check_output(source, expect, flag)
2545+
2546+
24292547
if __name__ == "__main__":
24302548
unittest.main()

Lib/test/test_pathlib/test_pathlib_abc.py

Lines changed: 8 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
import stat
66
import unittest
77

8-
from pathlib._abc import UnsupportedOperation, ParserBase, PurePathBase, PathBase
8+
from pathlib._abc import UnsupportedOperation, PurePathBase, PathBase
9+
from pathlib._types import Parser
910
import posixpath
1011

1112
from test.support.os_helper import TESTFN
@@ -31,22 +32,6 @@ def test_is_notimplemented(self):
3132
self.assertTrue(issubclass(UnsupportedOperation, NotImplementedError))
3233
self.assertTrue(isinstance(UnsupportedOperation(), NotImplementedError))
3334

34-
35-
class ParserBaseTest(unittest.TestCase):
36-
cls = ParserBase
37-
38-
def test_unsupported_operation(self):
39-
m = self.cls()
40-
e = UnsupportedOperation
41-
with self.assertRaises(e):
42-
m.sep
43-
self.assertRaises(e, m.join, 'foo')
44-
self.assertRaises(e, m.split, 'foo')
45-
self.assertRaises(e, m.splitdrive, 'foo')
46-
self.assertRaises(e, m.splitext, 'foo')
47-
self.assertRaises(e, m.normcase, 'foo')
48-
self.assertRaises(e, m.isabs, 'foo')
49-
5035
#
5136
# Tests for the pure classes.
5237
#
@@ -55,37 +40,6 @@ def test_unsupported_operation(self):
5540
class PurePathBaseTest(unittest.TestCase):
5641
cls = PurePathBase
5742

58-
def test_unsupported_operation_pure(self):
59-
p = self.cls('foo')
60-
e = UnsupportedOperation
61-
with self.assertRaises(e):
62-
p.drive
63-
with self.assertRaises(e):
64-
p.root
65-
with self.assertRaises(e):
66-
p.anchor
67-
with self.assertRaises(e):
68-
p.parts
69-
with self.assertRaises(e):
70-
p.parent
71-
with self.assertRaises(e):
72-
p.parents
73-
with self.assertRaises(e):
74-
p.name
75-
with self.assertRaises(e):
76-
p.stem
77-
with self.assertRaises(e):
78-
p.suffix
79-
with self.assertRaises(e):
80-
p.suffixes
81-
self.assertRaises(e, p.with_name, 'bar')
82-
self.assertRaises(e, p.with_stem, 'bar')
83-
self.assertRaises(e, p.with_suffix, '.txt')
84-
self.assertRaises(e, p.relative_to, '')
85-
self.assertRaises(e, p.is_relative_to, '')
86-
self.assertRaises(e, p.is_absolute)
87-
self.assertRaises(e, p.match, '*')
88-
8943
def test_magic_methods(self):
9044
P = self.cls
9145
self.assertFalse(hasattr(P, '__fspath__'))
@@ -100,12 +54,11 @@ def test_magic_methods(self):
10054
self.assertIs(P.__ge__, object.__ge__)
10155

10256
def test_parser(self):
103-
self.assertIsInstance(self.cls.parser, ParserBase)
57+
self.assertIs(self.cls.parser, posixpath)
10458

10559

10660
class DummyPurePath(PurePathBase):
10761
__slots__ = ()
108-
parser = posixpath
10962

11063
def __eq__(self, other):
11164
if not isinstance(other, DummyPurePath):
@@ -136,6 +89,9 @@ def setUp(self):
13689
self.sep = self.parser.sep
13790
self.altsep = self.parser.altsep
13891

92+
def test_parser(self):
93+
self.assertIsInstance(self.cls.parser, Parser)
94+
13995
def test_constructor_common(self):
14096
P = self.cls
14197
p = P('a')
@@ -1359,8 +1315,8 @@ def test_unsupported_operation(self):
13591315
self.assertRaises(e, p.write_bytes, b'foo')
13601316
self.assertRaises(e, p.write_text, 'foo')
13611317
self.assertRaises(e, p.iterdir)
1362-
self.assertRaises(e, p.glob, '*')
1363-
self.assertRaises(e, p.rglob, '*')
1318+
self.assertRaises(e, lambda: list(p.glob('*')))
1319+
self.assertRaises(e, lambda: list(p.rglob('*')))
13641320
self.assertRaises(e, lambda: list(p.walk()))
13651321
self.assertRaises(e, p.expanduser)
13661322
self.assertRaises(e, p.readlink)
@@ -1411,7 +1367,6 @@ class DummyPath(PathBase):
14111367
memory.
14121368
"""
14131369
__slots__ = ()
1414-
parser = posixpath
14151370

14161371
_files = {}
14171372
_directories = {}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add tests for the :mod:`dis` command-line interface. Patch by Bénédikt Tran.

0 commit comments

Comments
 (0)