Turn FFI tests into native Rust tests

Keep running tests serially to avoid breaking assumptions.

I think many of these tests can run in parallel and/or don't need test_init().
Use the safe variant everywhere, to get it done faster.
This commit is contained in:
Johannes Altmanninger 2024-01-03 20:57:28 +01:00
parent ae9e7a25f8
commit 77550a2f0d
39 changed files with 463 additions and 299 deletions

View file

@ -266,14 +266,12 @@ regressions in the future (i.e., we dont reintroduce the bug).
The tests can be found in three places:
- src/fish_tests.cpp for tests to the core C++ code
- fish-rust/src/tests for unit tests.
- tests/checks for script tests, run by `littlecheck <https://github.com/ridiculousfish/littlecheck>`__
- tests/pexpects for interactive tests using `pexpect <https://pexpect.readthedocs.io/en/stable/>`__
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.
fish_tests.cpp is mostly useful for unit tests - if you wish to test that a function does the correct thing for given input, use it.
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 build_tools/pexpect_helper.py, in case you need to modify something there.
Local testing

110
Cargo.lock generated
View file

@ -8,7 +8,7 @@ version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f"
dependencies = [
"cfg-if",
"cfg-if 1.0.0",
"once_cell",
"version_check",
]
@ -183,6 +183,12 @@ dependencies = [
"nom",
]
[[package]]
name = "cfg-if"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
[[package]]
name = "cfg-if"
version = "1.0.0"
@ -200,6 +206,15 @@ dependencies = [
"libloading",
]
[[package]]
name = "cloudabi"
version = "0.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f"
dependencies = [
"bitflags 1.3.2",
]
[[package]]
name = "codespan-reporting"
version = "0.11.1"
@ -339,7 +354,6 @@ dependencies = [
"fast-float",
"git-version",
"hexponent",
"inventory",
"lazy_static",
"libc",
"lru",
@ -353,6 +367,7 @@ dependencies = [
"rand",
"rand_pcg",
"rsconf",
"serial_test",
"unixstring",
"widestring",
"widestring-suffix",
@ -364,7 +379,7 @@ version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427"
dependencies = [
"cfg-if",
"cfg-if 1.0.0",
"libc",
"wasi",
]
@ -455,12 +470,6 @@ version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa799dd5ed20a7e349f3b4639aa80d74549c81716d9ec4f994c9b5815598306"
[[package]]
name = "inventory"
version = "0.3.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a53088c87cf71c9d4f3372a2cb9eea1e7b8a0b1bf8b7f7d23fe5b76dbb07e63b"
[[package]]
name = "itertools"
version = "0.9.0"
@ -518,7 +527,7 @@ version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f"
dependencies = [
"cfg-if",
"cfg-if 1.0.0",
"winapi",
]
@ -537,6 +546,15 @@ version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503"
[[package]]
name = "lock_api"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4da24a77a3d8a6d4862d95f72e6fdb9c09a643ecdb402d754004a557f2bec75"
dependencies = [
"scopeguard",
]
[[package]]
name = "log"
version = "0.4.20"
@ -604,7 +622,7 @@ checksum = "f346ff70e7dbfd675fe90590b92d59ef2de15a8779ae305ebcbfd3f0caf59be4"
dependencies = [
"autocfg",
"bitflags 1.3.2",
"cfg-if",
"cfg-if 1.0.0",
"libc",
]
@ -644,6 +662,30 @@ version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
[[package]]
name = "parking_lot"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3a704eb390aafdc107b0e392f56a82b668e3a71366993b5340f5833fd62505e"
dependencies = [
"lock_api",
"parking_lot_core",
]
[[package]]
name = "parking_lot_core"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b93f386bb233083c799e6e642a9d73db98c24a5deeb95ffc85bf281255dffc98"
dependencies = [
"cfg-if 0.1.10",
"cloudabi",
"libc",
"redox_syscall 0.1.57",
"smallvec",
"winapi",
]
[[package]]
name = "pcre2"
version = "0.2.3"
@ -791,6 +833,12 @@ dependencies = [
"rand_core",
]
[[package]]
name = "redox_syscall"
version = "0.1.57"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce"
[[package]]
name = "redox_syscall"
version = "0.3.5"
@ -868,6 +916,12 @@ version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741"
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "scratch"
version = "1.0.7"
@ -905,12 +959,40 @@ dependencies = [
"serde",
]
[[package]]
name = "serial_test"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fef5f7c7434b2f2c598adc6f9494648a1e41274a75c0ba4056f680ae0c117fd6"
dependencies = [
"lazy_static",
"parking_lot",
"serial_test_derive",
]
[[package]]
name = "serial_test_derive"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d08338d8024b227c62bd68a12c7c9883f5c66780abaef15c550dc56f46ee6515"
dependencies = [
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "shlex"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3"
[[package]]
name = "smallvec"
version = "1.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970"
[[package]]
name = "strum_macros"
version = "0.24.3"
@ -952,9 +1034,9 @@ version = "3.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc02fddf48964c42031a0b3fe0428320ecf3a73c401040fc0096f97794310651"
dependencies = [
"cfg-if",
"cfg-if 1.0.0",
"fastrand",
"redox_syscall",
"redox_syscall 0.3.5",
"rustix",
"windows-sys",
]
@ -994,7 +1076,7 @@ version = "1.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152"
dependencies = [
"cfg-if",
"cfg-if 1.0.0",
"once_cell",
]

View file

@ -46,7 +46,6 @@ autocxx = "0.23.1"
bitflags = "2.4.0"
cxx = "1.0"
errno = "0.2.8"
inventory = { version = "0.3.3", optional = true}
lazy_static = "1.4.0"
libc = "0.2.137"
lru = "0.10.0"
@ -62,6 +61,9 @@ widestring = "1.0.2"
rand_pcg = "0.3.1"
git-version = "0.3"
[dev-dependencies]
serial_test = "0.4.0"
[build-dependencies]
autocxx-build = "0.23.1"
cc = { git = "https://github.com/mqudsi/cc-rs", branch = "fish" }
@ -74,10 +76,7 @@ crate-type = ["staticlib"]
path = "fish-rust/src/lib.rs"
[features]
# The fish-ffi-tests feature causes tests to be built which need to use the FFI.
# These tests are run by fish_tests().
default = ["fish-ffi-tests"]
fish-ffi-tests = ["inventory"]
default = []
benchmark = []
# The following features are auto-detected by the build-script and should not be enabled manually.

View file

@ -25,7 +25,7 @@ set(fish_rust_target "fish-rust")
set(fish_autocxx_gen_dir "${CMAKE_BINARY_DIR}/fish-autocxx-gen/")
set(FISH_CRATE_FEATURES "fish-ffi-tests")
set(FISH_CRATE_FEATURES)
if(NOT DEFINED CARGO_FLAGS)
# Corrosion doesn't like an empty string as FLAGS. This is basically a no-op alternative.
# See https://github.com/corrosion-rs/corrosion/issues/356
@ -33,13 +33,13 @@ if(NOT DEFINED CARGO_FLAGS)
endif()
if(DEFINED ASAN)
list(APPEND CARGO_FLAGS "-Z" "build-std")
list(APPEND FISH_CRATE_FEATURES "asan")
list(APPEND FISH_CRATE_FEATURES FEATURES "asan")
endif()
corrosion_import_crate(
MANIFEST_PATH "${CMAKE_SOURCE_DIR}/Cargo.toml"
CRATES "fish-rust"
FEATURES "${FISH_CRATE_FEATURES}"
"${FISH_CRATE_FEATURES}"
FLAGS "${CARGO_FLAGS}"
)

View file

@ -113,7 +113,6 @@ fn main() {
"fish-rust/src/fd_readable_set.rs",
"fish-rust/src/fds.rs",
"fish-rust/src/ffi_init.rs",
"fish-rust/src/ffi_tests.rs",
"fish-rust/src/fish_indent.rs",
"fish-rust/src/fish_key_reader.rs",
"fish-rust/src/fish.rs",
@ -138,7 +137,6 @@ fn main() {
"fish-rust/src/redirection.rs",
"fish-rust/src/screen.rs",
"fish-rust/src/signal.rs",
"fish-rust/src/smoke.rs",
"fish-rust/src/termsize.rs",
"fish-rust/src/threads.rs",
"fish-rust/src/timer.rs",

View file

@ -11,6 +11,8 @@ use once_cell::sync::Lazy;
use crate::abbrs::abbrs_ffi::abbrs_replacer_t;
use crate::parse_constants::SourceRange;
#[cfg(test)]
use crate::tests::prelude::*;
use pcre2::utf32::Regex;
use self::abbrs_ffi::{abbreviation_t, abbrs_position_t, abbrs_replacement_t};
@ -439,8 +441,10 @@ impl<'a> GlobalAbbrs<'a> {
self.g.erase(name.as_wstr());
}
}
use crate::ffi_tests::add_test;
add_test!("rename_abbrs", || {
#[test]
#[serial]
fn rename_abbrs() {
test_init();
use crate::abbrs::{Abbreviation, Position};
use crate::wchar::prelude::*;
@ -473,4 +477,4 @@ add_test!("rename_abbrs", || {
assert!(abbrs_g.erase(L!("gcc")));
assert!(!abbrs_g.erase(L!("gcc")));
})
});
}

View file

@ -18,6 +18,8 @@ use crate::parse_constants::{
SOURCE_OFFSET_INVALID,
};
use crate::parse_tree::ParseToken;
#[cfg(test)]
use crate::tests::prelude::*;
use crate::tokenizer::{
variable_assignment_equals_pos, TokFlags, TokenType, Tokenizer, TokenizerError,
TOK_ACCEPT_UNFINISHED, TOK_CONTINUE_AFTER_ERROR, TOK_SHOW_COMMENTS,
@ -4011,12 +4013,14 @@ fn keyword_for_token(tok: TokenType, token: &wstr) -> ParseKeyword {
result
}
use crate::ffi_tests::add_test;
add_test!("test_ast_parse", || {
#[test]
#[serial]
fn test_ast_parse() {
test_init();
let src = L!("echo");
let ast = Ast::parse(src, ParseTreeFlags::empty(), None);
assert!(!ast.any_error);
});
}
pub use ast_ffi::{Category, Type};

View file

@ -4,6 +4,8 @@ use crate::common::{escape, ScopeGuard};
use crate::env::Environment;
use crate::io::IoChain;
use crate::parser::Parser;
#[cfg(test)]
use crate::tests::prelude::*;
use crate::wchar::{wstr, WString, L};
use crate::wutil::{file_id_for_path, FileId, INVALID_FILE_ID};
use lru::LruCache;
@ -315,9 +317,11 @@ impl AutoloadFileCache {
}
}
use crate::ffi_tests::add_test;
#[widestring_suffix::widestrs]
add_test!("test_autoload", || {
#[test]
#[serial]
fn test_autoload() {
test_init();
use crate::common::{charptr2wcstring, wcs2zstring, write_loop};
use crate::fds::wopen_cloexec;
use crate::wutil::sprintf;
@ -387,4 +391,4 @@ add_test!("test_autoload", || {
run!("rm -Rf %ls"L, p1);
run!("rm -Rf %ls"L, p2);
});
}

View file

@ -40,7 +40,7 @@ pub mod r#type;
pub mod ulimit;
pub mod wait;
// Note these tests will NOT run with cfg(test).
#[cfg(test)]
mod tests;
mod prelude {

View file

@ -1,14 +1,20 @@
use crate::ffi_tests::add_test;
use crate::tests::prelude::*;
use crate::wchar::prelude::*;
add_test! {"test_string", || {
use crate::parser::Parser;
#[test]
#[serial]
fn test_string() {
test_init();
use crate::builtins::shared::{STATUS_CMD_ERROR, STATUS_CMD_OK, STATUS_INVALID_ARGS};
use crate::builtins::string::string;
use crate::wchar::prelude::*;
use crate::common::escape;
use crate::builtins::shared::{STATUS_CMD_ERROR,STATUS_CMD_OK, STATUS_INVALID_ARGS};
use crate::io::{IoStreams, OutputStream, StringOutputStream};
use crate::future_feature_flags::{scoped_test, FeatureFlag};
use crate::io::{IoStreams, OutputStream, StringOutputStream};
use crate::parser::Parser;
use crate::tests::prelude::*;
use crate::wchar::prelude::*;
test_init();
// avoid 1.3k L!()'s
macro_rules! test_cases {
@ -26,13 +32,21 @@ add_test! {"test_string", || {
let rc = string(parser, &mut streams, args.as_mut_slice()).expect("string failed");
assert_eq!(expected_rc.unwrap(), rc, "string builtin returned unexpected return code");
assert_eq!(
expected_rc.unwrap(),
rc,
"string builtin returned unexpected return code"
);
let actual = escape(outs.contents());
let expected = escape(expected_out);
assert_eq!(expected, actual, "string builtin returned unexpected output");
assert_eq!(
expected, actual,
"string builtin returned unexpected output"
);
}
#[rustfmt::skip]
let tests = test_cases![
(["string", "escape"], STATUS_CMD_ERROR, ""),
(["string", "escape", ""], STATUS_CMD_OK, "''\n"),
@ -269,6 +283,7 @@ add_test! {"test_string", || {
string_test(cmd, expected_status, expected_stdout);
}
#[rustfmt::skip]
let qmark_noglob_tests = test_cases![
(["string", "match", "a*b?c", "axxb?c"], STATUS_CMD_OK, "axxb?c\n"),
(["string", "match", "*?", "a"], STATUS_CMD_ERROR, ""),
@ -284,6 +299,7 @@ add_test! {"test_string", || {
}
});
#[rustfmt::skip]
let qmark_glob_tests = test_cases![
(["string", "match", "a*b?c", "axxbyc"], STATUS_CMD_OK, "axxbyc\n"),
(["string", "match", "*?", "a"], STATUS_CMD_OK, "a\n"),
@ -298,5 +314,4 @@ add_test! {"test_string", || {
string_test(cmd, expected_status, expected_stdout);
}
});
}}
}

View file

@ -1,6 +1,7 @@
use crate::builtins::prelude::*;
use crate::builtins::test::test as builtin_test;
use crate::io::OutputStream;
use crate::tests::prelude::*;
fn run_one_test_test_mbracket(expected: i32, lst: &[&str], bracket: bool) -> bool {
let parser = Parser::principal_parser();
@ -164,7 +165,10 @@ fn test_test() {
assert!(run_test_test(2, &["1", "-eq", "-99999999999999999999999999.9"]));
}
crate::ffi_tests::add_test!("test_test_builtin", || {
#[test]
#[serial]
fn test_test_builtin() {
test_init();
test_test_brackets();
test_test();
});
}

View file

@ -1,5 +1,7 @@
use crate::common::wcs2zstring;
use crate::flog::FLOG;
#[cfg(test)]
use crate::tests::prelude::*;
use crate::wchar::prelude::*;
use crate::wutil::perror;
use libc::{
@ -292,7 +294,10 @@ pub fn make_fd_blocking(fd: RawFd) -> Result<(), io::Error> {
Ok(())
}
crate::ffi_tests::add_test!("test_pipes", || {
#[test]
#[serial]
fn test_pipes() {
test_init();
// Here we just test that each pipe has CLOEXEC set and is in the high range.
// Note pipe creation may fail due to fd exhaustion; don't fail in that case.
let mut pipes = vec![];
@ -309,4 +314,4 @@ crate::ffi_tests::add_test!("test_pipes", || {
assert!(flags & FD_CLOEXEC != 0);
}
}
});
}

View file

@ -1,63 +0,0 @@
//! Support for tests which need to cross the FFI.
//!
//! Because the C++ is not compiled by `cargo test` and there is no natural way to
//! do it, use the following facilities for tests which need to use C++ types.
//! This uses the inventory crate to build a custom-test harness
//! as described at <https://www.infinyon.com/blog/2021/04/rust-custom-test-harness/>
//! See smoke.rs add_test for an example of how to use this.
#[cfg(all(feature = "fish-ffi-tests", not(test)))]
mod ffi_tests_impl {
/// A test which needs to cross the FFI.
#[derive(Debug)]
pub struct FFITest {
pub name: &'static str,
pub func: fn(),
}
/// Add a new test.
/// Example usage:
/// ```
/// add_test!("test_name", || {
/// assert!(1 + 2 == 3);
/// });
/// ```
macro_rules! add_test {
($name:literal, $func:expr) => {
inventory::submit!(crate::ffi_tests::FFITest {
name: $name,
func: $func,
});
};
}
pub(crate) use add_test;
inventory::collect!(crate::ffi_tests::FFITest);
/// Runs all ffi tests.
pub fn run_ffi_tests() {
for test in inventory::iter::<crate::ffi_tests::FFITest> {
println!("Running ffi test {}", test.name);
(test.func)();
}
}
}
#[cfg(not(all(feature = "fish-ffi-tests", not(test))))]
mod ffi_tests_impl {
macro_rules! add_test {
($name:literal, $func:expr) => {};
}
pub(crate) use add_test;
pub fn run_ffi_tests() {}
}
pub(crate) use ffi_tests_impl::*;
#[allow(clippy::module_inception)]
#[cxx::bridge(namespace = rust)]
mod ffi_tests {
extern "Rust" {
fn run_ffi_tests();
}
}

View file

@ -31,8 +31,6 @@ mod future_feature_flags_ffi {
extern "Rust" {
#[cxx_name = "feature_test"]
fn test(flag: FeatureFlag) -> bool;
#[cxx_name = "feature_set"]
fn set(flag: FeatureFlag, value: bool);
#[cxx_name = "feature_set_from_string"]
fn set_from_string(str: wcharz_t);
}
@ -106,7 +104,7 @@ pub const METADATA: &[FeatureMetadata] = &[
];
thread_local!(
#[cfg(any(test, feature = "fish-ffi-tests"))]
#[cfg(test)]
static LOCAL_FEATURES: std::cell::RefCell<Option<Features>> = std::cell::RefCell::new(None);
);
@ -115,11 +113,11 @@ static FEATURES: Features = Features::new();
/// Perform a feature test on the global set of features.
pub fn test(flag: FeatureFlag) -> bool {
#[cfg(any(test, feature = "fish-ffi-tests"))]
#[cfg(test)]
{
LOCAL_FEATURES.with(|fc| fc.borrow().as_ref().unwrap_or(&FEATURES).test(flag))
}
#[cfg(not(any(test, feature = "fish-ffi-tests")))]
#[cfg(not(test))]
{
FEATURES.test(flag)
}
@ -128,7 +126,7 @@ pub fn test(flag: FeatureFlag) -> bool {
pub use test as feature_test;
/// Set a flag.
#[cfg(any(test, feature = "fish-ffi-tests"))]
#[cfg(test)]
pub fn set(flag: FeatureFlag, value: bool) {
LOCAL_FEATURES.with(|fc| fc.borrow().as_ref().unwrap_or(&FEATURES).set(flag, value));
}
@ -139,7 +137,7 @@ pub fn set(flag: FeatureFlag, value: bool) {
/// Unknown features are silently ignored.
pub fn set_from_string<'a>(str: impl Into<&'a wstr>) {
let wstr: &wstr = str.into();
#[cfg(any(test, feature = "fish-ffi-tests"))]
#[cfg(test)]
{
LOCAL_FEATURES.with(|fc| {
fc.borrow()
@ -148,7 +146,7 @@ pub fn set_from_string<'a>(str: impl Into<&'a wstr>) {
.set_from_string(wstr)
});
}
#[cfg(not(any(test, feature = "fish-ffi-tests")))]
#[cfg(not(test))]
{
FEATURES.set_from_string(wstr)
}
@ -216,7 +214,7 @@ impl Features {
}
}
#[cfg(any(test, feature = "fish-ffi-tests"))]
#[cfg(test)]
pub fn scoped_test(flag: FeatureFlag, value: bool, test_fn: impl FnOnce()) {
LOCAL_FEATURES.with(|fc| {
assert!(

View file

@ -2,7 +2,6 @@ use crate::common::{get_by_sorted_name, shell_modes, str2wcstring, Named};
use crate::curses;
use crate::env::{EnvMode, Environment, CURSES_INITIALIZED};
use crate::event;
use crate::ffi_tests::add_test;
use crate::flog::FLOG;
use crate::input_common::{
CharEvent, CharEventType, CharInputStyle, InputEventQueuer, ReadlineCmd, R_END_INPUT_FUNCTIONS,
@ -13,6 +12,8 @@ use crate::reader::{
reader_reading_interrupted, reader_reset_interrupted, reader_schedule_prompt_repaint,
};
use crate::signal::signal_clear_cancel;
#[cfg(test)]
use crate::tests::prelude::*;
use crate::threads::assert_is_main_thread;
use crate::wchar::prelude::*;
use errno::{set_errno, Errno};
@ -1191,7 +1192,10 @@ pub fn input_function_get_code(name: &wstr) -> Option<ReadlineCmd> {
get_by_sorted_name(name, INPUT_FUNCTION_METADATA).map(|md| md.code)
}
add_test!("test_input", || {
#[test]
#[serial]
fn test_input() {
test_init();
use crate::env::EnvStack;
let parser = Parser::new(Arc::pin(EnvStack::new()), false);
let mut input = Inputter::new(parser, libc::STDIN_FILENO);
@ -1233,4 +1237,4 @@ add_test!("test_input", || {
} else if evt.get_readline() != ReadlineCmd::DownLine {
panic!("Expected to read char down_line");
}
});
}

View file

@ -58,7 +58,6 @@ mod fds;
#[allow(unused_imports)]
mod ffi;
mod ffi_init;
mod ffi_tests;
mod fish;
mod fish_indent;
mod fish_key_reader;
@ -98,7 +97,6 @@ mod reader_history_search;
mod redirection;
mod screen;
mod signal;
mod smoke;
mod termsize;
mod threads;
mod timer;
@ -118,6 +116,6 @@ mod widecharwidth;
mod wildcard;
mod wutil;
#[cfg(any(test, feature = "fish-ffi-tests"))]
#[cfg(test)]
#[allow(unused_imports)] // Easy way to suppress warnings while we have two testing modes.
mod tests;

View file

@ -9,7 +9,6 @@ use crate::expand::{
expand_one, expand_to_command_and_args, ExpandFlags, ExpandResultCode, BRACE_BEGIN, BRACE_END,
BRACE_SEP, INTERNAL_SEPARATOR, VARIABLE_EXPAND, VARIABLE_EXPAND_EMPTY, VARIABLE_EXPAND_SINGLE,
};
use crate::ffi_tests::add_test;
use crate::future_feature_flags::{feature_test, FeatureFlag};
use crate::operation_context::OperationContext;
use crate::parse_constants::{
@ -20,6 +19,8 @@ use crate::parse_constants::{
ERROR_NOT_PID, ERROR_NOT_STATUS, ERROR_NO_VAR_NAME, INVALID_BREAK_ERR_MSG,
INVALID_CONTINUE_ERR_MSG, INVALID_PIPELINE_CMD_ERR_MSG, UNKNOWN_BUILTIN_ERR_MSG,
};
#[cfg(test)]
use crate::tests::prelude::*;
use crate::tokenizer::{
comment_end, is_token_delimiter, quote_end, Tok, TokenType, Tokenizer, TOK_ACCEPT_UNFINISHED,
TOK_SHOW_COMMENTS,
@ -1785,7 +1786,10 @@ const TIME_IN_PIPELINE_ERR_MSG: &str =
/// Maximum length of a variable name to show in error reports before truncation
const var_err_len: usize = 16;
add_test!("test_parse_util_cmdsubst_extent", || {
#[test]
#[serial]
fn test_parse_util_cmdsubst_extent() {
test_init();
const a: &wstr = L!("echo (echo (echo hi");
assert_eq!(parse_util_cmdsubst_extent(a, 0), 0..a.len());
assert_eq!(parse_util_cmdsubst_extent(a, 1), 0..a.len());
@ -1799,9 +1803,12 @@ add_test!("test_parse_util_cmdsubst_extent", || {
parse_util_cmdsubst_extent(a, 17),
"echo (echo (".chars().count()..a.len()
);
});
}
add_test!("test_escape_quotes", || {
#[test]
#[serial]
fn test_escape_quotes() {
test_init();
macro_rules! validate {
($cmd:expr, $quote:expr, $no_tilde:expr, $expected:expr) => {
assert_eq!(
@ -1839,9 +1846,12 @@ add_test!("test_escape_quotes", || {
validate!("~abc'def", Some('"'), true, "~abc'def");
validate!("foo\nba'r", Some('"'), false, "foo\"\\n\"ba'r");
validate!("foo\\\\bar", Some('"'), false, "foo\\\\\\\\bar");
});
}
add_test!("test_indents", || {
#[test]
#[serial]
fn test_indents() {
test_init();
// A struct which is either text or a new indent.
struct Segment {
// The indent to set
@ -2025,7 +2035,7 @@ add_test!("test_indents", || {
0, "\nend"
);
})();
});
}
#[cxx::bridge]
mod parse_util_ffi {

View file

@ -582,19 +582,20 @@ impl From<Signal> for NonZeroI32 {
}
// Need to use add_test for wgettext support.
use crate::ffi_tests::add_test;
add_test!("test_signal_name", || {
#[test]
fn test_signal_name() {
let sig = Signal::new(libc::SIGINT);
assert_eq!(sig.name(), "SIGINT");
});
}
fn new_sighupint_checker() -> Box<SigChecker> {
Box::new(SigChecker::new_sighupint())
}
#[rustfmt::skip]
add_test!("test_signal_parse", || {
#[test]
fn test_signal_parse() {
assert_eq!(Signal::parse(L!("SIGHUP")), Some(Signal::new(libc::SIGHUP)));
assert_eq!(Signal::parse(L!("sigwinch")), Some(Signal::new(libc::SIGWINCH)));
assert_eq!(Signal::parse(L!("TSTP")), Some(Signal::new(libc::SIGTSTP)));
@ -609,7 +610,7 @@ add_test!("test_signal_parse", || {
assert_eq!(Signal::parse(L!("0")), None);
assert_eq!(Signal::parse(L!("-0")), None);
assert_eq!(Signal::parse(L!("-1")), None);
});
}
#[test]
#[cfg(any(target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))]

View file

@ -1,26 +0,0 @@
#[cxx::bridge(namespace = rust)]
mod ffi {
extern "Rust" {
fn add(left: usize, right: usize) -> usize;
}
}
pub fn add(left: usize, right: usize) -> usize {
left + right
}
use crate::ffi_tests::add_test;
add_test!("test_add", || {
assert_eq!(add(2, 3), 5);
});
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
let result = add(2, 2);
assert_eq!(result, 4);
}
}

View file

@ -2,6 +2,8 @@
use crate::common::assert_sync;
use crate::env::{EnvMode, EnvVar, Environment};
use crate::flog::FLOG;
#[cfg(test)]
use crate::tests::prelude::*;
use crate::wchar::prelude::*;
use crate::wutil::fish_wcstoi;
use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
@ -288,10 +290,11 @@ pub fn termsize_invalidate_tty() {
TermsizeContainer::invalidate_tty();
}
use crate::ffi_tests::add_test;
use self::termsize_ffi::Parser;
add_test!("test_termsize", || {
#[test]
#[serial]
fn test_termsize() {
test_init();
let env_global = EnvMode::GLOBAL;
let parser = Parser::principal_parser();
let vars = parser.vars();
@ -364,4 +367,4 @@ add_test!("test_termsize", || {
assert_eq!(ts.last(), Termsize::new(83, 38));
TermsizeContainer::handle_winch();
assert_eq!(ts2.updating(parser), stubby_termsize().unwrap());
});
}

View file

@ -7,9 +7,13 @@ use crate::reader::{
combine_command_and_autosuggestion, completion_apply_to_command_line,
reader_expand_abbreviation_at_cursor,
};
use crate::tests::prelude::*;
use crate::wchar::prelude::*;
crate::ffi_tests::add_test!("test_abbreviations", || {
#[test]
#[serial]
fn test_abbreviations() {
test_init();
{
let mut abbrs = abbrs_get_set();
abbrs.add(Abbreviation::new(
@ -136,4 +140,4 @@ crate::ffi_tests::add_test!("test_abbreviations", || {
// yin/yang expands everywhere.
validate!("command yin", None, "command yang");
});
}

View file

@ -5,7 +5,6 @@ use crate::complete::{
CompletionList, CompletionMode, CompletionRequestOptions,
};
use crate::env::{EnvMode, Environment};
use crate::ffi_tests::add_test;
use crate::io::IoChain;
use crate::operation_context::{
no_cancel, OperationContext, EXPANSION_LIMIT_BACKGROUND, EXPANSION_LIMIT_DEFAULT,
@ -13,6 +12,7 @@ use crate::operation_context::{
use crate::parser::Parser;
use crate::reader::completion_apply_to_command_line;
use crate::tests::prelude::*;
use crate::tests::prelude::*;
use crate::wchar::prelude::*;
use crate::wcstringutil::join_strings;
use std::collections::HashMap;
@ -23,7 +23,10 @@ fn comma_join(lst: Vec<WString>) -> WString {
join_strings(&lst, ',')
}
add_test!("test_complete", || {
#[test]
#[serial]
fn test_complete() {
test_init();
let vars = PwdEnvironment {
parent: TestEnvironment {
vars: HashMap::from([
@ -424,11 +427,14 @@ add_test!("test_complete", || {
complete_remove_wrapper(L!("cdwrap1").into(), L!("cd"));
complete_remove_wrapper(L!("cdwrap2").into(), L!("cdwrap1"));
popd();
});
}
// Testing test_autosuggest_suggest_special, in particular for properly handling quotes and
// backslashes.
add_test!("test_autosuggest_suggest_special", || {
#[test]
#[serial]
fn test_autosuggest_suggest_special() {
test_init();
macro_rules! perform_one_autosuggestion_cd_test {
($command:literal, $expected:literal, $vars:expr) => {
let mut comps = complete(
@ -593,9 +599,12 @@ add_test!("test_autosuggest_suggest_special", || {
.vars()
.remove(L!("HOME"), EnvMode::LOCAL | EnvMode::EXPORT);
popd();
});
}
add_test!("test_autosuggestion_ignores", || {
#[test]
#[serial]
fn test_autosuggestion_ignores() {
test_init();
// Testing scenarios that should produce no autosuggestions
macro_rules! perform_one_autosuggestion_should_ignore_test {
($command:literal) => {
@ -613,4 +622,4 @@ add_test!("test_autosuggestion_ignores", || {
perform_one_autosuggestion_should_ignore_test!("echo PIPE_TEST&");
perform_one_autosuggestion_should_ignore_test!("echo PIPE_TEST#comment");
perform_one_autosuggestion_should_ignore_test!("echo PIPE_TEST;");
});
}

View file

@ -5,14 +5,17 @@ use std::sync::{
use std::time::Duration;
use crate::common::ScopeGuard;
use crate::ffi_tests::add_test;
use crate::global_safety::RelaxedAtomicBool;
use crate::parser::Parser;
use crate::reader::{reader_current_data, reader_pop, reader_push, ReaderConfig, ReaderData};
use crate::tests::prelude::*;
use crate::threads::{iothread_drain_all, iothread_service_main, Debounce};
use crate::wchar::prelude::*;
add_test!("test_debounce", || {
#[test]
#[serial]
fn test_debounce() {
test_init();
// Run 8 functions using a condition variable.
// Only the first and last should run.
let db = Debounce::new(Duration::from_secs(0));
@ -80,9 +83,12 @@ add_test!("test_debounce", || {
assert_eq!(ctx.handler_ran[idx].load(), ctx.completion_ran[idx].load());
}
assert!(total_ran <= 2);
});
}
add_test!("test_debounce_timeout", || {
#[test]
#[serial]
fn test_debounce_timeout() {
test_init();
// Verify that debounce doesn't wait forever.
// Use a shared_ptr so we don't have to join our threads.
let timeout = Duration::from_millis(500);
@ -126,4 +132,4 @@ add_test!("test_debounce_timeout", || {
let mut exit_ok = data.exit_ok.lock().unwrap();
*exit_ok = true;
data.cv.notify_all();
});
}

View file

@ -1,5 +1,4 @@
use crate::env::{EnvMode, EnvVar, EnvVarFlags, Environment};
use crate::ffi_tests::add_test;
use crate::parser::Parser;
use crate::tests::prelude::*;
use crate::wchar::prelude::*;
@ -89,7 +88,10 @@ fn test_timezone_env_vars() {
}
// Verify that setting special env vars have the expected effect on the current shell process.
add_test!("test_env_vars", || {
#[test]
#[serial]
fn test_env_vars() {
test_init();
test_timezone_env_vars();
// TODO: Add tests for the locale and ncurses vars.
@ -103,9 +105,12 @@ add_test!("test_env_vars", || {
assert!(v1 == v2 && !(v1 != v2));
assert!(v1 != v3 && !(v1 == v3));
assert!(v1 != v4 && !(v1 == v4));
});
}
add_test!("test_env_snapshot", || {
#[test]
#[serial]
fn test_env_snapshot() {
test_init();
std::fs::create_dir_all("test/fish_env_snapshot_test/").unwrap();
pushd("test/fish_env_snapshot_test/");
let vars = Parser::principal_parser().vars();
@ -171,4 +176,4 @@ add_test!("test_env_snapshot", || {
vars.pop();
popd();
});
}

View file

@ -2,10 +2,10 @@ use crate::common::wcs2osstring;
use crate::common::ScopeGuard;
use crate::env::{EnvVar, EnvVarFlags, VarTable};
use crate::env_universal_common::{CallbackDataList, EnvUniversal, UvarFormat};
use crate::ffi_tests::add_test;
use crate::flog::FLOG;
use crate::parser::Parser;
use crate::reader::{reader_current_data, reader_pop, reader_push, ReaderConfig};
use crate::tests::prelude::*;
use crate::threads::{iothread_drain_all, iothread_perform};
use crate::wchar::prelude::*;
use crate::wutil::file_id_for_path;
@ -15,6 +15,7 @@ const UVARS_PER_THREAD: usize = 8;
const UVARS_TEST_PATH: &wstr = L!("test/fish_uvars_test/varsfile.txt");
fn test_universal_helper(x: usize) {
test_init();
let mut callbacks = CallbackDataList::new();
let mut uvars = EnvUniversal::new();
uvars.initialize_at_path(&mut callbacks, UVARS_TEST_PATH.to_owned());
@ -36,7 +37,10 @@ fn test_universal_helper(x: usize) {
assert!(synced, "Failed to sync universal variables after deletion");
}
add_test!("test_universal", || {
#[test]
#[serial]
fn test_universal() {
test_init();
let _ = std::fs::remove_dir_all("test/fish_uvars_test/");
std::fs::create_dir_all("test/fish_uvars_test/").unwrap();
@ -70,9 +74,12 @@ add_test!("test_universal", || {
}
std::fs::remove_dir_all("test/fish_uvars_test/").unwrap();
});
}
add_test!("test_universal_output", || {
#[test]
#[serial]
fn test_universal_output() {
test_init();
let flag_export = EnvVarFlags::EXPORT;
let flag_pathvar = EnvVarFlags::PATHVAR;
@ -116,9 +123,10 @@ add_test!("test_universal_output", || {
)
.as_bytes();
assert_eq!(text, expected);
});
}
fn test_universal_parsing() {
test_init();
let input = concat!(
"# This file contains fish universal variable definitions.\n",
"# VERSION: 3.0\n",
@ -167,7 +175,10 @@ fn test_universal_parsing() {
assert_eq!(vars, parsed_vars);
}
add_test!("test_universal_parsing_legacy", || {
#[test]
#[serial]
fn test_universal_parsing_legacy() {
test_init();
let input = concat!(
"# This file contains fish universal variable definitions.\n",
"SET varA:ValA1\\x1eValA2\n",
@ -191,9 +202,12 @@ add_test!("test_universal_parsing_legacy", || {
let mut parsed_vars = VarTable::new();
EnvUniversal::populate_variables(input, &mut parsed_vars);
assert_eq!(vars, parsed_vars);
});
}
add_test!("test_universal_callbacks", || {
#[test]
#[serial]
fn test_universal_callbacks() {
test_init();
std::fs::create_dir_all("test/fish_uvars_test/").unwrap();
let mut callbacks = CallbackDataList::new();
let mut uvars1 = EnvUniversal::new();
@ -245,9 +259,12 @@ add_test!("test_universal_callbacks", || {
assert_eq!(callbacks[2].key, L!("delta"));
assert_eq!(callbacks[2].val, None);
std::fs::remove_dir_all("test/fish_uvars_test/").unwrap();
});
}
add_test!("test_universal_formats", || {
#[test]
#[serial]
fn test_universal_formats() {
test_init();
macro_rules! validate {
( $version_line:literal, $expected_format:expr ) => {
assert_eq!(
@ -264,9 +281,12 @@ add_test!("test_universal_formats", || {
validate!(b"# blah\n#VERSION: 3.0", UvarFormat::fish_3_0);
validate!(b"# blah\n#VERSION:3.0", UvarFormat::fish_3_0);
validate!(b"# blah\n#VERSION:3.1", UvarFormat::future);
});
}
add_test!("test_universal_ok_to_save", || {
#[test]
#[serial]
fn test_universal_ok_to_save() {
test_init();
// Ensure we don't try to save after reading from a newer fish.
std::fs::create_dir_all("test/fish_uvars_test/").unwrap();
let contents = b"# VERSION: 99999.99\n";
@ -297,4 +317,4 @@ add_test!("test_universal_ok_to_save", || {
"UVARS_TEST_PATH should not have changed",
);
std::fs::remove_dir_all("test/fish_uvars_test/").unwrap();
});
}

View file

@ -4,11 +4,11 @@ use crate::abbrs::{with_abbrs, with_abbrs_mut};
use crate::complete::{CompletionList, CompletionReceiver};
use crate::env::{EnvMode, EnvStackSetResult};
use crate::expand::{expand_to_receiver, ExpandResultCode};
use crate::ffi_tests::add_test;
use crate::operation_context::{no_cancel, EXPANSION_LIMIT_DEFAULT};
use crate::parse_constants::ParseErrorList;
use crate::parser::Parser;
use crate::tests::prelude::*;
use crate::tests::prelude::*;
use crate::wildcard::ANY_STRING;
use crate::{
expand::{expand_string, ExpandFlags},
@ -63,7 +63,10 @@ fn expand_test_impl(
}
// Test globbing and other parameter expansion.
add_test!("test_expand", || {
#[test]
#[serial]
fn test_expand() {
test_init();
/// Perform parameter expansion and test if the output equals the zero-terminated parameter list /// supplied.
///
/// \param in the string to expand
@ -342,9 +345,12 @@ add_test!("test_expand", || {
expand_test!("l///n", fuzzy_comp, "lol///nub/", "Wrong fuzzy matching 6");
popd();
});
}
add_test!("test_expand_overflow", || {
#[test]
#[serial]
fn test_expand_overflow() {
test_init();
// Testing overflowing expansions
// Ensure that we have sane limits on number of expansions - see #7497.
@ -376,9 +382,12 @@ add_test!("test_expand_overflow", || {
assert_eq!(res, ExpandResultCode::error);
parser.vars().pop();
});
}
add_test!("test_abbreviations", || {
#[test]
#[serial]
fn test_abbreviations() {
test_init();
// Testing abbreviations
with_abbrs_mut(|abbrset| {
@ -442,6 +451,4 @@ add_test!("test_abbreviations", || {
);
assert_eq!(abbr_expand_1(L!("foo"), cmd), Some(L!("bar").into()));
// todo!("port the rest");
});
}

View file

@ -8,7 +8,7 @@ use crate::fd_monitor::{
FdEventSignaller, FdMonitor, FdMonitorItem, FdMonitorItemId, ItemWakeReason,
};
use crate::fds::{make_autoclose_pipes, AutoCloseFd};
use crate::ffi_tests::add_test;
use crate::tests::prelude::*;
/// Helper to make an item which counts how many times its callback was invoked.
///
@ -104,7 +104,10 @@ impl ItemMaker {
}
}
add_test!("fd_monitor_items", || {
#[test]
#[serial]
fn fd_monitor_items() {
test_init();
let monitor = FdMonitor::new();
// Items which will never receive data or be called.
@ -189,7 +192,7 @@ add_test!("fd_monitor_items", || {
assert_eq!(item_pokee.length_read.load(Ordering::Relaxed), 0);
assert_eq!(item_pokee.total_calls.load(Ordering::Relaxed), 1);
assert_eq!(item_pokee.pokes.load(Ordering::Relaxed), 1);
});
}
#[test]
fn test_fd_event_signaller() {

View file

@ -1,6 +1,5 @@
use crate::common::ScopeGuard;
use crate::env::EnvMode;
use crate::ffi_tests::add_test;
use crate::future_feature_flags::{self, FeatureFlag};
use crate::parser::Parser;
use crate::tests::prelude::*;
@ -25,7 +24,10 @@ fn get_overlong_path() -> String {
longpath
}
add_test!("test_is_potential_path", || {
#[test]
#[serial]
fn test_is_potential_path() {
test_init();
// Directories
std::fs::create_dir_all("test/is_potential_path_test/alpha/").unwrap();
std::fs::create_dir_all("test/is_potential_path_test/beta/").unwrap();
@ -156,9 +158,12 @@ add_test!("test_is_potential_path", || {
&ctx,
PathFlags::PATH_REQUIRE_DIR
));
});
}
add_test!("test_highlighting", || {
#[test]
#[serial]
fn test_highlighting() {
test_init();
// Testing syntax highlighting
pushd("test/fish_highlight_test/");
let _popd = ScopeGuard::new((), |_| popd());
@ -634,4 +639,4 @@ add_test!("test_highlighting", || {
(">", fg(HighlightRole::error)),
("echo", fg(HighlightRole::error)),
);
});
}

View file

@ -3,10 +3,10 @@ use crate::common::{
};
use crate::env::{EnvDyn, EnvMode, EnvStack, Environment};
use crate::fds::{wopen_cloexec, AutoCloseFd};
use crate::ffi_tests::add_test;
use crate::history::{self, History, HistoryItem, HistorySearch, PathList, SearchDirection};
use crate::path::path_get_data;
use crate::tests::prelude::*;
use crate::tests::prelude::*;
use crate::tests::string_escape::ESCAPE_TEST_CHAR;
use crate::wchar::prelude::*;
use crate::wcstringutil::{string_prefixes_string, string_prefixes_string_case_insensitive};
@ -43,7 +43,10 @@ fn random_string() -> WString {
result
}
add_test!("test_history", || {
#[test]
#[serial]
fn test_history() {
test_init();
macro_rules! test_history_matches {
($search:expr, $expected:expr) => {
let expected: Vec<&wstr> = $expected;
@ -199,7 +202,7 @@ add_test!("test_history", || {
// Clean up after our tests.
history.clear();
});
}
// Wait until the next second.
fn time_barrier() {
@ -226,6 +229,7 @@ fn generate_history_lines(item_count: usize, idx: usize) -> Vec<WString> {
}
fn test_history_races_pound_on_history(item_count: usize, idx: usize) {
test_init();
// Called in child thread to modify history.
let hist = History::new(L!("race_test"));
let hist_lines = generate_history_lines(item_count, idx);
@ -235,7 +239,10 @@ fn test_history_races_pound_on_history(item_count: usize, idx: usize) {
}
}
add_test!("test_history_races", || {
#[test]
#[serial]
fn test_history_races() {
test_init();
// This always fails under WSL
if is_windows_subsystem_for_linux() {
return;
@ -331,9 +338,12 @@ add_test!("test_history_races", || {
assert_eq!(list, Vec::<WString>::new(), "Lines still left in the array");
}
hist.clear();
});
}
add_test!("test_history_merge", || {
#[test]
#[serial]
fn test_history_merge() {
test_init();
// In a single fish process, only one history is allowed to exist with the given name But it's
// common to have multiple history instances with the same name active in different processes,
// e.g. when you have multiple shells open. We try to get that right and merge all their history
@ -437,9 +447,12 @@ add_test!("test_history_merge", || {
}
}
everything.clear();
});
}
add_test!("test_history_path_detection", || {
#[test]
#[serial]
fn test_history_path_detection() {
test_init();
// Regression test for #7582.
let tmpdirbuff = CString::new("/tmp/fish_test_history.XXXXXX").unwrap();
let tmpdir = unsafe { libc::mkdtemp(tmpdirbuff.into_raw()) };
@ -541,7 +554,7 @@ add_test!("test_history_path_detection", || {
std::thread::sleep(std::time::Duration::from_millis(2));
}
history.clear();
});
}
fn install_sample_history(name: &wstr) {
let path = path_get_data().expect("Failed to get data directory");
@ -552,7 +565,10 @@ fn install_sample_history(name: &wstr) {
.unwrap();
}
add_test!("test_history_formats", || {
#[test]
#[serial]
fn test_history_formats() {
test_init();
// Test inferring and reading legacy and bash history formats.
let name = L!("history_sample_fish_2_0");
install_sample_history(name);
@ -598,4 +614,4 @@ add_test!("test_history_formats", || {
];
assert_eq!(test_history_imported_from_corrupted.get_history(), expected);
test_history_imported_from_corrupted.clear();
});
}

View file

@ -1,13 +1,10 @@
use crate::wchar::prelude::*;
mod abbrs;
#[cfg(test)]
mod common;
mod complete;
mod debounce;
#[cfg(test)]
mod editable_line;
#[cfg(test)]
mod encoding;
mod env;
mod env_universal_common;
@ -18,15 +15,11 @@ mod history;
mod pager;
mod parse_util;
mod parser;
#[cfg(test)]
mod reader;
#[cfg(test)]
mod redirection;
mod screen;
mod string_escape;
#[cfg(test)]
mod threads;
#[cfg(test)]
mod tokenizer;
mod topic_monitor;
mod wgetopt;
@ -87,4 +80,6 @@ pub mod prelude {
EnvStack::principal().set_pwd_from_getcwd();
});
}
pub use serial_test::serial;
}

View file

@ -1,13 +1,16 @@
use crate::common::get_ellipsis_char;
use crate::complete::{CompleteFlags, Completion};
use crate::ffi_tests::add_test;
use crate::pager::{Pager, SelectionMotion};
use crate::termsize::Termsize;
use crate::tests::prelude::*;
use crate::wchar::prelude::*;
use crate::wchar_ext::WExt;
use crate::wcstringutil::StringFuzzyMatch;
add_test!("test_pager_navigation", || {
#[test]
#[serial]
fn test_pager_navigation() {
test_init();
// Generate 19 strings of width 10. There's 2 spaces between completions, and our term size is
// 80; these can therefore fit into 6 columns (6 * 12 - 2 = 70) or 5 columns (58) but not 7
// columns (7 * 12 - 2 = 82).
@ -92,9 +95,12 @@ add_test!("test_pager_navigation", || {
validate!(pager, render, SelectionMotion::North, 2);
validate!(pager, render, SelectionMotion::PageNorth, 0);
validate!(pager, render, SelectionMotion::PageSouth, 3);
});
}
add_test!("test_pager_layout", || {
#[test]
#[serial]
fn test_pager_layout() {
test_init();
// These tests are woefully incomplete
// They only test the truncation logic for a single completion
@ -189,4 +195,4 @@ add_test!("test_pager_layout", || {
validate!(&mut pager, 18, L!("abcdefghijklmnopq…"));
validate!(&mut pager, 17, L!("abcdefghijklmnop…"));
validate!(&mut pager, 16, L!("abcdefghijklmno…"));
});
}

View file

@ -1,16 +1,19 @@
use pcre2::utf32::Regex;
use crate::ffi_tests::add_test;
use crate::parse_constants::{
ERROR_BAD_VAR_CHAR1, ERROR_BRACKETED_VARIABLE1, ERROR_BRACKETED_VARIABLE_QUOTED1,
ERROR_NOT_ARGV_AT, ERROR_NOT_ARGV_COUNT, ERROR_NOT_ARGV_STAR, ERROR_NOT_PID, ERROR_NOT_STATUS,
ERROR_NO_VAR_NAME,
};
use crate::parse_util::parse_util_detect_errors;
use crate::tests::prelude::*;
use crate::wchar::prelude::*;
use crate::wchar_ext::WExt;
add_test!("test_error_messages", || {
#[test]
#[serial]
fn test_error_messages() {
test_init();
// Given a format string, returns a list of non-empty strings separated by format specifiers. The
// format specifiers themselves are omitted.
fn separate_by_format_specifiers(format: &wstr) -> Vec<&wstr> {
@ -67,4 +70,4 @@ add_test!("test_error_messages", || {
validate!("echo foo\"$\"bar", ERROR_NO_VAR_NAME);
validate!("echo \"foo\"$\"bar\"", ERROR_NO_VAR_NAME);
validate!("echo foo $ bar", ERROR_NO_VAR_NAME);
});
}

View file

@ -13,14 +13,17 @@ use crate::reader::{
};
use crate::signal::{signal_clear_cancel, signal_reset_handlers, signal_set_handlers};
use crate::tests::prelude::*;
use crate::tests::prelude::*;
use crate::threads::{iothread_drain_all, iothread_perform};
use crate::wchar::prelude::*;
use crate::wcstringutil::join_strings;
use libc::SIGINT;
use std::time::Duration;
use crate::ffi_tests::add_test;
add_test!("test_parser", || {
#[test]
#[serial]
fn test_parser() {
test_init();
macro_rules! detect_errors {
($src:literal) => {
parse_util_detect_errors(L!($src), None, true /* accept incomplete */)
@ -297,9 +300,12 @@ add_test!("test_parser", || {
detect_errors!("true || \n") == Err(ParserTestErrorBits::INCOMPLETE),
"unterminated conjunction not reported properly"
);
});
}
add_test!("test_new_parser_correctness", || {
#[test]
#[serial]
fn test_new_parser_correctness() {
test_init();
macro_rules! validate {
($src:expr, $ok:expr) => {
let ast = Ast::parse(L!($src), ParseTreeFlags::default(), None);
@ -324,9 +330,12 @@ add_test!("test_new_parser_correctness", || {
validate!("true || ||", false);
validate!("|| true", false);
validate!("true || \n\n false", true);
});
}
add_test!("test_new_parser_correctness", || {
#[test]
#[serial]
fn test_new_parser_correctness_by_fuzzing() {
test_init();
let fuzzes = [
L!("if"),
L!("else"),
@ -379,13 +388,16 @@ add_test!("test_new_parser_correctness", || {
Ast::parse(&src, ParseTreeFlags::default(), None);
}
}
});
}
// Test the LL2 (two token lookahead) nature of the parser by exercising the special builtin and
// command handling. In particular, 'command foo' should be a decorated statement 'foo' but 'command
// -help' should be an undecorated statement 'command' with argument '--help', and NOT attempt to
// run a command called '--help'.
add_test!("test_new_parser_ll2", || {
#[test]
#[serial]
fn test_new_parser_ll2() {
test_init();
// Parse a statement, returning the command, args (joined by spaces), and the decoration. Returns
// true if successful.
fn test_1_parse_ll2(src: &wstr) -> Option<(WString, WString, StatementDecoration)> {
@ -501,9 +513,12 @@ add_test!("test_new_parser_ll2", || {
check_function_help!("function --help", ast::Type::decorated_statement);
check_function_help!("function --foo; end", ast::Type::function_header);
check_function_help!("function foo; end", ast::Type::function_header);
});
}
add_test!("test_new_parser_ad_hoc", || {
#[test]
#[serial]
fn test_new_parser_ad_hoc() {
test_init();
// Very ad-hoc tests for issues encountered.
// Ensure that 'case' terminates a job list.
@ -559,9 +574,12 @@ add_test!("test_new_parser_ad_hoc", || {
);
assert!(errors.len() == 1);
assert!(errors[0].code == ParseErrorCode::tokenizer_unterminated_quote);
});
}
add_test!("test_new_parser_errors", || {
#[test]
#[serial]
fn test_new_parser_errors() {
test_init();
macro_rules! validate {
($src:expr, $expected_code:expr) => {
let mut errors = vec![];
@ -590,9 +608,12 @@ add_test!("test_new_parser_errors", || {
validate!("true | and", ParseErrorCode::andor_in_pipeline);
validate!("a=", ParseErrorCode::bare_variable_assignment);
});
}
add_test!("test_eval_recursion_detection", || {
#[test]
#[serial]
fn test_eval_recursion_detection() {
test_init();
// Ensure that we don't crash on infinite self recursion and mutual recursion. These must use
// the principal parser because we cannot yet execute jobs on other parsers.
let parser = Parser::principal_parser().shared();
@ -608,9 +629,12 @@ add_test!("test_eval_recursion_detection", || {
)),
&IoChain::new(),
);
});
}
add_test!("test_eval_illegal_exit_code", || {
#[test]
#[serial]
fn test_eval_illegal_exit_code() {
test_init();
macro_rules! validate {
($cmd:expr, $result:expr) => {
let parser = Parser::principal_parser();
@ -637,17 +661,23 @@ add_test!("test_eval_illegal_exit_code", || {
validate!(L!("?"), STATUS_UNMATCHED_WILDCARD.unwrap());
validate!(L!("abc?def"), STATUS_UNMATCHED_WILDCARD.unwrap());
popd();
});
}
add_test!("test_eval_empty_function_name", || {
#[test]
#[serial]
fn test_eval_empty_function_name() {
test_init();
let parser = Parser::principal_parser().shared();
parser.eval(
L!("function '' ; echo fail; exit 42 ; end ; ''"),
&IoChain::new(),
);
});
}
add_test!("test_expand_argument_list", || {
#[test]
#[serial]
fn test_expand_argument_list() {
test_init();
let parser = Parser::principal_parser().shared();
let comps: Vec<WString> = Parser::expand_argument_list(
L!("alpha 'beta gamma' delta"),
@ -658,7 +688,7 @@ add_test!("test_expand_argument_list", || {
.map(|c| c.completion)
.collect();
assert_eq!(comps, &[L!("alpha"), L!("beta gamma"), L!("delta"),]);
});
}
fn test_1_cancellation(src: &wstr) {
let filler = IoBufferfill::create().unwrap();
@ -688,7 +718,10 @@ fn test_1_cancellation(src: &wstr) {
}
}
add_test!("test_cancellation", || {
#[test]
#[serial]
fn test_cancellation() {
test_init();
reader_push(Parser::principal_parser(), L!(""), ReaderConfig::default());
let _pop = ScopeGuard::new((), |()| reader_pop());
@ -717,4 +750,4 @@ add_test!("test_cancellation", || {
// Ensure that we don't think we should cancel.
reader_reset_interrupted();
signal_clear_cancel();
});
}

View file

@ -1,10 +1,13 @@
use crate::common::get_ellipsis_char;
use crate::ffi_tests::add_test;
use crate::screen::{LayoutCache, PromptCacheEntry, PromptLayout};
use crate::tests::prelude::*;
use crate::wchar::prelude::*;
use crate::wcstringutil::join_strings;
add_test!("test_complete", || {
#[test]
#[serial]
fn test_complete() {
test_init();
let mut lc = LayoutCache::new();
assert_eq!(lc.escape_code_length(L!("")), 0);
assert_eq!(lc.escape_code_length(L!("abcd")), 0);
@ -34,9 +37,12 @@ add_test!("test_complete", || {
);
assert_eq!(lc.escape_code_length(L!("\x1B]blahblahblah\x1B\\")), 16);
assert_eq!(lc.escape_code_length(L!("\x1B]blahblahblah\x07")), 15);
});
}
add_test!("test_layout_cache", || {
#[test]
#[serial]
fn test_layout_cache() {
test_init();
let mut seqs = LayoutCache::new();
// Verify escape code cache.
@ -105,9 +111,12 @@ add_test!("test_layout_cache", || {
seqs.prompt_cache.front().unwrap().layout.max_line_width,
100
);
});
}
add_test!("test_prompt_truncation", || {
#[test]
#[serial]
fn test_prompt_truncation() {
test_init();
let mut cache = LayoutCache::new();
let mut trunc = WString::new();
@ -235,4 +244,4 @@ add_test!("test_prompt_truncation", || {
},
);
assert_eq!(trunc, ellipsis());
});
}

View file

@ -1,11 +1,14 @@
use crate::ffi_tests::add_test;
use crate::tests::prelude::*;
use crate::topic_monitor::{topic_monitor_t, topic_t, GenerationsList};
use std::sync::{
atomic::{AtomicU32, AtomicU64, Ordering},
Arc,
};
add_test!("test_topic_monitor", || {
#[test]
#[serial]
fn test_topic_monitor() {
test_init();
let monitor = topic_monitor_t::default();
let gens = GenerationsList::new();
let t = topic_t::sigchld;
@ -26,9 +29,12 @@ add_test!("test_topic_monitor", || {
let changed = monitor.check(&gens, true /* wait */);
assert!(changed);
assert_eq!(gens.sigchld.get(), 2);
});
}
add_test!("test_topic_monitor_torture", || {
#[test]
#[serial]
fn test_topic_monitor_torture() {
test_init();
let monitor = Arc::new(topic_monitor_t::default());
const THREAD_COUNT: usize = 64;
let t1 = topic_t::sigchld;
@ -68,4 +74,4 @@ add_test!("test_topic_monitor_torture", || {
for t in threads {
t.join().unwrap();
}
});
}

View file

@ -23,7 +23,10 @@ impl FloggableDebug for ThreadId {}
/// The thread id of the main thread, as set by [`init()`] at startup.
static mut MAIN_THREAD_ID: Option<ThreadId> = None;
/// Used to bypass thread assertions when testing.
#[cfg(not(test))]
static THREAD_ASSERTS_CFG_FOR_TESTING: AtomicBool = AtomicBool::new(false);
#[cfg(test)]
static THREAD_ASSERTS_CFG_FOR_TESTING: AtomicBool = AtomicBool::new(true);
/// This allows us to notice when we've forked.
static IS_FORKED_PROC: AtomicBool = AtomicBool::new(false);

View file

@ -7,6 +7,8 @@ use std::sync::Mutex;
use crate::common::{charptr2wcstring, wcs2zstring};
use crate::fish::PACKAGE_NAME;
#[cfg(test)]
use crate::tests::prelude::*;
use crate::wchar::prelude::*;
use crate::wchar_ffi::wchar_t;
use errno::{errno, set_errno};
@ -150,10 +152,12 @@ macro_rules! wgettext_maybe_fmt {
}
pub(crate) use wgettext_maybe_fmt;
use crate::ffi_tests::add_test;
add_test!("test_untranslated", || {
#[test]
#[serial]
fn test_untranslated() {
test_init();
let s: &'static wstr = wgettext!("abc");
assert_eq!(s, "abc");
let s2: &'static wstr = wgettext!("static");
assert_eq!(s2, "static");
});
}

View file

@ -1,4 +1,4 @@
use crate::ffi_tests::add_test;
use crate::tests::prelude::*;
use libc::{c_void, O_CREAT, O_RDWR, O_TRUNC, SEEK_SET};
use rand::random;
use std::{ffi::CString, ptr};
@ -57,7 +57,10 @@ fn test_wdirname_wbasename() {
assert_eq!(wbasename(&longpath), "overlong"L);
}
add_test!("test_wwrite_to_fd", || {
#[test]
#[serial]
fn test_wwrite_to_fd() {
test_init();
let (fd, filename) =
fish_mkstemp_cloexec(CString::new("/tmp/fish_test_wwrite.XXXXXX").unwrap());
{
@ -98,4 +101,4 @@ add_test!("test_wwrite_to_fd", || {
assert_eq!(&contents, &narrow);
}
unsafe { libc::remove(filename.as_ptr()) };
});
}

View file

@ -66,7 +66,6 @@
#include "fds.h"
#include "ffi_baggage.h"
#include "ffi_init.rs.h"
#include "ffi_tests.rs.h"
#include "function.h"
#include "future_feature_flags.h"
#include "global_safety.h"
@ -90,7 +89,6 @@
#include "redirection.h"
#include "screen.h"
#include "signals.h"
#include "smoke.rs.h"
#include "termsize.h"
#include "threads.rs.h"
#include "tokenizer.h"
@ -801,13 +799,6 @@ void test_dirname_basename() {
do_test(wbasename(longpath) == L"overlong");
}
void test_rust_smoke() {
size_t x = rust::add(37, 5);
do_test(x == 42);
}
void test_rust_ffi() { rust::run_ffi_tests(); }
// typedef void (test_entry_point_t)();
using test_entry_point_t = void (*)();
struct test_t {
@ -841,8 +832,6 @@ static const test_t s_tests[]{
{TEST_GROUP("maybe"), test_maybe},
{TEST_GROUP("normalize"), test_normalize_path},
{TEST_GROUP("dirname"), test_dirname_basename},
{TEST_GROUP("rust_smoke"), test_rust_smoke},
{TEST_GROUP("rust_ffi"), test_rust_ffi},
};
void list_tests() {