1
1
"""Test cases for building an C extension and running it."""
2
2
3
+ import ast
3
4
import glob
4
5
import os .path
5
6
import platform
7
+ import re
6
8
import subprocess
7
9
import contextlib
8
10
import shutil
9
11
import sys
10
12
from typing import Any , Iterator , List , cast
11
13
12
14
from mypy import build
13
- from mypy .test .data import DataDrivenTestCase
15
+ from mypy .test .data import DataDrivenTestCase , UpdateFile
14
16
from mypy .test .config import test_temp_dir
15
17
from mypy .errors import CompileError
16
18
from mypy .options import Options
19
+ from mypy .test .helpers import copy_and_fudge_mtime , assert_module_equivalence
17
20
18
21
from mypyc import emitmodule
19
22
from mypyc .options import CompilerOptions
@@ -109,14 +112,45 @@ def run_case(self, testcase: DataDrivenTestCase) -> None:
109
112
# by chdiring into tmp/
110
113
with use_custom_builtins (os .path .join (self .data_prefix , ICODE_GEN_BUILTINS ), testcase ), (
111
114
chdir_manager ('tmp' )):
112
- os .mkdir (WORKDIR )
113
115
self .run_case_inner (testcase )
114
116
115
117
def run_case_inner (self , testcase : DataDrivenTestCase ) -> None :
116
- bench = testcase . config . getoption ( '--bench' , False ) and 'Benchmark' in testcase . name
118
+ os . mkdir ( WORKDIR )
117
119
118
120
text = '\n ' .join (testcase .input )
119
121
122
+ with open ('native.py' , 'w' , encoding = 'utf-8' ) as f :
123
+ f .write (text )
124
+ with open ('interpreted.py' , 'w' , encoding = 'utf-8' ) as f :
125
+ f .write (text )
126
+
127
+ shutil .copyfile (TESTUTIL_PATH , 'testutil.py' )
128
+
129
+ step = 1
130
+ self .run_case_step (testcase , step )
131
+
132
+ steps = testcase .find_steps ()
133
+ if steps == [[]]:
134
+ steps = []
135
+
136
+ for operations in steps :
137
+ step += 1
138
+ with chdir_manager ('..' ):
139
+ for op in operations :
140
+ if isinstance (op , UpdateFile ):
141
+ # Modify/create file
142
+ copy_and_fudge_mtime (op .source_path , op .target_path )
143
+ else :
144
+ # Delete file
145
+ try :
146
+ os .remove (op .path )
147
+ except FileNotFoundError :
148
+ pass
149
+ self .run_case_step (testcase , step )
150
+
151
+ def run_case_step (self , testcase : DataDrivenTestCase , incremental_step : int ) -> None :
152
+ bench = testcase .config .getoption ('--bench' , False ) and 'Benchmark' in testcase .name
153
+
120
154
options = Options ()
121
155
options .use_builtins_fixtures = True
122
156
options .show_traceback = True
@@ -128,43 +162,39 @@ def run_case_inner(self, testcase: DataDrivenTestCase) -> None:
128
162
options .python_version = max (sys .version_info [:2 ], (3 , 6 ))
129
163
options .export_types = True
130
164
options .preserve_asts = True
165
+ options .incremental = False
131
166
132
167
# Avoid checking modules/packages named 'unchecked', to provide a way
133
168
# to test interacting with code we don't have types for.
134
169
options .per_module_options ['unchecked.*' ] = {'follow_imports' : 'error' }
135
170
136
- source_path = 'native.py'
137
- with open (source_path , 'w' , encoding = 'utf-8' ) as f :
138
- f .write (text )
139
- with open ('interpreted.py' , 'w' , encoding = 'utf-8' ) as f :
140
- f .write (text )
141
-
142
- shutil .copyfile (TESTUTIL_PATH , 'testutil.py' )
143
-
144
- source = build .BuildSource (source_path , 'native' , text )
171
+ source = build .BuildSource ('native.py' , 'native' , None )
145
172
sources = [source ]
146
173
module_names = ['native' ]
147
- module_paths = [os . path . abspath ( 'native.py' ) ]
174
+ module_paths = ['native.py' ]
148
175
149
176
# Hard code another module name to compile in the same compilation unit.
150
177
to_delete = []
151
178
for fn , text in testcase .files :
152
179
fn = os .path .relpath (fn , test_temp_dir )
153
180
154
- if os .path .basename (fn ).startswith ('other' ):
181
+ if os .path .basename (fn ).startswith ('other' ) and fn . endswith ( '.py' ) :
155
182
name = os .path .basename (fn ).split ('.' )[0 ]
156
183
module_names .append (name )
157
- sources .append (build .BuildSource (fn , name , text ))
184
+ sources .append (build .BuildSource (fn , name , None ))
158
185
to_delete .append (fn )
159
- module_paths .append (os . path . abspath ( fn ) )
186
+ module_paths .append (fn )
160
187
161
188
shutil .copyfile (fn ,
162
189
os .path .join (os .path .dirname (fn ), name + '_interpreted.py' ))
163
190
164
191
for source in sources :
165
192
options .per_module_options .setdefault (source .module , {})['mypyc' ] = True
166
193
167
- groups = construct_groups (sources , self .separate , len (module_names ) > 1 )
194
+ separate = (self .get_separate ('\n ' .join (testcase .input ), incremental_step ) if self .separate
195
+ else False )
196
+
197
+ groups = construct_groups (sources , separate , len (module_names ) > 1 )
168
198
169
199
try :
170
200
result = emitmodule .parse_and_typecheck (
@@ -193,7 +223,7 @@ def run_case_inner(self, testcase: DataDrivenTestCase) -> None:
193
223
setup_file = os .path .abspath (os .path .join (WORKDIR , 'setup.py' ))
194
224
# We pass the C file information to the build script via setup.py unfortunately
195
225
with open (setup_file , 'w' , encoding = 'utf-8' ) as f :
196
- f .write (setup_format .format (module_paths , self . separate , cfiles , self .multi_file ))
226
+ f .write (setup_format .format (module_paths , separate , cfiles , self .multi_file ))
197
227
198
228
if not run_setup (setup_file , ['build_ext' , '--inplace' ]):
199
229
if testcase .config .getoption ('--mypyc-showc' ):
@@ -204,9 +234,6 @@ def run_case_inner(self, testcase: DataDrivenTestCase) -> None:
204
234
suffix = 'pyd' if sys .platform == 'win32' else 'so'
205
235
assert glob .glob ('native.*.{}' .format (suffix ))
206
236
207
- for p in to_delete :
208
- os .remove (p )
209
-
210
237
driver_path = 'driver.py'
211
238
env = os .environ .copy ()
212
239
env ['MYPYC_RUN_BENCH' ] = '1' if bench else '0'
@@ -240,10 +267,41 @@ def run_case_inner(self, testcase: DataDrivenTestCase) -> None:
240
267
print ('Test output:' )
241
268
print (output )
242
269
else :
243
- assert_test_output (testcase , outlines , 'Invalid output' )
270
+ if incremental_step == 1 :
271
+ msg = 'Invalid output'
272
+ expected = testcase .output
273
+ else :
274
+ msg = 'Invalid output (step {})' .format (incremental_step )
275
+ expected = testcase .output2 .get (incremental_step , [])
276
+
277
+ assert_test_output (testcase , outlines , msg , expected )
278
+
279
+ if incremental_step > 1 and options .incremental :
280
+ suffix = '' if incremental_step == 2 else str (incremental_step - 1 )
281
+ expected_rechecked = testcase .expected_rechecked_modules .get (incremental_step - 1 )
282
+ if expected_rechecked is not None :
283
+ assert_module_equivalence (
284
+ 'rechecked' + suffix ,
285
+ expected_rechecked , result .manager .rechecked_modules )
286
+ expected_stale = testcase .expected_stale_modules .get (incremental_step - 1 )
287
+ if expected_stale is not None :
288
+ assert_module_equivalence (
289
+ 'stale' + suffix ,
290
+ expected_stale , result .manager .stale_modules )
244
291
245
292
assert proc .returncode == 0
246
293
294
+ def get_separate (self , program_text : str ,
295
+ incremental_step : int ) -> Any :
296
+ template = r'# separate{}: (\[.*\])$'
297
+ m = re .search (template .format (incremental_step ), program_text , flags = re .MULTILINE )
298
+ if not m :
299
+ m = re .search (template .format ('' ), program_text , flags = re .MULTILINE )
300
+ if m :
301
+ return ast .literal_eval (m .group (1 ))
302
+ else :
303
+ return True
304
+
247
305
248
306
# Run the main multi-module tests in multi-file compliation mode
249
307
class TestRunMultiFile (TestRun ):
0 commit comments