fish-shell/src/tests/env_universal_common.rs

332 lines
11 KiB
Rust
Raw Normal View History

use crate::common::char_offset;
use crate::common::wcs2osstring;
2023-12-22 19:27:01 +08:00
use crate::common::ScopeGuard;
use crate::common::ENCODE_DIRECT_BASE;
use crate::env::{EnvVar, EnvVarFlags, VarTable};
use crate::env_universal_common::{CallbackDataList, EnvUniversal, UvarFormat};
2023-12-22 19:27:01 +08:00
use crate::parser::Parser;
use crate::reader::{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;
use crate::wutil::INVALID_FILE_ID;
const UVARS_PER_THREAD: usize = 8;
const UVARS_TEST_PATH: &wstr = L!("test/fish_uvars_test/varsfile.txt");
fn test_universal_helper(x: usize) {
let _cleanup = test_init();
let mut callbacks = CallbackDataList::new();
let mut uvars = EnvUniversal::new();
uvars.initialize_at_path(&mut callbacks, UVARS_TEST_PATH.to_owned());
for j in 0..UVARS_PER_THREAD {
let key = sprintf!("key_%d_%d", x, j);
let val = sprintf!("val_%d_%d", x, j);
uvars.set(&key, EnvVar::new(val, EnvVarFlags::empty()));
let synced = uvars.sync(&mut callbacks);
assert!(
synced,
"Failed to sync universal variables after modification"
);
}
// Last step is to delete the first key.
uvars.remove(&sprintf!("key_%d_%d", x, 0));
let synced = uvars.sync(&mut callbacks);
assert!(synced, "Failed to sync universal variables after deletion");
}
#[test]
#[serial]
fn test_universal() {
let _cleanup = test_init();
let _ = std::fs::remove_dir_all("test/fish_uvars_test/");
std::fs::create_dir_all("test/fish_uvars_test/").unwrap();
let mut reader = reader_push(Parser::principal_parser(), L!(""), ReaderConfig::default());
2023-12-22 19:27:01 +08:00
let _pop = ScopeGuard::new((), |()| reader_pop());
let threads = 1;
for i in 0..threads {
iothread_perform(move || test_universal_helper(i));
}
iothread_drain_all(&mut reader);
let mut uvars = EnvUniversal::new();
let mut callbacks = CallbackDataList::new();
uvars.initialize_at_path(&mut callbacks, UVARS_TEST_PATH.to_owned());
for i in 0..threads {
for j in 0..UVARS_PER_THREAD {
let key = sprintf!("key_%d_%d", i, j);
let expected_val = if j == 0 {
None
} else {
Some(EnvVar::new(
sprintf!("val_%d_%d", i, j),
EnvVarFlags::empty(),
))
};
let var = uvars.get(&key);
assert_eq!(var, expected_val);
}
}
std::fs::remove_dir_all("test/fish_uvars_test/").unwrap();
}
#[test]
#[serial]
fn test_universal_output() {
let _cleanup = test_init();
let flag_export = EnvVarFlags::EXPORT;
let flag_pathvar = EnvVarFlags::PATHVAR;
let mut vars = VarTable::new();
vars.insert(
L!("varA").to_owned(),
EnvVar::new_vec(
vec![L!("ValA1").to_owned(), L!("ValA2").to_owned()],
EnvVarFlags::empty(),
),
);
vars.insert(
L!("varB").to_owned(),
EnvVar::new_vec(vec![L!("ValB1").to_owned()], flag_export),
);
vars.insert(
L!("varC").to_owned(),
EnvVar::new_vec(vec![L!("ValC1").to_owned()], EnvVarFlags::empty()),
);
vars.insert(
L!("varD").to_owned(),
EnvVar::new_vec(vec![L!("ValD1").to_owned()], flag_export | flag_pathvar),
);
vars.insert(
L!("varE").to_owned(),
EnvVar::new_vec(
vec![L!("ValE1").to_owned(), L!("ValE2").to_owned()],
flag_pathvar,
),
);
vars.insert(
L!("varF").to_owned(),
EnvVar::new_vec(
vec![WString::from_chars([char_offset(ENCODE_DIRECT_BASE, 0xfc)])],
EnvVarFlags::empty(),
),
);
let text = EnvUniversal::serialize_with_vars(&vars);
let expected = concat!(
"# This file contains fish universal variable definitions.\n",
"# VERSION: 3.0\n",
"SETUVAR varA:ValA1\\x1eValA2\n",
"SETUVAR --export varB:ValB1\n",
"SETUVAR varC:ValC1\n",
"SETUVAR --export --path varD:ValD1\n",
"SETUVAR --path varE:ValE1\\x1eValE2\n",
"SETUVAR varF:\\xfc\n",
)
.as_bytes();
assert_eq!(text, expected);
}
#[test]
#[serial]
fn test_universal_parsing() {
let _cleanup = test_init();
let input = concat!(
"# This file contains fish universal variable definitions.\n",
"# VERSION: 3.0\n",
"SETUVAR varA:ValA1\\x1eValA2\n",
"SETUVAR --export varB:ValB1\n",
"SETUVAR --nonsenseflag varC:ValC1\n",
"SETUVAR --export --path varD:ValD1\n",
"SETUVAR --path --path varE:ValE1\\x1eValE2\n",
)
.as_bytes();
let flag_export = EnvVarFlags::EXPORT;
let flag_pathvar = EnvVarFlags::PATHVAR;
let mut vars = VarTable::new();
vars.insert(
L!("varA").to_owned(),
EnvVar::new_vec(
vec![L!("ValA1").to_owned(), L!("ValA2").to_owned()],
EnvVarFlags::empty(),
),
);
vars.insert(
L!("varB").to_owned(),
EnvVar::new_vec(vec![L!("ValB1").to_owned()], flag_export),
);
vars.insert(
L!("varC").to_owned(),
EnvVar::new_vec(vec![L!("ValC1").to_owned()], EnvVarFlags::empty()),
);
vars.insert(
L!("varD").to_owned(),
EnvVar::new_vec(vec![L!("ValD1").to_owned()], flag_export | flag_pathvar),
);
vars.insert(
L!("varE").to_owned(),
EnvVar::new_vec(
vec![L!("ValE1").to_owned(), L!("ValE2").to_owned()],
flag_pathvar,
),
);
let mut parsed_vars = VarTable::new();
EnvUniversal::populate_variables(input, &mut parsed_vars);
assert_eq!(vars, parsed_vars);
}
#[test]
#[serial]
fn test_universal_parsing_legacy() {
let _cleanup = test_init();
let input = concat!(
"# This file contains fish universal variable definitions.\n",
"SET varA:ValA1\\x1eValA2\n",
"SET_EXPORT varB:ValB1\n",
)
.as_bytes();
let mut vars = VarTable::new();
vars.insert(
L!("varA").to_owned(),
EnvVar::new_vec(
vec![L!("ValA1").to_owned(), L!("ValA2").to_owned()],
EnvVarFlags::empty(),
),
);
vars.insert(
L!("varB").to_owned(),
EnvVar::new(L!("ValB1").to_owned(), EnvVarFlags::EXPORT),
);
let mut parsed_vars = VarTable::new();
EnvUniversal::populate_variables(input, &mut parsed_vars);
assert_eq!(vars, parsed_vars);
}
#[test]
#[serial]
fn test_universal_callbacks() {
let _cleanup = test_init();
std::fs::create_dir_all("test/fish_uvars_test/").unwrap();
let mut callbacks = CallbackDataList::new();
let mut uvars1 = EnvUniversal::new();
let mut uvars2 = EnvUniversal::new();
uvars1.initialize_at_path(&mut callbacks, UVARS_TEST_PATH.to_owned());
uvars2.initialize_at_path(&mut callbacks, UVARS_TEST_PATH.to_owned());
let noflags = EnvVarFlags::empty();
// Put some variables into both.
uvars1.set(L!("alpha"), EnvVar::new(L!("1").to_owned(), noflags)); //
uvars1.set(L!("beta"), EnvVar::new(L!("1").to_owned(), noflags)); //
uvars1.set(L!("delta"), EnvVar::new(L!("1").to_owned(), noflags)); //
uvars1.set(L!("epsilon"), EnvVar::new(L!("1").to_owned(), noflags)); //
uvars1.set(L!("lambda"), EnvVar::new(L!("1").to_owned(), noflags)); //
uvars1.set(L!("kappa"), EnvVar::new(L!("1").to_owned(), noflags)); //
uvars1.set(L!("omicron"), EnvVar::new(L!("1").to_owned(), noflags)); //
uvars1.sync(&mut callbacks);
uvars2.sync(&mut callbacks);
// Change uvars1.
uvars1.set(L!("alpha"), EnvVar::new(L!("2").to_owned(), noflags)); // changes value
uvars1.set(
L!("beta"),
EnvVar::new(L!("1").to_owned(), EnvVarFlags::EXPORT),
); // changes export
uvars1.remove(L!("delta")); // erases value
uvars1.set(L!("epsilon"), EnvVar::new(L!("1").to_owned(), noflags)); // changes nothing
uvars1.sync(&mut callbacks);
// Change uvars2. It should treat its value as correct and ignore changes from uvars1.
uvars2.set(L!("lambda"), EnvVar::new(L!("1").to_owned(), noflags)); // same value
uvars2.set(L!("kappa"), EnvVar::new(L!("2").to_owned(), noflags)); // different value
// Now see what uvars2 sees.
callbacks.clear();
uvars2.sync(&mut callbacks);
// Sort them to get them in a predictable order.
callbacks.sort_by(|a, b| a.key.cmp(&b.key));
// Should see exactly three changes.
assert_eq!(callbacks.len(), 3);
assert_eq!(callbacks[0].key, L!("alpha"));
assert_eq!(callbacks[0].val.as_ref().unwrap().as_string(), L!("2"));
assert_eq!(callbacks[1].key, L!("beta"));
assert_eq!(callbacks[1].val.as_ref().unwrap().as_string(), L!("1"));
assert_eq!(callbacks[2].key, L!("delta"));
assert_eq!(callbacks[2].val, None);
std::fs::remove_dir_all("test/fish_uvars_test/").unwrap();
}
#[test]
#[serial]
fn test_universal_formats() {
let _cleanup = test_init();
macro_rules! validate {
( $version_line:literal, $expected_format:expr ) => {
assert_eq!(
EnvUniversal::format_for_contents($version_line),
$expected_format
);
};
}
validate!(b"# VERSION: 3.0", UvarFormat::fish_3_0);
validate!(b"# version: 3.0", UvarFormat::fish_2_x);
validate!(b"# blah blahVERSION: 3.0", UvarFormat::fish_2_x);
validate!(b"stuff\n# blah blahVERSION: 3.0", UvarFormat::fish_2_x);
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.0", UvarFormat::fish_3_0);
validate!(b"# blah\n#VERSION:3.1", UvarFormat::future);
}
#[test]
#[serial]
fn test_universal_ok_to_save() {
let _cleanup = 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";
std::fs::write(wcs2osstring(UVARS_TEST_PATH), contents).unwrap();
let before_id = file_id_for_path(UVARS_TEST_PATH);
assert_ne!(
before_id, INVALID_FILE_ID,
"UVARS_TEST_PATH should be readable"
);
let mut cbs = CallbackDataList::new();
let mut uvars = EnvUniversal::new();
uvars.initialize_at_path(&mut cbs, UVARS_TEST_PATH.to_owned());
assert!(!uvars.is_ok_to_save(), "Should not be OK to save");
uvars.sync(&mut cbs);
cbs.clear();
assert!(!uvars.is_ok_to_save(), "Should still not be OK to save");
uvars.set(
L!("SOMEVAR"),
EnvVar::new(L!("SOMEVALUE").to_owned(), EnvVarFlags::empty()),
);
// Ensure file is same.
let after_id = file_id_for_path(UVARS_TEST_PATH);
assert_eq!(
before_id, after_id,
"UVARS_TEST_PATH should not have changed",
);
std::fs::remove_dir_all("test/fish_uvars_test/").unwrap();
}