Fix use of shared state in test multiprocessing (#14556)
In #14536 I moved away from solely using environment variables to
control testing options and towards using command line args. These
command line args set global state in the same way that the env vars do.
However, due to the way multiprocessing works on windows this global
state is not shared with child processes.
On windows the 'spawn' method is used to create children whereas on
unix the 'fork' method us used.
With this change always process the environment variables even when not
running `main`. I also set those environment variable based on the
command line options so they will always be shared with the child
process.
In the future we should probably transmit out state/option explicitly to
out children but for now this fixes the issue we have been seeing on the
windows roller.
diff --git a/tests/parallel_testsuite.py b/tests/parallel_testsuite.py
index ed603ad..c8f16b8 100644
--- a/tests/parallel_testsuite.py
+++ b/tests/parallel_testsuite.py
@@ -41,6 +41,12 @@
self.max_cores = max_cores
def run(self, result):
+ # The 'spawn' method is used on windows and it can be useful to set this on
+ # all platforms when debugging multiprocessing issues. Without this we
+ # default to 'fork' on unix which is better because global state is
+ # inherited by the child process, but can lead to hard-to-debug windows-only
+ # issues.
+ # multiprocessing.set_start_method('spawn')
test_queue = self.create_test_queue()
self.init_processes(test_queue)
results = self.collect_results()
diff --git a/tests/runner.py b/tests/runner.py
index 797103d..29467e1 100755
--- a/tests/runner.py
+++ b/tests/runner.py
@@ -319,14 +319,14 @@
return parser.parse_args()
-def env_config():
+def configure():
common.EMTEST_BROWSER = os.getenv('EMTEST_BROWSER')
common.EMTEST_DETECT_TEMPFILE_LEAKS = int(os.getenv('EMTEST_DETECT_TEMPFILE_LEAKS', '0'))
common.EMTEST_SAVE_DIR = int(os.getenv('EMTEST_SAVE_DIR', '0'))
- common.EMTEST_ALL_ENGINES = os.getenv('EMTEST_ALL_ENGINES')
- common.EMTEST_SKIP_SLOW = os.getenv('EMTEST_SKIP_SLOW')
- common.EMTEST_LACKS_NATIVE_CLANG = os.getenv('EMTEST_LACKS_NATIVE_CLANG')
- common.EMTEST_REBASELINE = os.getenv('EMTEST_REBASELINE')
+ common.EMTEST_ALL_ENGINES = int(os.getenv('EMTEST_ALL_ENGINES', '0'))
+ common.EMTEST_SKIP_SLOW = int(os.getenv('EMTEST_SKIP_SLOW', '0'))
+ common.EMTEST_LACKS_NATIVE_CLANG = int(os.getenv('EMTEST_LACKS_NATIVE_CLANG', '0'))
+ common.EMTEST_REBASELINE = int(os.getenv('EMTEST_REBASELINE', '0'))
common.EMTEST_VERBOSE = int(os.getenv('EMTEST_VERBOSE', '0')) or shared.DEBUG
assert 'PARALLEL_SUITE_EMCC_CORES' not in os.environ, 'use EMTEST_CORES rather than PARALLEL_SUITE_EMCC_CORES'
@@ -334,26 +334,37 @@
def main(args):
- env_config()
options = parse_args(args)
- if options.browser:
- common.EMTEST_BROWSER = options.browser
- if options.detect_leaks:
- common.EMTEST_DETECT_TEMPFILE_LEAKS = options.detect_leaks
- if options.save_dir:
- common.EMTEST_SAVE_DIR = options.save_dir
+
+ # We set the environments variables here and then call configure,
+ # to apply them. This means the python's multiprocessing child
+ # process will see the same configuration even though they don't
+ # parse the command line.
+ def set_env(name, option_value):
+ if option_value is None:
+ return
+ if option_value is False:
+ value = '0'
+ elif option_value is True:
+ value = '1'
+ else:
+ value = str(option_value)
+ os.environ[name] = value
+
+ set_env('EMTEST_BROWSER', options.browser)
+ set_env('EMTEST_DETECT_TEMPFILE_LEAKS', options.detect_leaks)
+ set_env('EMTEST_SAVE_DIR', options.save_dir)
if options.no_clean:
- common.EMTEST_SAVE_DIR = 2
- if options.skip_slow:
- common.EMTEST_SKIP_SLOW = options.skip_slow
- if options.all_engines:
- common.EMTEST_ALL_ENGINES = options.all_engines
- if options.rebaseline:
- common.EMTEST_REBASELINE = options.rebaseline
- if options.verbose:
- common.EMTEST_VERBOSE = options.verbose
- if options.cores:
- parallel_testsuite.NUM_CORES = options.cores
+ set_env('EMTEST_SAVE_DIR', 2)
+ else:
+ set_env('EMTEST_SAVE_DIR', options.save_dir)
+ set_env('EMTEST_SKIP_SLOW', options.skip_slow)
+ set_env('EMTEST_ALL_ENGINES', options.all_engines)
+ set_env('EMTEST_REBASELINE', options.rebaseline)
+ set_env('EMTEST_VERBOSE', options.verbose)
+ set_env('EMTEST_CORES', options.cores)
+
+ configure()
check_js_engines()
@@ -381,6 +392,8 @@
return min(num_failures, 125)
+configure()
+
if __name__ == '__main__':
try:
sys.exit(main(sys.argv))