Rewrite test driver in python (#11028)

This replaces the test_driver.sh/test.fish/interactive.fish system with a test driver written in python that calls into littlecheck directly and runs pexpect in a subprocess.

This means we reduce the reliance on the fish that we're testing, and we remove a posix sh script that is a weird stumbling block (see my recent quest to make it work on directories with spaces).

To run specific tests, e.g. all the tmux tests and bind.py:

tests/test_driver.py target/release/ tests/checks/tmux*.fish tests/pexpects/bind.py
This commit is contained in:
Fabian Boehm 2025-01-11 21:13:19 +01:00 committed by GitHub
parent 51adba6ee0
commit b43b0e0195
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 292 additions and 461 deletions

View file

@ -198,10 +198,11 @@ The tests can be found in three places:
When in doubt, the bulk of the tests should be added as a littlecheck test in tests/checks, as they are the easiest to modify and run, and much faster and more dependable than pexpect tests. The syntax is fairly self-explanatory. It's a fish script with the expected output in ``# CHECK:`` or ``# CHECKERR:`` (for stderr) comments.
If your littlecheck test has a specific dependency, use ``# REQUIRE: ...`` with a posix sh script.
Tests are run in a temporary $HOME, but that is shared among the tests by default. If you need a temporary directory for your test, you should create one (e.g. with ``mktemp``).
The pexpects are written in python and can simulate input and output to/from a terminal, so they are needed for anything that needs actual interactivity. The runner is in tests/pexpect_helper.py, in case you need to modify something there.
These tests can be run via the tests/test_driver.py python script, which will set up the environment.
It sets up a temporary $HOME and also uses it as the current directory, so you do not need to create a temporary directoy in them.
If you need a command to do something weird to test something, maybe add it to the ``fish_test_helper`` binary (in tests/fish_test_helper.c), or see if it can already do it.
Local testing
@ -217,11 +218,13 @@ The tests can be run on your local computer on all operating systems.
Or you can run them on a fish, without involving cmake::
cargo build
FISHDIR=target/debug tests/test_driver.sh tests/test.fish # script tests, the checks
FISHDIR=target/debug tests/test_driver.sh tests/interactive.fish # interactive tests, the pexpects
cargo test # for the unit tests
tests/test_driver.py --cachedir=/tmp target/debug # for the script and interactive tests
Here, ``FISHDIR`` refers to a directory with ``fish``, ``fish_indent`` and ``fish_key_reader`` in it.
Here, the first argument to test_driver.py refers to a directory with ``fish``, ``fish_indent`` and ``fish_key_reader`` in it.
In this example we're in the root of the git repo and have run ``cargo build`` without ``--release``, so it's a debug build.
The ``--cachedir /tmp`` argument means it will keep the fish_test_helper binary in /tmp instead of recompiling it for every test.
This saves some time, but isn't strictly necessary.
Git hooks
---------

View file

@ -114,8 +114,8 @@ foreach(CHECK ${FISH_CHECKS})
get_filename_component(CHECK_NAME ${CHECK} NAME)
get_filename_component(CHECK ${CHECK} NAME_WE)
add_test(NAME ${CHECK_NAME}
COMMAND env FISHDIR=${CMAKE_CURRENT_BINARY_DIR}/ ${CMAKE_CURRENT_BINARY_DIR}/tests/test_driver.sh
${CMAKE_CURRENT_BINARY_DIR}/tests/test.fish ${CHECK}
COMMAND ${CMAKE_CURRENT_BINARY_DIR}/tests/test_driver.py --cachedir ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_BINARY_DIR}
checks/${CHECK}.fish
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/tests
)
set_tests_properties(${CHECK_NAME} PROPERTIES SKIP_RETURN_CODE ${SKIP_RETURN_CODE})
@ -127,8 +127,8 @@ FILE(GLOB PEXPECTS CONFIGURE_DEPENDS ${CMAKE_SOURCE_DIR}/tests/pexpects/*.py)
foreach(PEXPECT ${PEXPECTS})
get_filename_component(PEXPECT ${PEXPECT} NAME)
add_test(NAME ${PEXPECT}
COMMAND env FISHDIR=${CMAKE_CURRENT_BINARY_DIR}/ ${CMAKE_CURRENT_BINARY_DIR}/tests/test_driver.sh
${CMAKE_CURRENT_BINARY_DIR}/tests/interactive.fish ${PEXPECT}
COMMAND ${CMAKE_CURRENT_BINARY_DIR}/tests/test_driver.py --cachedir ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_BINARY_DIR}
pexpects/${PEXPECT}
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/tests
)
set_tests_properties(${PEXPECT} PROPERTIES SKIP_RETURN_CODE ${SKIP_RETURN_CODE})

View file

@ -1,4 +1,4 @@
#RUN: fish=%fish %fish %s | %fish %filter-control-sequences
#RUN: fish=%fish %fish %s
set -g PATH
$fish -c "nonexistent-command-1234 banana rama"
#CHECKERR: fish: Unknown command: nonexistent-command-1234

View file

@ -1,4 +1,4 @@
#RUN: %fish --interactive %s | %fish %filter-control-sequences
#RUN: %fish --interactive %s
# ^ interactive so we can do `complete`
mkdir -p __fish_complete_directories/
cd __fish_complete_directories

View file

@ -150,7 +150,10 @@ end
# CHECK: disown
# CHECK: fg
# CHECK: fish_command_not_found
# CHECK: fish_prompt
# CHECK: fish_prompt_event
# CHECK: fish_sigtrap_handler
# CHECK: fish_title
# CHECK: frob
# CHECK: kill
# CHECK: name1

View file

@ -1,4 +1,4 @@
#RUN: %fish -i %s | %fish %filter-control-sequences
#RUN: %fish -i %s
# Note: ^ this is interactive so we test interactive behavior,
# e.g. the fish_git_prompt variable handlers test `status is-interactive`.
#REQUIRES: command -v git

View file

@ -1,4 +1,4 @@
#RUN: fish=%fish %fish %s | %fish %filter-control-sequences
#RUN: fish=%fish %fish %s
$fish -c "echo 1.2.3.4."
# CHECK: 1.2.3.4.

View file

@ -1,4 +1,4 @@
# RUN: fish=%fish filter_ctrls=%filter-control-sequences %fish %s
# RUN: fish=%fish %fish %s
# Set term again explicitly to ensure behavior.
set -gx TERM xterm
# Read with no vars is not an error
@ -248,7 +248,7 @@ if test (string length "$x") -ne $fish_read_limit
end
# Confirm reading non-interactively works -- \#4206 regression
echo abc\ndef | $fish -i -c 'read a; read b; set --show a; set --show b' | $fish $filter_ctrls
echo abc\ndef | $fish -i -c 'read a; read b; set --show a; set --show b'
#CHECK: $a: set in global scope, unexported, with 1 elements
#CHECK: $a[1]: |abc|
#CHECK: $b: set in global scope, unexported, with 1 elements

View file

@ -1,4 +1,4 @@
#RUN: fish=%fish filter_ctrls=%filter-control-sequences %fish %s
#RUN: fish=%fish %fish %s
# Some tests of the "return" builtin.
$fish -c 'return 5'
@ -21,7 +21,7 @@ begin
# but not bar
echo $status
# CHECK: 69
end | $fish $filter_ctrls
end
# Verify negative return values don't cause UB and never map to 0
function empty_return

View file

@ -1,4 +1,4 @@
# RUN: env FISH=%fish filter_ctrls=%filter-control-sequences %fish %s
# RUN: env FISH=%fish %fish %s
# Environment variable tests
# Test if variables can be properly set
@ -367,7 +367,7 @@ begin
env SHLVL=" 3" $FISH -ic 'echo SHLVL: $SHLVL'
# CHECK: SHLVL: 4
# CHECK: SHLVL: 4
end | $FISH $filter_ctrls
end
# Non-interactive fish doesn't touch $SHLVL
env SHLVL=2 $FISH -c 'echo SHLVL: $SHLVL'

View file

@ -1,14 +0,0 @@
# Remove the sorts of escape sequences interactive fish prints.
# First the enable sequences, then a "|" combiner and then the disable ones
set -l escapes "\e\[\?2004h"\
"\e\[>4;1m"\
"\e\[>5u"\
"\e="\
"|"\
"\e\[\?2004l"\
"\e\[>4;0m"\
"\e\[<1u"\
"\e>"
cat | string replace -ra -- $escapes ''

View file

@ -1,96 +0,0 @@
#! /bin/echo "interactive.fish must be run via the test driver!"
#
# Interactive tests using `pexpect`
# Set this var to modify behavior of the code being tests. Such as avoiding running
# `fish_update_completions` when running tests.
set -gx FISH_UNIT_TESTS_RUNNING 1
# Save the directory containing this script
# Do not *cd* here, otherwise you'll ruin our nice tmpdir setup!!!
set -l scriptdir (status dirname)
# Test files specified on commandline, or all pexpect files.
if set -q argv[1] && test -n "$argv[1]"
set pexpect_files_to_test $scriptdir/pexpects/$argv
else if set -q FISH_PEXPECT_FILES
set pexpect_files_to_test (string replace -r '^.*/(?=pexpects/)' '' -- $FISH_PEXPECT_FILES)
else
say -o cyan "Testing interactive functionality"
set pexpect_files_to_test $scriptdir/pexpects/*.py
end
source $scriptdir/test_util.fish || exit
cat $scriptdir/interactive.config >>$XDG_CONFIG_HOME/fish/config.fish
set -lx --prepend PYTHONPATH (realpath $scriptdir)
function test_pexpect_file
set -l file $argv[1]
echo -n "Testing file $file:"
begin
set starttime (timestamp)
set -lx TERM dumb
# Help the script find the pexpect_helper module in our parent directory.
set -q FISHDIR
or set -l FISHDIR ../test/root/bin/
set -lx fish $FISHDIR/fish
set -lx fish_key_reader $FISHDIR/fish_key_reader
path is -fx -- $FISHDIR/fish_test_helper
and set -lx fish_test_helper $FISHDIR/fish_test_helper
# Note we require Python3.
python3 $file
end
set -l exit_status $status
if test "$exit_status" -eq 0
set test_duration (delta $starttime)
say green "ok ($test_duration $unit)"
else if test "$exit_status" -eq 127
say blue "SKIPPED"
set exit_status 0
end
return $exit_status
end
set failed
# The test here looks wrong, but False sets exit status to 0, which is what we want
if python3 -c 'import sys; exit(sys.version_info > (3, 5))'
say red "pexpect tests disabled: python3 is too old"
set pexpect_files_to_test
end
if not python3 -c 'import pexpect'
say red "pexpect tests disabled: `python3 -c 'import pexpect'` failed"
set pexpect_files_to_test
end
for i in $pexpect_files_to_test
if not test_pexpect_file $i
# Retry pexpect tests under CI twice, as they are timing-sensitive and CI resource
# contention can cause tests to spuriously fail.
if set -qx CI
say yellow "Trying $i for a second time"
if not test_pexpect_file $i
set failed $failed $i
end
else
set failed $failed $i
end
end
end
set failed (count $failed)
if test $failed -eq 0
if test (count $pexpect_files_to_test) -gt 1
say green "All interactive tests completed successfully"
else
say green "$pexpect_files_to_test completed successfully"
end
exit 0
else
set plural (test $failed -eq 1; or echo s)
say red "$failed test$plural failed"
exit 1
end

View file

@ -11,22 +11,21 @@ send, sendline, sleep, expect_prompt, expect_re, expect_str = (
sp.expect_re,
sp.expect_str,
)
expect_prompt()
# We're going to use three history files, including the default, to verify
# that the fish_history variable works as expected.
default_histfile = "../test/data/fish/fish_history"
my_histfile = "../test/data/fish/my_history"
env_histfile = "../test/data/fish/env_history"
# (using as a relative path to avoid interpolating $XDG_DATA_HOME)
default_histfile = "xdg_data_home/fish/fish_history"
my_histfile = "xdg_data_home/fish/my_history"
env_histfile = "xdg_data_home/fish/env_history"
def grephistfile(line, file):
sendline("grep '^" + line + "' " + file)
# Verify that if we spawn fish with no fish_history env var it uses the
# default file.
expect_prompt
expect_prompt()
# Verify that a command is recorded in the default history file.
cmd1 = "echo $fish_pid default histfile"

View file

@ -1,71 +0,0 @@
# Fishscript tests
#
# There is no shebang line because you shouldn't be running this by hand. You
# should be running it via `make test` to ensure the environment is properly
# setup.
# Set this var to modify behavior of the code being tests. Such as avoiding running
# `fish_update_completions` when running tests.
set -x FISH_UNIT_TESTS_RUNNING 1
# Save the directory containing this script
# Do not *cd* here, otherwise you'll ruin our nice tmpdir setup!!!
set -l scriptdir (status dirname)
# Test files specified on commandline, or all checks.
set -l files_to_test
if set -q argv[1]
set files_to_test $scriptdir/checks/$argv.fish
else
set files_to_test $scriptdir/checks/*.fish
end
# Be less verbose when running tests one-by-one
if test (count $files_to_test) -gt 1
say -o cyan "Testing high level script functionality"
end
set -g python (__fish_anypython)
or begin
say red "Python is not installed. These tests require python."
exit 125
end
set -q FISHDIR
or set -l FISHDIR ../test/root/bin
# Test littlecheck files.
set -l skipped 0
set -l failed 0
if set -q files_to_test[1]
set -l force_color
test "$FISH_FORCE_COLOR" = 1
and set force_color --force-color
$python -S $scriptdir/littlecheck.py \
--progress $force_color \
-s fish=$FISHDIR/fish \
-s fish_test_helper=$fish_test_helper \
-s filter-control-sequences="$scriptdir/filter-control-sequences.fish" \
$files_to_test
set -l littlecheck_status $status
if test "$littlecheck_status" -eq 125
# 125 indicates that all tests executed were skipped.
set skipped (count $files_to_test)
else
# The return code indicates the number of tests that failed
set failed $littlecheck_status
end
end
if test $failed -eq 0 && test $skipped -gt 0
test (count $files_to_test) -gt 1 && say blue (count $files_to_test)" tests skipped"
exit 125
else if test $failed -eq 0
test (count $files_to_test) -gt 1 && say green "All high level script tests completed successfully"
exit 0
else
test (count $files_to_test) -gt 1 && say red "$failed tests failed"
exit 1
end

262
tests/test_driver.py Executable file
View file

@ -0,0 +1,262 @@
#!/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)

View file

@ -1,106 +0,0 @@
#!/bin/sh
# vim: set ts=4 sw=4 tw=100 et:
# POSIX sh test driver to reduce dependency on fish in tests.
# Executes the specified *fish script* with the provided arguments, after setting up a clean test
# environment (see `test_env.sh`) and then importing the fish-related helper functions and
# performing some state initialization required by the various fish tests. Each payload script is
# executed in its own environment, this script waits for fish to exit then cleans up the target
# environment and bubbles up the fish exit code.
# macOS has really weird default IFS behavior that splits output in random places, and the trailing
# backspace is to prevent \n from being gobbled up by the subshell output substitution.
# Folks, this is why you should use fish!
IFS="$(printf "\n\b")"
# If CDPATH is set, `cd foo` will print the directory.
unset CDPATH
# The first argument is the path to the script to launch; all remaining arguments are forwarded to
# the script.
# Resolve the script now because we are going to `cd` later.
fish_script="$(realpath $1)"
shift 1
die() {
if test "$#" -ge 0; then
printf "%s\n" "$@" 1>&2
fi
exit 1
}
# To keep things sane and to make error messages comprehensible, do not use relative paths anywhere
# in this script. Instead, make all paths relative to one of these or the new $HOME ($homedir)."
TESTS_ROOT="$(cd $(dirname "$0") && pwd -P)"
BUILD_ROOT="$(cd $(dirname "$TESTS_ROOT") && pwd -P)"
if test -z "$FISHDIR"; then
die "Please set \$FISHDIR to a directory that contains fish, fish_indent and fish_key_reader"
fi
FISHDIR=$(realpath -- "$FISHDIR")
fish="${FISHDIR}/fish"
if ! test -x "$fish" || ! test -f "$fish"; then
printf '%s\n' "'$fish' is not an executable fish." \
"Please set \$FISHDIR to a directory that contains fish, fish_indent and fish_key_reader" >&2
exit 7
fi
if ! test -z "$__fish_is_running_tests"; then
echo "Recursive test invocation detected!" 1>&2
exit 10
fi
# Set up the test environment. Does not change the current working directory.
. ${TESTS_ROOT}/test_env.sh
test -n "$homedir" || die "Failed to set up home"
# Compile our fish_test_helper program now.
# This takes about 50ms.
if command -v cc >/dev/null ; then
cc "$TESTS_ROOT/fish_test_helper.c" -o "$homedir/fish_test_helper"
else
echo "Cannot find a C compiler. Skipping tests that require fish_test_helper" >&2
fi
# These are used read-only so it's OK to symlink instead of copy
rm -f "$XDG_CONFIG_HOME/fish/functions"
ln -s "${TESTS_ROOT}/test_functions" "$XDG_CONFIG_HOME/fish/functions" || die "Failed to symlink"
# Set the function path at startup, referencing the default fish functions and the test-specific
# functions.
fish_init_cmd="set fish_function_path '${XDG_CONFIG_HOME}/fish/functions' '${BUILD_ROOT}/share/functions'"
__fish_is_running_tests="$homedir"
export __fish_is_running_tests
# Set a marker to indicate whether colored output should be suppressed (used in `test_util.fish`)
suppress_color=""
if ! tty 0>&1 > /dev/null; then
suppress_color="yes"
fi
export suppress_color
# Source test util functions at startup
fish_init_cmd="${fish_init_cmd} && source ${TESTS_ROOT}/test_util.fish";
# Indicate that the fish panic handler shouldn't wait for input to prevent tests from hanging
FISH_FAST_FAIL=1
export FISH_FAST_FAIL
# Run the test script, but don't exec so we can clean up after it succeeds/fails. Each test is
# launched directly within its TMPDIR, so that the fish tests themselves do not need to refer to
# TMPDIR (to ensure their output as displayed in case of failure by littlecheck is reproducible).
if test -n "${@}"; then
(cd $TMPDIR && env HOME="$homedir" fish_test_helper="$homedir/fish_test_helper" "$fish" \
--init-command "${fish_init_cmd}" "$fish_script" "${@}")
else
(cd $TMPDIR && env HOME="$homedir" fish_test_helper="$homedir/fish_test_helper" "$fish" \
--init-command "${fish_init_cmd}" "$fish_script")
fi
test_status="$?"
rm -rf "$homedir"
exit "$test_status"

View file

@ -1,82 +0,0 @@
# vim: set ts=4 sw=4 tw=100 et:
# This script sets up a clean environment for a script or executable to execute in/under. It creates
# (and sets) $TMPDIR initialized to a clean & unique temporary directory, creates a new $HOME, sets
# the relevant XDG_* directories to point to subdirectories of that $HOME, cleans up any potentially
# problematic environment variables, executes the provided command, waits for it to exit, then
# cleans up the newly created environment before bubbling up the exit code. $PWD is not changed.
# If sourced instead of executed, sets up the environment as before but does not execute any payload
# and does not destroy the newly created environment.
# macOS has really weird default IFS behavior that splits output in random places, and the trailing
# backspace is to prevent \n from being gobbled up by the subshell output substitution.
# Folks, this is why you should use fish!
IFS="$(printf "\n\b")"
# set -ex
die() {
if test "$#" -ge 0; then
printf "%s\n" "$@" 1>&2
fi
exit 1
}
# Set up a test environment to run the specified target under. We do not share environments
# whatsoever between tests, so each test driver run sets up a new profile altogether.
# macOS 10.10 requires an explicit template for `mktemp` and will create the folder in the
# current directory unless told otherwise. Linux isn't guaranteed to have $TMPDIR set.
homedir="$(mktemp -d 2>/dev/null || mktemp -d "${TMPDIR}tmp.XXXXXXXXXX")"
export HOME="$homedir"
XDG_DATA_HOME="$homedir/xdg_data_home"
export XDG_DATA_HOME
mkdir -p $XDG_DATA_HOME/fish || die
XDG_CONFIG_HOME="$homedir/xdg_config_home"
export XDG_CONFIG_HOME
mkdir -p $XDG_CONFIG_HOME/fish || die
XDG_RUNTIME_DIR="$homedir/xdg_runtime_dir"
export XDG_RUNTIME_DIR
mkdir -p $XDG_RUNTIME_DIR/fish || die
chmod 700 "$XDG_RUNTIME_DIR"
XDG_CACHE_HOME="$homedir/xdg_cache_home"
export XDG_CACHE_HOME
mkdir -p $XDG_CACHE_HOME/fish || die
# Create a temp/scratch directory for tests to use, if they want (tests shouldn't write to a
# shared temp folder).
TMPDIR="$homedir/temp"
mkdir ${TMPDIR}
export TMPDIR
# Set locale information for consistent tests. Fish should work with a lot of locales but the
# tests assume an english UTF-8 locale unless they explicitly override this default. We do not
# want the users locale to affect the tests since they might, for example, change the wording of
# logged messages.
#
# TODO: set LANG to en_US.UTF-8 so we test the locale message conversions (i.e., gettext).
unset LANGUAGE
# Remove "LC_" env vars from the test environment
for key in $(env | grep -E "^LC_"| grep -oE "^[^=]+"); do
unset "$key"
done
# Set the desired lang/locale tests are hard-coded against
export LANG="C"
export LC_CTYPE="en_US.UTF-8"
# These env vars should not be inherited from the user environment because they can affect the
# behavior of the tests. So either remove them or set them to a known value.
# See also tests/interactive.fish.
export TERM=xterm
unset COLORTERM
unset KONSOLE_PROFILE_NAME
unset KONSOLE_VERSION
unset PANTHEON_TERMINAL_ID
unset LC_TERMINAL
unset LC_TERMINAL_VERSION
unset TERM_PROGRAM
unset TERM_PROGRAM_VERSION
unset VTE_VERSION

View file

@ -1,67 +0,0 @@
# vim: set ts=4 sw=4 tw=100 et:
# Utilities for the test runners
function die
set -q argv[1]; and echo $argv[1] >&2
exit 1
end
# $suppress_color is set by `test_driver.sh` (via import of exported variables)
function say -V suppress_color
set -l color_flags
set -l suppress_newline
while set -q argv[1]
switch $argv[1]
case -b -o -u
set color_flags $color_flags $argv[1]
case -n
set suppress_newline 1
case --
set -e argv[1]
break
case -\*
continue
case \*
break
end
set -e argv[1]
end
if not set -q argv[2]
echo 'usage: say [flags] color string [string...]' >&2
return 1
end
if begin
test -n "$suppress_color"; or set_color $color_flags $argv[1]
end
printf '%s' $argv[2..-1]
test -z "$suppress_color"; and set_color normal
if test -z "$suppress_newline"
echo
end
end
end
# lame timer
for program in {g,}date
if command -q $program && $program --version 1>/dev/null 2>/dev/null
set -g milli $program
set -g unit ms
break
else
set -g unit sec
end
end
function timestamp
set -q milli[1]
and $milli +%s%3N
or date +%s
end
function delta
set -q milli[1]
and math "( "($milli +%s%3N)" - $argv[1])"
or math (date +%s) - $argv[1]
end