Repository navigation
Expand file tree
/
Copy pathtest_copy_test.py
More file actions
206 lines (190 loc) · 8.54 KB
/
Copy pathtest_copy_test.py
File metadata and controls
206 lines (190 loc) · 8.54 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
"""Tests for bounded, hash-pinned canonical test copying."""
import ast
import contextlib
import hashlib
import io
import tempfile
import unittest
from pathlib import Path
from update_lib.cmd_copy_test import copy_test, main
class TestCopyTest(unittest.TestCase):
destination = '''# Destination-only header
class Finder:
if int.__doc__:
def example():
r"""Count is below 800. π\\n"""
def untouched(self):
return "destination"
'''
source = '''# Canonical header must not be copied
class Finder:
if int.__doc__:
def example():
r"""Count is below 850. π\\n"""
def untouched(self):
return "canonical"
'''
def test_complete_definition_and_surroundings(self):
result = copy_test(self.source, self.destination, "Finder.example")
self.assertEqual(result, self.destination.replace("below 800", "below 850"))
self.assertIn('return "destination"', result)
source_method = ast.parse(self.source).body[0].body[0].body[0]
result_method = ast.parse(result).body[0].body[0].body[0]
self.assertEqual(
ast.get_source_segment(self.source, source_method),
ast.get_source_segment(result, result_method),
)
def test_guard_mismatch_is_rejected(self):
with self.assertRaisesRegex(ValueError, "guards differ"):
copy_test(
self.source.replace("if int.__doc__", "if enabled"),
self.destination,
"Finder.example",
)
def test_missing_or_ambiguous_definition_is_rejected(self):
for source in ("class Other: pass\n", self.source + self.source):
with self.subTest(source=source):
with self.assertRaisesRegex(ValueError, "Expected one class"):
copy_test(source, self.destination, "Finder.example")
duplicate = self.source.replace(
" def untouched(self):",
" def example():\n pass\n\n def untouched(self):",
)
with self.assertRaisesRegex(ValueError, "Expected one method"):
copy_test(duplicate, self.destination, "Finder.example")
def test_decorator_and_signature_changes_are_rejected(self):
decorated = self.source.replace(
" def example():", " @decorator\n def example():"
)
with self.assertRaisesRegex(ValueError, "decorators differ"):
copy_test(decorated, self.destination, "Finder.example")
with self.assertRaisesRegex(ValueError, "signatures differ"):
copy_test(
self.source.replace("def example():", "def example(arg):"),
self.destination,
"Finder.example",
)
def test_nested_local_scope_is_not_selected(self):
local = "def factory():\n" + "".join(
" " + line for line in self.source.splitlines(True)
)
with self.assertRaisesRegex(ValueError, "Expected one class"):
copy_test(local, self.destination, "Finder.example")
def test_decorator_comments_must_match_exactly(self):
for source_marker, destination_marker in (
(
"@unittest.expectedFailure",
"@unittest.expectedFailure # TODO: RUSTPYTHON; reason",
),
(
"@unittest.expectedFailure # original reason",
"@unittest.expectedFailure # changed reason",
),
(
"@unittest.expectedFailure\n @other",
"@unittest.expectedFailure\n # marker rationale\n @other",
),
(
"@unittest.expectedFailureIf(\n condition,\n 'reason',\n )",
"@unittest.expectedFailureIf(\n condition, # project limitation\n 'reason',\n )",
),
):
with self.subTest(destination_marker=destination_marker):
source = self.source.replace(
" def example():",
f" {source_marker}\n def example():",
)
destination = self.destination.replace(
" def example():",
f" {destination_marker}\n def example():",
)
with self.assertRaisesRegex(ValueError, "decorator text differs"):
copy_test(source, destination, "Finder.example")
def test_identical_decorator_comments_are_preserved(self):
prefix = (
" @unittest.expectedFailure # TODO: RUSTPYTHON; reason\n"
" # Keep this rationale with the marker.\n"
" def example():"
)
for declaration in ("def example():", "async def example():"):
with self.subTest(declaration=declaration):
source = self.source.replace(" def example():", prefix)
destination = self.destination.replace(" def example():", prefix)
source = source.replace("def example():", declaration)
destination = destination.replace("def example():", declaration)
self.assertEqual(
copy_test(source, destination, "Finder.example"),
destination.replace("below 800", "below 850"),
)
def test_cli_does_not_write_on_decorator_comment_mismatch(self):
with tempfile.TemporaryDirectory() as temp:
source = Path(temp) / "source.py"
destination = Path(temp) / "destination.py"
source.write_text(
self.source.replace(
" def example():",
" @unittest.expectedFailure\n def example():",
)
)
destination.write_text(
self.destination.replace(
" def example():",
" @unittest.expectedFailure # TODO: RUSTPYTHON; reason\n"
" def example():",
)
)
before = destination.read_bytes()
with (
contextlib.redirect_stderr(io.StringIO()) as errors,
self.assertRaises(SystemExit),
):
main(
[
str(source),
str(destination),
"--test",
"Finder.example",
"--source-sha256",
hashlib.sha256(source.read_bytes()).hexdigest(),
"--destination-sha256",
hashlib.sha256(before).hexdigest(),
]
)
self.assertIn("decorator text differs", errors.getvalue())
self.assertEqual(destination.read_bytes(), before)
def test_return_annotation_and_type_parameters_are_rejected(self):
for declaration in ("def example() -> int:", "def example[T]():"):
with self.subTest(declaration=declaration):
source = self.source.replace("def example():", declaration)
with self.assertRaisesRegex(ValueError, "signatures differ"):
copy_test(source, self.destination, "Finder.example")
def test_cli_hashes_and_no_write_on_stale_input(self):
with tempfile.TemporaryDirectory() as temp:
source = Path(temp) / "source.py"
destination = Path(temp) / "destination.py"
source.write_bytes(self.source.encode())
destination.write_bytes(self.destination.encode())
args = [
str(source),
str(destination),
"--test",
"Finder.example",
"--source-sha256",
hashlib.sha256(source.read_bytes()).hexdigest(),
"--destination-sha256",
"0" * 64,
]
with (
contextlib.redirect_stderr(io.StringIO()),
self.assertRaises(SystemExit),
):
main(args)
self.assertEqual(destination.read_bytes(), self.destination.encode())
args[-1] = hashlib.sha256(destination.read_bytes()).hexdigest()
with contextlib.redirect_stdout(io.StringIO()) as output:
self.assertEqual(main(args), 0)
self.assertIn('"source_sha256"', output.getvalue())
self.assertEqual(
destination.read_text(),
self.destination.replace("below 800", "below 850"),
)