Skip to content

Commit 01ac999

Browse files
committed
Fix occasional test failures when running multiple jobs
The way runtest.py passes the list of fixture directories is racy because it sets it in os.environ['FIXTURE_DIRS'] and then spawns the subprocess, counting on Python to start the subprocess before that list is overwritten when spawning the next directory. At least on Windows, the environment is not copied in subprocess.run so runtest.py may overwrite the list of fixture directories with the list for test #2 while the subprocess module is still kicking off test #1. I was able to easily reproduce this by running the command: `python runtest.py -j 2 test\MSVC\VSWHERE.py test\AS\ASPPFLAGS.py` a few times in a row. However, with this fix, that command repeatedly succeeds. To validate ths fix, I also ran that command with "--xml a.xml" and "--xml a.xml --nopipefiles" to validate that those other executors worked correctly.
1 parent fb03ae9 commit 01ac999

File tree

2 files changed

+25
-14
lines changed

2 files changed

+25
-14
lines changed

CHANGES.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ RELEASE VERSION/DATE TO BE FILLED IN LATER
3333
From Adam Gross:
3434
- Fix minor bug affecting SCons.Node.FS.File.get_csig()'s usage of the MD5 chunksize.
3535
User-facing behavior does not change with this fix (GH Issue #3726).
36+
- Fix occasional test failures caused by not being able to find a file or directory fixture
37+
when running multiple tests with multiple jobs.
3638

3739
From Joachim Kuebart:
3840
- Suppress missing SConscript deprecation warning if `must_exist=False`

runtest.py

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -293,8 +293,8 @@ def escape(s):
293293
if not catch_output:
294294
# Without any output suppressed, we let the subprocess
295295
# write its stuff freely to stdout/stderr.
296-
def spawn_it(command_args):
297-
cp = subprocess.run(command_args, shell=False)
296+
def spawn_it(command_args, env):
297+
cp = subprocess.run(command_args, shell=False, env=env)
298298
return cp.stdout, cp.stderr, cp.returncode
299299
else:
300300
# Else, we catch the output of both pipes...
@@ -310,15 +310,16 @@ def spawn_it(command_args):
310310
# http://http://thraxil.org/users/anders/posts/2008/03/13/Subprocess-Hanging-PIPE-is-your-enemy/
311311
# and pass temp file objects to Popen() instead of the ubiquitous
312312
# subprocess.PIPE.
313-
def spawn_it(command_args):
313+
def spawn_it(command_args, env):
314314
# Create temporary files
315315
tmp_stdout = tempfile.TemporaryFile(mode='w+t')
316316
tmp_stderr = tempfile.TemporaryFile(mode='w+t')
317317
# Start subprocess...
318318
cp = subprocess.run(command_args,
319319
stdout=tmp_stdout,
320320
stderr=tmp_stderr,
321-
shell=False)
321+
shell=False,
322+
env=env)
322323

323324
try:
324325
# Rewind to start of files
@@ -348,11 +349,12 @@ def spawn_it(command_args):
348349
# (but the subprocess isn't writing anything there).
349350
# Hence a deadlock.
350351
# Be dragons here! Better don't use this!
351-
def spawn_it(command_args):
352+
def spawn_it(command_args, env):
352353
cp = subprocess.run(command_args,
353354
stdout=subprocess.PIPE,
354355
stderr=subprocess.PIPE,
355-
shell=False)
356+
shell=False,
357+
env=env)
356358
return cp.stdout, cp.stderr, cp.returncode
357359

358360

@@ -380,8 +382,8 @@ def execute(self):
380382

381383
class SystemExecutor(RuntestBase):
382384
""" Test class for tests executed with spawn_it() """
383-
def execute(self):
384-
self.stderr, self.stdout, s = spawn_it(self.command_args)
385+
def execute(self, env):
386+
self.stderr, self.stdout, s = spawn_it(self.command_args, env)
385387
self.status = s
386388
if s < 0 or s > 2:
387389
sys.stdout.write("Unexpected exit status %d\n" % s)
@@ -400,15 +402,16 @@ class PopenExecutor(RuntestBase):
400402
# and the 'allow_pipe_files' option, please check out the
401403
# definition of spawn_it() above.
402404
if allow_pipe_files:
403-
def execute(self):
405+
def execute(self, env):
404406
# Create temporary files
405407
tmp_stdout = tempfile.TemporaryFile(mode='w+t')
406408
tmp_stderr = tempfile.TemporaryFile(mode='w+t')
407409
# Start subprocess...
408410
cp = subprocess.run(self.command_str.split(),
409411
stdout=tmp_stdout,
410412
stderr=tmp_stderr,
411-
shell=False)
413+
shell=False,
414+
env=env)
412415
self.status = cp.returncode
413416

414417
try:
@@ -423,11 +426,12 @@ def execute(self):
423426
tmp_stdout.close()
424427
tmp_stderr.close()
425428
else:
426-
def execute(self):
429+
def execute(self, env):
427430
cp = subprocess.run(self.command_str.split(),
428431
stdout=subprocess.PIPE,
429432
stderr=subprocess.PIPE,
430-
shell=False)
433+
shell=False,
434+
env=env)
431435
self.status = cp.returncode
432436
self.stdout = cp.stdout
433437
self.stderr = cp.stderr
@@ -756,11 +760,16 @@ def run_test(t, io_lock=None, run_async=True):
756760
if head:
757761
fixture_dirs.append(head)
758762
fixture_dirs.append(os.path.join(scriptpath, 'test', 'fixture'))
759-
os.environ['FIXTURE_DIRS'] = os.pathsep.join(fixture_dirs)
763+
764+
# Set the list of fixture dirs directly in the environment. Just putting
765+
# it in os.environ and spawning the process is racy. Make it reliable by
766+
# overriding the environment passed to execute().
767+
env = dict(os.environ)
768+
env['FIXTURE_DIRS'] = os.pathsep.join(fixture_dirs)
760769

761770
test_start_time = time_func()
762771
if execute_tests:
763-
t.execute()
772+
t.execute(env)
764773

765774
t.test_time = time_func() - test_start_time
766775
log_result(t, io_lock=io_lock)

0 commit comments

Comments
 (0)