test(e2e): add e2e tests for secondary cli commands (version, init, list-channels, ...)

This commit is contained in:
alexandre pasmantier 2025-06-15 18:34:55 +02:00 committed by Alex Pasmantier
parent bc8d636005
commit 8d822cd2fc
7 changed files with 181 additions and 29 deletions

63
Cargo.lock generated
View File

@ -159,6 +159,12 @@ dependencies = [
"console",
]
[[package]]
name = "bitflags"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
[[package]]
name = "bitflags"
version = "2.9.0"
@ -341,6 +347,12 @@ dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "comma"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55b672471b4e9f9e95499ea597ff64941a309b2cdbffcc46f2cc5e2d971fd335"
[[package]]
name = "compact_str"
version = "0.8.1"
@ -461,7 +473,7 @@ version = "0.28.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6"
dependencies = [
"bitflags",
"bitflags 2.9.0",
"crossterm_winapi",
"filedescriptor",
"mio",
@ -963,7 +975,7 @@ version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d"
dependencies = [
"bitflags",
"bitflags 2.9.0",
"libc",
]
@ -1019,6 +1031,15 @@ version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "memoffset"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
dependencies = [
"autocfg",
]
[[package]]
name = "minimal-lexical"
version = "0.2.1"
@ -1046,6 +1067,20 @@ dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "nix"
version = "0.25.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f346ff70e7dbfd675fe90590b92d59ef2de15a8779ae305ebcbfd3f0caf59be4"
dependencies = [
"autocfg",
"bitflags 1.2.1",
"cfg-if",
"libc",
"memoffset",
"pin-utils",
]
[[package]]
name = "nom"
version = "7.1.3"
@ -1290,7 +1325,7 @@ version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eabd94c2f37801c20583fc49dd5cd6b0ba68c716787c2dd6ed18571e1e63117b"
dependencies = [
"bitflags",
"bitflags 2.9.0",
"cassowary",
"compact_str",
"crossterm",
@ -1332,7 +1367,7 @@ version = "0.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2f103c6d277498fbceb16e84d317e2a400f160f46904d5f5410848c829511a3"
dependencies = [
"bitflags",
"bitflags 2.9.0",
]
[[package]]
@ -1396,6 +1431,19 @@ version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
[[package]]
name = "rexpect"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01ff60778f96fb5a48adbe421d21bf6578ed58c0872d712e7e08593c195adff8"
dependencies = [
"comma",
"nix",
"regex",
"tempfile",
"thiserror 1.0.69",
]
[[package]]
name = "ring"
version = "0.17.14"
@ -1434,7 +1482,7 @@ version = "0.38.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
dependencies = [
"bitflags",
"bitflags 2.9.0",
"errno",
"libc",
"linux-raw-sys 0.4.15",
@ -1447,7 +1495,7 @@ version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d97817398dd4bb2e6da002002db259209759911da105da92bec29ccb12cf58bf"
dependencies = [
"bitflags",
"bitflags 2.9.0",
"errno",
"libc",
"linux-raw-sys 0.9.4",
@ -1742,6 +1790,7 @@ dependencies = [
"nucleo",
"parking_lot",
"ratatui",
"rexpect",
"rustc-hash",
"serde",
"serde_json",
@ -2378,7 +2427,7 @@ version = "0.39.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
dependencies = [
"bitflags",
"bitflags 2.9.0",
]
[[package]]

View File

@ -75,6 +75,7 @@ clipboard-win = "5.4.0"
[dev-dependencies]
criterion = { version = "0.5", features = ["async_tokio"] }
tempfile = "3.16.0"
rexpect = "0.5"
[build-dependencies]

View File

@ -10,7 +10,6 @@ use walkdir::WalkDir;
use crate::{
channels::prototypes::ChannelPrototype, cli::unknown_channel_exit,
config::get_config_dir,
};
/// A neat `HashMap` of channel prototypes indexed by their name.
@ -129,16 +128,13 @@ where
/// ├── channel_2.toml
/// └── ...
/// ```
pub fn load_cable<P>(cable_dir: Option<P>) -> Option<Cable>
pub fn load_cable<P>(cable_dir: P) -> Option<Cable>
where
P: AsRef<Path>,
{
let cable_dir = match cable_dir {
Some(dir) => PathBuf::from(dir.as_ref()),
None => get_config_dir().join(CABLE_DIR_NAME),
};
let cable_dir = cable_dir.as_ref();
debug!("Using cable directory: {}", cable_dir.to_string_lossy());
let cable_files = get_cable_files(&cable_dir);
let cable_files = get_cable_files(cable_dir);
debug!("Found cable channel files: {:?}", cable_files);
if cable_files.is_empty() {

View File

@ -156,9 +156,12 @@ fn parse_keybindings_literal(
toml::from_str(&toml_definition).map_err(|e| anyhow!(e))
}
pub fn list_channels(cable_dir: Option<&Path>) {
let channels = cable::load_cable::<&Path>(cable_dir)
.expect("Failed to load cable channels");
pub fn list_channels<P>(cable_dir: P)
where
P: AsRef<Path>,
{
let channels =
cable::load_cable(cable_dir).expect("Failed to load cable channels");
for c in channels.keys() {
println!("\t{c}");
}

View File

@ -35,6 +35,8 @@ pub struct AppConfig {
pub data_dir: PathBuf,
#[serde(default = "get_config_dir")]
pub config_dir: PathBuf,
#[serde(default = "default_cable_dir")]
pub cable_dir: PathBuf,
#[serde(default = "default_frame_rate")]
pub frame_rate: f64,
#[serde(default = "default_tick_rate")]
@ -290,6 +292,10 @@ pub fn get_config_dir() -> PathBuf {
}
}
fn default_cable_dir() -> PathBuf {
get_config_dir().join(CABLE_DIR_NAME)
}
fn project_directory() -> Option<ProjectDirs> {
ProjectDirs::from("com", "", env!("CARGO_PKG_NAME"))
}

View File

@ -47,22 +47,23 @@ async fn main() -> Result<()> {
let mut config =
Config::new(&ConfigEnv::init()?, args.config_file.as_deref())?;
// override configuration values with provided CLI arguments
debug!("Applying CLI overrides...");
apply_cli_overrides(&args, &mut config);
// handle subcommands
debug!("Handling subcommands...");
if let Some(subcommand) = &args.command {
handle_subcommand(subcommand, &config, &args)?;
handle_subcommand(subcommand, &config)?;
}
debug!("Loading cable channels...");
let cable = load_cable(args.cable_dir.as_ref()).unwrap_or_else(|| exit(1));
let cable =
load_cable(&config.application.cable_dir).unwrap_or_else(|| exit(1));
// optionally change the working directory
args.working_directory.as_ref().map(set_current_dir);
// optionally override configuration values with CLI arguments
debug!("Applying CLI overrides...");
apply_cli_overrides(&args, &mut config);
// determine the channel to use based on the CLI arguments and configuration
debug!("Determining channel...");
let channel_prototype =
@ -106,6 +107,9 @@ async fn main() -> Result<()> {
///
/// This function mutates the configuration in place.
fn apply_cli_overrides(args: &PostProcessedCli, config: &mut Config) {
if let Some(cable_dir) = &args.cable_dir {
config.application.cable_dir.clone_from(cable_dir);
}
if let Some(tick_rate) = args.tick_rate {
config.application.tick_rate = tick_rate;
}
@ -140,14 +144,10 @@ pub fn set_current_dir(path: &String) -> Result<()> {
Ok(())
}
pub fn handle_subcommand(
command: &Command,
config: &Config,
args: &PostProcessedCli,
) -> Result<()> {
pub fn handle_subcommand(command: &Command, config: &Config) -> Result<()> {
match command {
Command::ListChannels => {
list_channels(args.cable_dir.as_deref());
list_channels(&config.application.cable_dir);
exit(0);
}
Command::InitShell { shell } => {

97
tests/e2e.rs Normal file
View File

@ -0,0 +1,97 @@
use std::path::Path;
use rexpect::error::Error;
use rexpect::process::wait::WaitStatus;
use rexpect::spawn;
use television::channels::prototypes::ChannelPrototype;
use tempfile::TempDir;
#[allow(dead_code)]
fn setup_config(content: &str) -> TempDir {
let temp_dir = tempfile::tempdir().unwrap();
let config_path = temp_dir.path().join("config.toml");
std::fs::write(&config_path, content).unwrap();
temp_dir
}
fn setup_cable_channels<D>(channels: Vec<&str>, cable_dir: D)
where
D: AsRef<Path>,
{
let cable_dir = cable_dir.as_ref();
std::fs::create_dir_all(cable_dir).unwrap();
for channel in channels {
let name = toml::from_str::<ChannelPrototype>(channel)
.unwrap()
.metadata
.name;
let channel_path = cable_dir.join(format!("{}.toml", name));
std::fs::write(&channel_path, channel).unwrap();
}
}
#[test]
fn tv_version() -> Result<(), Error> {
let mut p = spawn("./target/debug/tv --version", Some(500))?;
p.exp_regex("television [0-9]+\\.[0-9]+\\.[0-9]+")?;
Ok(())
}
#[test]
fn tv_help() -> Result<(), Error> {
let mut p = spawn("./target/debug/tv --help", Some(500))?;
p.exp_regex("A cross-platform")?;
Ok(())
}
const BASIC_FILE_CHANNEL: &str = r#"
[metadata]
name = "files"
[source]
command = "fd -t f"
"#;
const BASIC_DIR_CHANNEL: &str = r#"
[metadata]
name = "dirs"
[source]
command = "fd -t d"
"#;
#[test]
fn tv_list_channels() -> Result<(), Error> {
let temp_dir = TempDir::new().unwrap();
setup_cable_channels(
vec![BASIC_FILE_CHANNEL, BASIC_DIR_CHANNEL],
&temp_dir,
);
let mut p = spawn(
&format!(
"./target/debug/tv --cable-dir {} list-channels ",
temp_dir.path().display()
),
Some(500),
)?;
p.exp_regex("files")?;
p.exp_regex("dirs")?;
Ok(())
}
#[test]
fn tv_init_zsh() -> Result<(), Error> {
let p = spawn("./target/debug/tv init zsh", Some(500))?;
// check that the process exits successfully
if let Ok(w) = p.process.wait() {
assert_eq!(w, WaitStatus::Exited(w.pid().unwrap(), 0));
} else {
panic!("Failed to wait for process");
}
Ok(())
}