1515# limitations under the License.
1616
1717import glob
18+ import io
1819import os
1920import subprocess
2021import sys
2122import unittest
2223from collections import OrderedDict
23- from concurrent . futures import ThreadPoolExecutor
24+ from multiprocessing . pool import ThreadPool
2425from pathlib import Path
25- import queue
26- import io
27- import threading
28- from functools import partial
2926
30- from scripts .test import binaryenjs
31- from scripts .test import lld
32- from scripts .test import shared
33- from scripts .test import support
34- from scripts .test import wasm2js
35- from scripts .test import wasm_opt
27+ from scripts .test import binaryenjs , lld , shared , support , wasm2js , wasm_opt
3628
3729
3830def get_changelog_version ():
@@ -41,7 +33,7 @@ def get_changelog_version():
4133 lines = [line for line in lines if len (line .split ()) == 1 ]
4234 lines = [line for line in lines if line .startswith ('v' )]
4335 version = lines [0 ][1 :]
44- print ("Parsed CHANGELOG.md version: %s" % version )
36+ print (f "Parsed CHANGELOG.md version: { version } " )
4537 return int (version )
4638
4739
@@ -55,16 +47,16 @@ def run_version_tests():
5547 not any (f .endswith (s ) for s in not_executable_suffix ) and
5648 any (os .path .basename (f ).startswith (s ) for s in executable_prefix )]
5749 executables = sorted (executables )
58- assert len ( executables )
50+ assert executables
5951
6052 changelog_version = get_changelog_version ()
6153 for e in executables :
62- print ('.. %s --version' % e )
54+ print (f '.. { e } --version' )
6355 proc = subprocess .run ([e , '--version' ], capture_output = True , text = True )
64- assert len (proc .stderr ) == 0 , 'Expected no stderr, got:\n %s' % proc .stderr
56+ assert len (proc .stderr ) == 0 , f 'Expected no stderr, got:\n { proc .stderr } '
6557 out = proc .stdout
66- assert os .path .basename (e ).replace ('.exe' , '' ) in out , 'Expected version to contain program name, got:\n %s' % out
67- assert len (out .strip ().splitlines ()) == 1 , 'Expected only version info, got:\n %s' % out
58+ assert os .path .basename (e ).replace ('.exe' , '' ) in out , f 'Expected version to contain program name, got:\n { out } '
59+ assert len (out .strip ().splitlines ()) == 1 , f 'Expected only version info, got:\n { out } '
6860 parts = out .split ()
6961 assert parts [1 ] == 'version'
7062 version = int (parts [2 ])
@@ -158,7 +150,8 @@ def run_wasm_reduce_tests():
158150 print ('..' , os .path .basename (t ))
159151 # convert to wasm
160152 support .run_command (shared .WASM_AS + [t , '-o' , 'a.wasm' , '-all' ])
161- support .run_command (shared .WASM_REDUCE + ['a.wasm' , '--command=%s b.wasm --fuzz-exec -all ' % shared .WASM_OPT [0 ], '-t' , 'b.wasm' , '-w' , 'c.wasm' , '--timeout=4' ])
153+ cmd = shared .WASM_OPT [0 ]
154+ support .run_command (shared .WASM_REDUCE + ['a.wasm' , f'--command={ cmd } b.wasm --fuzz-exec -all ' , '-t' , 'b.wasm' , '-w' , 'c.wasm' , '--timeout=4' ])
162155 expected = t + '.txt'
163156 support .run_command (shared .WASM_DIS + ['c.wasm' , '-o' , 'a.wat' ])
164157 with open ('a.wat' ) as seen :
@@ -171,14 +164,15 @@ def run_wasm_reduce_tests():
171164 # TODO: re-enable multivalue once it is better optimized
172165 support .run_command (shared .WASM_OPT + [os .path .join (shared .options .binaryen_test , 'lit/basic/signext.wast' ), '-ttf' , '-Os' , '-o' , 'a.wasm' , '--detect-features' , '--disable-multivalue' ])
173166 before = os .stat ('a.wasm' ).st_size
174- support .run_command (shared .WASM_REDUCE + ['a.wasm' , '--command=%s b.wasm --fuzz-exec --detect-features' % shared .WASM_OPT [0 ], '-t' , 'b.wasm' , '-w' , 'c.wasm' ])
167+ cmd = shared .WASM_OPT [0 ]
168+ support .run_command (shared .WASM_REDUCE + ['a.wasm' , f'--command={ cmd } b.wasm --fuzz-exec --detect-features' , '-t' , 'b.wasm' , '-w' , 'c.wasm' ])
175169 after = os .stat ('c.wasm' ).st_size
176170 # This number is a custom threshold to check if we have shrunk the
177171 # output sufficiently
178172 assert after < 0.85 * before , [before , after ]
179173
180174
181- def run_spec_test (wast , stdout = None , stderr = None ):
175+ def run_spec_test (wast , stdout = None ):
182176 cmd = shared .WASM_SHELL + [wast ]
183177 output = support .run_command (cmd , stdout = stdout , stderr = subprocess .PIPE )
184178 # filter out binaryen interpreter logging that the spec suite
@@ -187,7 +181,7 @@ def run_spec_test(wast, stdout=None, stderr=None):
187181 return '\n ' .join (filtered ) + '\n '
188182
189183
190- def run_opt_test (wast , stdout = None , stderr = None ):
184+ def run_opt_test (wast , stdout = None ):
191185 # check optimization validation
192186 cmd = shared .WASM_OPT + [wast , '-O' , '-all' , '-q' ]
193187 support .run_command (cmd , stdout = stdout )
@@ -203,7 +197,7 @@ def check_expected(actual, expected, stdout=None):
203197 shared .fail (actual , expected )
204198
205199
206- def run_one_spec_test (wast : Path , stdout = None , stderr = None ):
200+ def run_one_spec_test (wast : Path , stdout = None ):
207201 test_name = wast .name
208202
209203 # /path/to/binaryen/test/spec/foo.wast -> test-spec-foo
@@ -218,7 +212,7 @@ def run_one_spec_test(wast: Path, stdout=None, stderr=None):
218212
219213 # some spec tests should fail (actual process failure, not just assert_invalid)
220214 try :
221- actual = run_spec_test (str (wast ), stdout = stdout , stderr = stderr )
215+ actual = run_spec_test (str (wast ), stdout = stdout )
222216 except Exception as e :
223217 if ('wasm-validator error' in str (e ) or 'error: ' in str (e )) and '.fail.' in test_name :
224218 print ('<< test failed as expected >>' , file = stdout )
@@ -240,67 +234,45 @@ def run_one_spec_test(wast: Path, stdout=None, stderr=None):
240234 print (f' testing split module { i } ' , file = stdout )
241235 split_name = base_name + f'_split{ i } .wast'
242236 support .write_wast (split_name , module )
243- run_opt_test (split_name , stdout = stdout , stderr = stderr ) # also that our optimizer doesn't break on it
237+ run_opt_test (split_name , stdout = stdout ) # also that our optimizer doesn't break on it
244238
245- result_wast_file = shared .binary_format_check (split_name , verify_final_result = False , base_name = base_name , stdout = stdout , stderr = stderr )
239+ result_wast_file = shared .binary_format_check (split_name , verify_final_result = False , base_name = base_name , stdout = stdout )
246240 with open (result_wast_file ) as f :
247241 result_wast = f .read ()
248242 # add the asserts, and verify that the test still passes
249243 transformed_spec_file .write (result_wast + '\n ' + '\n ' .join (asserts ))
250244
251245 # compare all the outputs to the expected output
252- actual = run_spec_test (transformed_path , stdout = stdout , stderr = stderr )
246+ actual = run_spec_test (transformed_path , stdout = stdout )
253247 check_expected (actual , os .path .join (shared .get_test_dir ('spec' ), 'expected-output' , test_name + '.log' ), stdout = stdout )
254248
255249
256- def run_spec_test_with_wrapped_stdout (output_queue , wast : Path ):
250+ def run_spec_test_with_wrapped_stdout (wast : Path ):
257251 out = io .StringIO ()
258252 try :
259- ret = run_one_spec_test (wast , stdout = out , stderr = out )
253+ run_one_spec_test (wast , stdout = out )
260254 except Exception as e :
255+ # Serialize exceptions into the output string buffer
256+ # so they can be reported on the main thread.
261257 print (e , file = out )
262258 raise
263- finally :
264- # If a test fails, it's important to keep its output
265- output_queue .put (out .getvalue ())
266- return ret
259+ return out .getvalue ()
267260
268261
269262def run_spec_tests ():
270263 print ('\n [ checking wasm-shell spec testcases... ]\n ' )
271264
272- output_queue = queue .Queue ()
273-
274- stop_printer = object ()
275-
276- def printer ():
277- while True :
278- string = output_queue .get ()
279- if string is stop_printer :
280- break
281-
282- print (string , end = "" )
283-
284- printing_thread = threading .Thread (target = printer )
285- printing_thread .start ()
286-
287265 worker_count = os .cpu_count ()
288266 print ("Running with" , worker_count , "workers" )
289- executor = ThreadPoolExecutor (max_workers = worker_count )
290- try :
291- results = executor .map (partial (run_spec_test_with_wrapped_stdout , output_queue ), map (Path , shared .options .spec_tests ))
292- for _ in results :
293- # Iterating joins the threads. No return value here.
294- pass
295- except KeyboardInterrupt :
296- # Hard exit to avoid threads continuing to run after Ctrl-C.
297- # There's no concern of deadlocking during shutdown here.
298- os ._exit (1 )
299- finally :
300- executor .shutdown (cancel_futures = True )
301-
302- output_queue .put (stop_printer )
303- printing_thread .join ()
267+ test_paths = [Path (x ) for x in shared .options .spec_tests ]
268+ with ThreadPool (processes = worker_count ) as pool :
269+ try :
270+ for result in pool .imap_unordered (run_spec_test_with_wrapped_stdout , test_paths ):
271+ print (result , end = "" )
272+ except KeyboardInterrupt :
273+ # Hard exit to avoid threads continuing to run after Ctrl-C.
274+ # There's no concern of deadlocking during shutdown here.
275+ os ._exit (1 )
304276
305277
306278def run_validator_tests ():
@@ -437,7 +409,7 @@ def main():
437409
438410 for r in shared .requested :
439411 if r not in all_suites :
440- print ('invalid test suite: %s (see --list-suites)\n ' % r )
412+ print (f 'invalid test suite: { r } (see --list-suites)\n ' )
441413 return 1
442414
443415 if not shared .requested :
0 commit comments