mirror of
https://github.com/fish-shell/fish-shell
synced 2025-01-15 22:44:01 +00:00
263 lines
8.5 KiB
Python
263 lines
8.5 KiB
Python
|
#!/usr/bin/env python3
|
||
|
import argparse
|
||
|
import os
|
||
|
from datetime import datetime
|
||
|
from pathlib import Path
|
||
|
import shutil
|
||
|
import subprocess
|
||
|
import sys
|
||
|
import tempfile
|
||
|
|
||
|
import littlecheck
|
||
|
|
||
|
try:
|
||
|
import pexpect
|
||
|
|
||
|
PEXPECT = True
|
||
|
except ImportError:
|
||
|
PEXPECT = False
|
||
|
|
||
|
RESET = "\033[0m"
|
||
|
GREEN = "\033[32m"
|
||
|
BLUE = "\033[34m"
|
||
|
RED = "\033[31m"
|
||
|
|
||
|
|
||
|
def makeenv(script_path, home, test_helper_path):
|
||
|
xdg_config = home + "/xdg_config_home"
|
||
|
func_dir = xdg_config + "/fish/functions"
|
||
|
os.makedirs(func_dir)
|
||
|
os.makedirs(xdg_config + "/fish/conf.d/")
|
||
|
for func in (script_path / "test_functions").glob("*.fish"):
|
||
|
shutil.copy(func, func_dir + "/" + func.parts[-1])
|
||
|
shutil.copy(
|
||
|
script_path / "interactive.config", xdg_config + "/fish/conf.d/interactive.fish"
|
||
|
)
|
||
|
|
||
|
xdg_data = home + "/xdg_data_home"
|
||
|
os.makedirs(xdg_data)
|
||
|
xdg_runtime = home + "/xdg_runtime_home"
|
||
|
os.makedirs(xdg_runtime)
|
||
|
xdg_cache = home + "/xdg_cache_home"
|
||
|
os.makedirs(xdg_cache)
|
||
|
tmp = home + "/temp"
|
||
|
os.makedirs(tmp)
|
||
|
|
||
|
# Compile fish_test_helper if necessary.
|
||
|
# If we're run multiple times, allow keeping this around to save time.
|
||
|
if test_helper_path:
|
||
|
thp = Path(test_helper_path)
|
||
|
if not os.path.exists(thp / "fish_test_helper"):
|
||
|
comp = subprocess.run(
|
||
|
[
|
||
|
"cc",
|
||
|
script_path / "fish_test_helper.c",
|
||
|
"-o",
|
||
|
thp / "fish_test_helper",
|
||
|
]
|
||
|
)
|
||
|
shutil.copy(thp / "fish_test_helper", home + "/fish_test_helper")
|
||
|
else:
|
||
|
comp = subprocess.run(
|
||
|
[
|
||
|
"cc",
|
||
|
script_path / "fish_test_helper.c",
|
||
|
"-o",
|
||
|
home + "/fish_test_helper",
|
||
|
]
|
||
|
)
|
||
|
|
||
|
# unset LANG, TERM, ...
|
||
|
for var in [
|
||
|
"XDG_DATA_DIRS",
|
||
|
"LANGUAGE",
|
||
|
"COLORTERM",
|
||
|
"KONSOLE_PROFILE_NAME",
|
||
|
"KONSOLE_VERSION",
|
||
|
"TERM_PROGRAM",
|
||
|
"TERM_PROGRAM_VERSION",
|
||
|
"VTE_VERSION",
|
||
|
]:
|
||
|
if var in os.environ:
|
||
|
del os.environ[var]
|
||
|
langvars = [key for key in os.environ.keys() if key.startswith("LC_")]
|
||
|
for key in langvars:
|
||
|
del os.environ[key]
|
||
|
|
||
|
os.environ.update(
|
||
|
{
|
||
|
"HOME": home,
|
||
|
"TMPDIR": tmp,
|
||
|
"FISH_FAST_FAIL": "1",
|
||
|
"FISH_UNIT_TESTS_RUNNING": "1",
|
||
|
"XDG_CONFIG_HOME": xdg_config,
|
||
|
"XDG_DATA_HOME": xdg_data,
|
||
|
"XDG_RUNTIME_DIR": xdg_runtime,
|
||
|
"XDG_CACHE_HOME": xdg_cache,
|
||
|
"fish_test_helper": home + "/fish_test_helper",
|
||
|
"TERM": "xterm",
|
||
|
"LANG": "C",
|
||
|
"LC_CTYPE": "en_US.UTF-8",
|
||
|
}
|
||
|
)
|
||
|
|
||
|
|
||
|
def main():
|
||
|
if len(sys.argv) < 2:
|
||
|
print("Usage: test_driver.py FISH_DIRECTORY TESTS")
|
||
|
return 1
|
||
|
|
||
|
script_path = Path(__file__).parent
|
||
|
|
||
|
argparser = argparse.ArgumentParser(
|
||
|
description="test_driver: Run fish's test suite"
|
||
|
)
|
||
|
argparser.add_argument(
|
||
|
"-f",
|
||
|
"--cachedir",
|
||
|
type=str,
|
||
|
help="Path to keep outputs to speed up the next run",
|
||
|
action="store",
|
||
|
default=None,
|
||
|
)
|
||
|
argparser.add_argument("fish", nargs=1, help="Fish to test")
|
||
|
argparser.add_argument("file", nargs="*", help="Tests to run")
|
||
|
args=argparser.parse_args()
|
||
|
|
||
|
fishdir = Path(args.fish[0]).absolute()
|
||
|
if not fishdir.is_dir():
|
||
|
fishdir = fishdir.parent
|
||
|
|
||
|
failcount = 0
|
||
|
failed=[]
|
||
|
passcount = 0
|
||
|
skipcount = 0
|
||
|
def_subs = {"%": "%"}
|
||
|
lconfig = littlecheck.Config()
|
||
|
lconfig.colorize = sys.stdout.isatty()
|
||
|
lconfig.progress = True
|
||
|
|
||
|
for bin in ["fish", "fish_indent", "fish_key_reader"]:
|
||
|
if os.path.exists(fishdir / bin):
|
||
|
def_subs[bin] = str(fishdir / bin)
|
||
|
else:
|
||
|
print(f"Binary does not exist: {fishdir / bin}")
|
||
|
return 127
|
||
|
|
||
|
if args.file:
|
||
|
files = [(os.path.abspath(path), path) for path in args.file]
|
||
|
else:
|
||
|
files = [
|
||
|
(os.path.abspath(path), str(path.relative_to(script_path)))
|
||
|
for path in sorted(script_path.glob("checks/*.fish"))
|
||
|
]
|
||
|
files += [
|
||
|
(os.path.abspath(path), str(path.relative_to(script_path)))
|
||
|
for path in sorted(script_path.glob("pexpects/*.py"))
|
||
|
]
|
||
|
|
||
|
if not PEXPECT and any(x.endswith(".py") for (x, _) in files):
|
||
|
print(f"{RED}Skipping pexpect tests because pexpect is not installed{RESET}")
|
||
|
|
||
|
for f, arg in files:
|
||
|
if not f.endswith(".fish") and not f.endswith(".py"):
|
||
|
print(f"Not a valid test file: {arg}")
|
||
|
failcount += 1
|
||
|
continue
|
||
|
|
||
|
starttime = datetime.now()
|
||
|
with tempfile.TemporaryDirectory(prefix="fishtest-") as home:
|
||
|
makeenv(script_path, home, args.cachedir)
|
||
|
os.chdir(home)
|
||
|
if f.endswith(".fish"):
|
||
|
subs = def_subs.copy()
|
||
|
subs.update({"s": f, "fish_test_helper": home + "/fish_test_helper"})
|
||
|
|
||
|
# littlecheck
|
||
|
print(f"{arg}..", end="", flush=True)
|
||
|
ret = littlecheck.check_path(
|
||
|
f, subs, lconfig, lambda x: print(x.message())
|
||
|
)
|
||
|
endtime = datetime.now()
|
||
|
duration_ms = round((endtime - starttime).total_seconds() * 1000)
|
||
|
if ret is littlecheck.SKIP:
|
||
|
print(f"{BLUE}SKIPPED{RESET}")
|
||
|
skipcount += 1
|
||
|
elif ret:
|
||
|
print(f"{GREEN}PASS{RESET} ({duration_ms} ms)")
|
||
|
passcount += 1
|
||
|
else:
|
||
|
print(f"{RED}FAIL{RESET} ({duration_ms} ms)")
|
||
|
failcount += 1
|
||
|
failed += [arg]
|
||
|
print(f"Tmpdir is {home}")
|
||
|
elif f.endswith(".py"):
|
||
|
# environ for py files has a few changes.
|
||
|
pyenviron = os.environ.copy()
|
||
|
pyenviron.update(
|
||
|
{
|
||
|
"PYTHONPATH": str(script_path),
|
||
|
"fish": str(fishdir / "fish"),
|
||
|
"fish_key_reader": str(fishdir / "fish_key_reader"),
|
||
|
"fish_indent": str(fishdir / "fish_indent"),
|
||
|
"TERM": "dumb",
|
||
|
"FISH_FORCE_COLOR": "1" if sys.stdout.isatty() else "0",
|
||
|
}
|
||
|
)
|
||
|
print(f"{arg}..", end="", flush=True)
|
||
|
if not PEXPECT:
|
||
|
print(f"{BLUE}SKIPPED{RESET}")
|
||
|
skipcount += 1
|
||
|
continue
|
||
|
try:
|
||
|
proc = subprocess.run(
|
||
|
["python3", f],
|
||
|
capture_output=True,
|
||
|
env=pyenviron,
|
||
|
# Timeout of 120 seconds, about 10 times what any of these takes
|
||
|
timeout=120,
|
||
|
)
|
||
|
except subprocess.TimeoutExpired as e:
|
||
|
print(f"{RED}FAILED due to timeout{RESET}")
|
||
|
if e.output:
|
||
|
print(e.output.decode("utf-8"))
|
||
|
if e.stderr:
|
||
|
print(e.stderr.decode("utf-8"))
|
||
|
failcount += 1
|
||
|
failed += [arg]
|
||
|
continue
|
||
|
|
||
|
endtime = datetime.now()
|
||
|
duration_ms = round((endtime - starttime).total_seconds() * 1000)
|
||
|
if proc.returncode == 0:
|
||
|
print(f"{GREEN}PASS{RESET} ({duration_ms} ms)")
|
||
|
passcount += 1
|
||
|
elif proc.returncode == 127:
|
||
|
print(f"{BLUE}SKIPPED{RESET}")
|
||
|
skipcount += 1
|
||
|
else:
|
||
|
print(f"{RED}FAILED{RESET} ({duration_ms} ms)")
|
||
|
if proc.stdout:
|
||
|
print(proc.stdout.decode("utf-8"))
|
||
|
if proc.stderr:
|
||
|
print(proc.stderr.decode("utf-8"))
|
||
|
failcount += 1
|
||
|
failed += [arg]
|
||
|
print(f"Tmpdir is {home}")
|
||
|
if passcount + failcount + skipcount > 1:
|
||
|
print(f"{passcount} / {passcount + failcount} passed ({skipcount} skipped)")
|
||
|
if failcount:
|
||
|
failstr = '\n '.join(failed)
|
||
|
print(f"{RED}Failed tests{RESET}: \n {failstr}")
|
||
|
if passcount == 0 and failcount == 0 and skipcount:
|
||
|
return 125
|
||
|
return 1 if failcount else 0
|
||
|
|
||
|
|
||
|
if __name__ == "__main__":
|
||
|
try:
|
||
|
ret = main()
|
||
|
sys.exit(ret)
|
||
|
except KeyboardInterrupt:
|
||
|
sys.exit(130)
|