test_driver: don't chdir in async driver

The test driver is async now; so we can't change the process-wide
working directory without causing unpredictable behavior.
For example, given these two tests:

	$ cat tests/checks/a.fish
	#RUN: %fish %s
	#REQUIRES: sleep 1
	pwd >/tmp/pwd-of-a

	$ cat tests/checks/b.fish
	#RUN: %fish %s
	pwd >/tmp/pwd-of-b

running them may give both fish processes the same working directory.

	$ tests/test_driver.py target/debug tests/checks/a.fish tests/checks/b.fish
	tests/checks/b.fish  PASSED      13 ms
	tests/checks/a.fish  PASSED    1019 ms
	2 / 2 passed (0 skipped)
	$ grep . /tmp/pwd-of-a /tmp/pwd-of-b
	/tmp/pwd-of-a:/tmp/fishtest-root-1q_tnyqa/fishtest-s9cyqkgz
	/tmp/pwd-of-b:/tmp/fishtest-root-1q_tnyqa/fishtest-s9cyqkgz
This commit is contained in:
Johannes Altmanninger
2025-10-11 16:31:06 +02:00
parent 5102c8b137
commit e1e5dfdd62
2 changed files with 23 additions and 11 deletions

View File

@@ -387,16 +387,16 @@ def runproc(cmd, env=None):
return asyncio.run(runproc_async(cmd, env=env))
async def runproc_async(cmd, env=None):
async def runproc_async(cmd, env=None, cwd=None):
"""Wrapper around subprocess.Popen to save typing"""
PIPE = asyncio.subprocess.PIPE
return await asyncio.create_subprocess_shell(
cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE, env=env
cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE, env=env, cwd=cwd
)
class TestRun(object):
def __init__(self, name, runcmd, checker, subs, config, env=None):
def __init__(self, name, runcmd, checker, subs, config, env=None, cwd=None):
self.name = name
self.runcmd = runcmd
self.subbed_command = perform_substitution(runcmd.args, subs)
@@ -404,6 +404,7 @@ class TestRun(object):
self.subs = subs
self.config = config
self.env = env
self.cwd = cwd
def check(self, lines, checks):
# Reverse our lines and checks so we can pop off the end.
@@ -493,7 +494,7 @@ class TestRun(object):
if self.config.verbose:
print(self.subbed_command)
proc = await runproc_async(self.subbed_command, env=self.env)
proc = await runproc_async(self.subbed_command, env=self.env, cwd=self.cwd)
stdout, stderr = await proc.communicate()
# Work around type system limitations / bad API design which makes the typechecker unhappy.
@@ -684,7 +685,9 @@ def check_file(input_file, name, subs, config, failure_handler, env=None):
)
async def check_file_async(input_file, name, subs, config, failure_handler, env=None):
async def check_file_async(
input_file, name, subs, config, failure_handler, env=None, cwd=None
):
"""Check a single file. Return a True on success, False on error."""
success = True
lines = Line.readfile(input_file, name)
@@ -693,7 +696,9 @@ async def check_file_async(input_file, name, subs, config, failure_handler, env=
# Run all the REQUIRES lines first,
# if any of them fail it's a SKIP
for reqcmd in checker.requirecmds:
proc = await runproc_async(perform_substitution(reqcmd.args, subs), env=env)
proc = await runproc_async(
perform_substitution(reqcmd.args, subs), env=env, cwd=cwd
)
await proc.communicate()
# Work around type system limitations / bad API design which makes the typechecker unhappy.
if proc.returncode is None:
@@ -709,7 +714,7 @@ async def check_file_async(input_file, name, subs, config, failure_handler, env=
# Only then run the RUN lines.
for runcmd in checker.runcmds:
failure = await TestRun(
name, runcmd, checker, subs, config, env=env
name, runcmd, checker, subs, config, env=env, cwd=cwd
).run_async()
if failure:
failure_handler(failure)
@@ -721,9 +726,11 @@ def check_path(path, subs, config, failure_handler, env=None):
return asyncio.run(check_path_async(path, subs, config, failure_handler, env=env))
async def check_path_async(path, subs, config, failure_handler, env=None):
async def check_path_async(path, subs, config, failure_handler, env=None, cwd=None):
with io.open(path, encoding="utf-8") as fd:
return await check_file_async(fd, path, subs, config, failure_handler, env=env)
return await check_file_async(
fd, path, subs, config, failure_handler, env=env, cwd=cwd
)
def parse_subs(subs):

View File

@@ -297,7 +297,6 @@ async def run_test(
starttime = datetime.now()
home = Path(tempfile.mkdtemp(prefix="fishtest-", dir=tmp_root))
test_env = makeenv(script_path, home)
os.chdir(home)
if test_file_path.endswith(".fish"):
subs = def_subs.copy()
subs.update(
@@ -309,7 +308,12 @@ async def run_test(
# littlecheck
ret = await littlecheck.check_path_async(
test_file_path, subs, lconfig, lambda x: print(x.message()), env=test_env
test_file_path,
subs,
lconfig,
lambda x: print(x.message()),
env=test_env,
cwd=home,
)
endtime = datetime.now()
duration_ms = round((endtime - starttime).total_seconds() * 1000)
@@ -339,6 +343,7 @@ async def run_test(
stdout=PIPE,
stderr=PIPE,
env=test_env,
cwd=home,
)
stdout, stderr = await proc.communicate()
endtime = datetime.now()