|
2 | 2 | """#2047: :file:`util/_actions.py`, the GitHub Actions plumbing the ``util/`` scripts share. |
3 | 3 |
|
4 | 4 | * :class:`TestStandardLibraryOnly` holds the helper to the standard library and |
5 | | - to 3.7's syntax, since :file:`util/pip_install.py` imports it on every |
6 | | - interpreter the matrices hold, before anything is installed. |
| 5 | + to the syntax of ``requires-python``'s floor, 3.8, since |
| 6 | + :file:`util/pip_install.py` imports it on every interpreter the matrices hold, |
| 7 | + before anything is installed. |
7 | 8 | * :class:`TestCommands`, :class:`TestFiles` and :class:`TestRun` pin each |
8 | 9 | function's exact output, GitHub's escaping included. |
9 | 10 | * :class:`TestScriptsImportIt` loads each script that imports the helper in the |
|
26 | 27 | import subprocess # nosec: B404 |
27 | 28 | import sys |
28 | 29 | import tempfile |
29 | | -import tokenize |
30 | 30 | import unittest |
31 | 31 | from typing import TYPE_CHECKING |
32 | 32 | from unittest import mock |
33 | 33 |
|
34 | 34 | from tests._workflows import WORKFLOWS, load |
| 35 | +from tests.project.test_requires_python_floor_2051 import FLOOR |
35 | 36 | from util import _actions |
36 | 37 |
|
37 | 38 | if TYPE_CHECKING: |
38 | | - from typing import Any, Iterator, Optional |
| 39 | + from typing import Any, Iterator |
39 | 40 |
|
40 | 41 | #: Test tier (#1958): reads and runs the tree's own scripts, a question about the tree. |
41 | 42 | __tier__ = 'LATEST' |
|
48 | 49 | 'python_versions', 'regenerate_samples', 'release_conda', 'release_published', 'release_reset_build', |
49 | 50 | 'release_status', 'release_tags', 'release_tiers', 'release_upload', 'superseded', |
50 | 51 | 'update_python_versions', 'vendor_code_changed', 'vendor_test_gate', 'verdict_covers_checkout') |
51 | | -#: The top-level modules the helper may import, each in Python 3.7's standard |
| 52 | +#: The top-level modules the helper may import, each in Python 3.8's standard |
52 | 53 | #: library: the running interpreter's :data:`sys.stdlib_module_names` would |
53 | | -#: pass ``tomllib``, which 3.7 lacks. |
54 | | -STDLIB_37 = frozenset({'__future__', 'contextlib', 'os', 'subprocess', 'sys', 'typing', 'uuid'}) |
| 54 | +#: pass ``tomllib``, which 3.8 lacks. |
| 55 | +STDLIB_38 = frozenset({'__future__', 'contextlib', 'os', 'subprocess', 'sys', 'typing', 'uuid'}) |
55 | 56 | #: A trivial run of each script, ``(arguments, environment)``, that gets past its imports. |
56 | 57 | RUNS = { |
57 | 58 | 'apt_packages': (['--help'], {}), |
@@ -106,65 +107,25 @@ def imported(source: 'str') -> 'set[str]': |
106 | 107 | return names |
107 | 108 |
|
108 | 109 |
|
109 | | -def self_documenting(source: 'str') -> 'list[str]': |
110 | | - """Each f-string replacement field in ``source`` that ends in 3.8's ``=``, as ``line:col``. |
111 | | -
|
112 | | - ``ast.parse(..., feature_version=(3, 7))`` accepts ``f'{x=}'``. Since 3.12 an |
113 | | - f-string tokenizes into its fields (PEP 701), and there the form is an ``=`` |
114 | | - token followed at once by the ``}``, ``!`` or ``:`` that ends the expression; |
115 | | - ``==``, ``:=`` and a keyword argument's ``=`` are other tokens or are followed |
116 | | - by an operand. |
117 | | -
|
118 | | - """ |
119 | | - found = [] |
120 | | - depth = 0 |
121 | | - previous = None # type: Optional[tokenize.TokenInfo] |
122 | | - for token in tokenize.generate_tokens(io.StringIO(source).readline): |
123 | | - if token.type == tokenize.FSTRING_START: # pylint: disable=no-member |
124 | | - depth += 1 |
125 | | - elif token.type == tokenize.FSTRING_END: # pylint: disable=no-member |
126 | | - depth -= 1 |
127 | | - elif depth and token.type == tokenize.OP and token.string in ('}', '!', ':') \ |
128 | | - and previous is not None and previous.type == tokenize.OP and previous.string == '=': |
129 | | - found.append('%d:%d' % previous.start) |
130 | | - if token.type not in (tokenize.NL, tokenize.NEWLINE, tokenize.COMMENT): |
131 | | - previous = token |
132 | | - return found |
133 | | - |
134 | | - |
135 | 110 | class TestStandardLibraryOnly(unittest.TestCase): |
136 | | - """The helper imports nothing but 3.7's standard library, and is written in 3.7's syntax.""" |
| 111 | + """The helper imports nothing but 3.8's standard library, and is written in 3.8's syntax.""" |
137 | 112 |
|
138 | 113 | def setUp(self) -> None: |
139 | 114 | self.source = HELPER.read_text(encoding='utf-8') |
140 | 115 |
|
141 | | - def test_it_parses_as_python_3_7(self) -> None: |
142 | | - ast.parse(self.source, filename=str(HELPER), feature_version=(3, 7)) |
| 116 | + def test_it_parses_as_python_3_8(self) -> None: |
| 117 | + ast.parse(self.source, filename=str(HELPER), feature_version=FLOOR) |
143 | 118 |
|
144 | | - def test_it_imports_only_what_3_7_has(self) -> None: |
145 | | - extra = sorted(imported(self.source) - STDLIB_37) |
| 119 | + def test_it_imports_only_what_3_8_has(self) -> None: |
| 120 | + extra = sorted(imported(self.source) - STDLIB_38) |
146 | 121 | self.assertEqual(extra, [], f'util/_actions.py imports {extra}: check that each is in the standard ' |
147 | | - 'library of Python 3.7 and every interpreter the matrices hold, then add ' |
148 | | - 'it to STDLIB_37 in this test') |
| 122 | + 'library of Python 3.8 and every interpreter the matrices hold, then add ' |
| 123 | + 'it to STDLIB_38 in this test') |
149 | 124 | self.assertIn('uuid', imported(self.source), 'the import inside a function is seen') |
150 | 125 |
|
151 | 126 | def test_the_import_check_sees_a_newer_module(self) -> None: |
152 | | - self.assertEqual(sorted(imported('import tomllib\nfrom os import path\n') - STDLIB_37), ['tomllib']) |
153 | | - self.assertEqual(sorted(imported('def f():\n from . import x\n') - STDLIB_37), ['.']) |
154 | | - |
155 | | - @unittest.skipUnless(hasattr(tokenize, 'FSTRING_START'), 'f-strings tokenize into fields from 3.12 (PEP 701)') |
156 | | - def test_it_has_no_self_documenting_f_string(self) -> None: |
157 | | - self.assertEqual(self_documenting(self.source), []) |
158 | | - |
159 | | - @unittest.skipUnless(hasattr(tokenize, 'FSTRING_START'), 'f-strings tokenize into fields from 3.12 (PEP 701)') |
160 | | - def test_the_f_string_check_tells_the_forms_apart(self) -> None: |
161 | | - for source in ('f"{x=}"', 'f"{x = }"', 'f"{x=!r}"', 'f"{x=:>4}"', 'f"a {f(y)=} b"', 'f"{f\'{x=}\'}"'): |
162 | | - with self.subTest(source=source): |
163 | | - self.assertEqual(len(self_documenting(source + '\n')), 1, source) |
164 | | - for source in ('f"{a==b}"', 'f"x={x!r}"', 'f"{x:=^5}"', 'f"{(y:=1)}"', 'f"{g(a=1)}"', 'f"{x!r:=>4}"', |
165 | | - 'x = {"a": 1}', 'def f(a=1): return {a: a}', "f'{d[\"k=\"]}'"): |
166 | | - with self.subTest(source=source): |
167 | | - self.assertEqual(self_documenting(source + '\n'), [], source) |
| 127 | + self.assertEqual(sorted(imported('import tomllib\nfrom os import path\n') - STDLIB_38), ['tomllib']) |
| 128 | + self.assertEqual(sorted(imported('def f():\n from . import x\n') - STDLIB_38), ['.']) |
168 | 129 |
|
169 | 130 |
|
170 | 131 | class TestCommands(unittest.TestCase): |
|
0 commit comments