Add tests for completions (#204)
This commit is contained in:
parent
efe11ec924
commit
d33bfd111f
|
|
@ -20,7 +20,5 @@ jobs:
|
||||||
- uses: cachix/install-nix-action@v12
|
- uses: cachix/install-nix-action@v12
|
||||||
if: ${{ matrix.os != 'windows-latest' }}
|
if: ${{ matrix.os != 'windows-latest' }}
|
||||||
with:
|
with:
|
||||||
nix_path: nixpkgs=channel:nixos-stable
|
nix_path: nixpkgs=https://github.com/NixOS/nixpkgs/archive/20.09.tar.gz
|
||||||
- run: mkdir -p /tmp/home && make test
|
- run: make test
|
||||||
env:
|
|
||||||
HOME: /tmp/home
|
|
||||||
|
|
|
||||||
14
Makefile
14
Makefile
|
|
@ -11,7 +11,7 @@ endif
|
||||||
.PHONY: build clean install test uninstall
|
.PHONY: build clean install test uninstall
|
||||||
|
|
||||||
build:
|
build:
|
||||||
cargo build --release $(ci_color_always)
|
cargo build $(ci_color_always)
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
cargo clean $(ci_color_always)
|
cargo clean $(ci_color_always)
|
||||||
|
|
@ -22,16 +22,16 @@ install:
|
||||||
ifeq ($(NIX), true)
|
ifeq ($(NIX), true)
|
||||||
test:
|
test:
|
||||||
nix-shell --pure --run 'cargo fmt -- --check --files-with-diff $(ci_color_always)'
|
nix-shell --pure --run 'cargo fmt -- --check --files-with-diff $(ci_color_always)'
|
||||||
nix-shell --pure --run 'cargo check --all-features --release $(ci_color_always)'
|
nix-shell --pure --run 'cargo check --all-features $(ci_color_always)'
|
||||||
nix-shell --pure --run 'cargo clippy --all-features --release $(ci_color_always) -- --deny warnings --deny clippy::all'
|
nix-shell --pure --run 'cargo clippy --all-features $(ci_color_always) -- --deny warnings --deny clippy::all'
|
||||||
nix-shell --pure --run 'cargo test --all-features --no-fail-fast --release $(ci_color_always)'
|
nix-shell --pure --run 'cargo test --all-features --no-fail-fast $(ci_color_always)'
|
||||||
nix-shell --pure --run 'cargo audit --deny warnings $(ci_color_always) --ignore=RUSTSEC-2020-0095'
|
nix-shell --pure --run 'cargo audit --deny warnings $(ci_color_always) --ignore=RUSTSEC-2020-0095'
|
||||||
else
|
else
|
||||||
test:
|
test:
|
||||||
cargo fmt -- --check --files-with-diff $(ci_color_always)
|
cargo fmt -- --check --files-with-diff $(ci_color_always)
|
||||||
cargo check --all-features --release $(ci_color_always)
|
cargo check --all-features $(ci_color_always)
|
||||||
cargo clippy --all-features --release $(ci_color_always) -- --deny warnings --deny clippy::all
|
cargo clippy --all-features $(ci_color_always) -- --deny warnings --deny clippy::all
|
||||||
cargo test --no-fail-fast --release $(ci_color_always)
|
cargo test --no-fail-fast $(ci_color_always)
|
||||||
cargo audit --deny warnings $(ci_color_always) --ignore=RUSTSEC-2020-0095
|
cargo audit --deny warnings $(ci_color_always) --ignore=RUSTSEC-2020-0095
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
|
|
||||||
5
build.rs
5
build.rs
|
|
@ -19,9 +19,8 @@ fn crate_version() -> String {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate_completions() {
|
fn generate_completions() {
|
||||||
mod app {
|
#[path = "src/app/_app.rs"]
|
||||||
include!("src/app.rs");
|
mod app;
|
||||||
}
|
|
||||||
|
|
||||||
use app::App;
|
use app::App;
|
||||||
use clap::IntoApp;
|
use clap::IntoApp;
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,6 @@ impl Run for Init {
|
||||||
InitShell::Zsh => shell::Zsh(opts).render(),
|
InitShell::Zsh => shell::Zsh(opts).render(),
|
||||||
}
|
}
|
||||||
.context("could not render template")?;
|
.context("could not render template")?;
|
||||||
writeln!(io::stdout(), "{}", source).wrap_write("stdout")
|
writeln!(io::stdout(), "{}", source).pipe_exit("stdout")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
|
mod _app;
|
||||||
mod add;
|
mod add;
|
||||||
mod import;
|
mod import;
|
||||||
mod init;
|
mod init;
|
||||||
mod query;
|
mod query;
|
||||||
mod remove;
|
mod remove;
|
||||||
|
|
||||||
use crate::app::App;
|
pub use crate::app::_app::*;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
|
|
||||||
|
|
@ -27,7 +27,7 @@ impl Run for Query {
|
||||||
if self.interactive {
|
if self.interactive {
|
||||||
let mut fzf = Fzf::new(false)?;
|
let mut fzf = Fzf::new(false)?;
|
||||||
for dir in matches {
|
for dir in matches {
|
||||||
writeln!(fzf.stdin(), "{}", dir.display_score(now)).wrap_write("fzf")?;
|
writeln!(fzf.stdin(), "{}", dir.display_score(now)).pipe_exit("fzf")?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let selection = fzf.wait_select()?;
|
let selection = fzf.wait_select()?;
|
||||||
|
|
@ -53,9 +53,9 @@ impl Run for Query {
|
||||||
} else {
|
} else {
|
||||||
writeln!(handle, "{}", dir.display())
|
writeln!(handle, "{}", dir.display())
|
||||||
}
|
}
|
||||||
.wrap_write("stdout")?;
|
.pipe_exit("stdout")?;
|
||||||
}
|
}
|
||||||
handle.flush().wrap_write("stdout")?;
|
handle.flush().pipe_exit("stdout")?;
|
||||||
} else {
|
} else {
|
||||||
let dir = matches.next().context("no match found")?;
|
let dir = matches.next().context("no match found")?;
|
||||||
if self.score {
|
if self.score {
|
||||||
|
|
@ -63,7 +63,7 @@ impl Run for Query {
|
||||||
} else {
|
} else {
|
||||||
writeln!(io::stdout(), "{}", dir.display())
|
writeln!(io::stdout(), "{}", dir.display())
|
||||||
}
|
}
|
||||||
.wrap_write("stdout")?;
|
.pipe_exit("stdout")?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -25,7 +25,7 @@ impl Run for Remove {
|
||||||
|
|
||||||
let mut fzf = Fzf::new(true)?;
|
let mut fzf = Fzf::new(true)?;
|
||||||
for dir in db.iter_matches(&query, now, resolve_symlinks) {
|
for dir in db.iter_matches(&query, now, resolve_symlinks) {
|
||||||
writeln!(fzf.stdin(), "{}", dir.display_score(now)).wrap_write("fzf")?;
|
writeln!(fzf.stdin(), "{}", dir.display_score(now)).pipe_exit("fzf")?;
|
||||||
}
|
}
|
||||||
|
|
||||||
selection = fzf.wait_select()?;
|
selection = fzf.wait_select()?;
|
||||||
|
|
@ -16,11 +16,11 @@ impl Display for SilentExit {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait WriteErrorHandler {
|
pub trait WriteErrorHandler {
|
||||||
fn wrap_write(self, device: &str) -> Result<()>;
|
fn pipe_exit(self, device: &str) -> Result<()>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WriteErrorHandler for io::Result<()> {
|
impl WriteErrorHandler for io::Result<()> {
|
||||||
fn wrap_write(self, device: &str) -> Result<()> {
|
fn pipe_exit(self, device: &str) -> Result<()> {
|
||||||
match self {
|
match self {
|
||||||
Err(e) if e.kind() == io::ErrorKind::BrokenPipe => bail!(SilentExit { code: 0 }),
|
Err(e) if e.kind() == io::ErrorKind::BrokenPipe => bail!(SilentExit { code: 0 }),
|
||||||
result => result.with_context(|| format!("could not write to {}", device)),
|
result => result.with_context(|| format!("could not write to {}", device)),
|
||||||
|
|
|
||||||
13
src/fzf.rs
13
src/fzf.rs
|
|
@ -3,6 +3,7 @@ use crate::error::SilentExit;
|
||||||
|
|
||||||
use anyhow::{bail, Context, Result};
|
use anyhow::{bail, Context, Result};
|
||||||
|
|
||||||
|
use std::io;
|
||||||
use std::process::{Child, ChildStdin, Command, Stdio};
|
use std::process::{Child, ChildStdin, Command, Stdio};
|
||||||
|
|
||||||
pub struct Fzf {
|
pub struct Fzf {
|
||||||
|
|
@ -23,9 +24,15 @@ impl Fzf {
|
||||||
command.env("FZF_DEFAULT_OPTS", fzf_opts);
|
command.env("FZF_DEFAULT_OPTS", fzf_opts);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Fzf {
|
let child = match command.spawn() {
|
||||||
child: command.spawn().context("could not launch fzf")?,
|
Ok(child) => child,
|
||||||
})
|
Err(e) if e.kind() == io::ErrorKind::NotFound => {
|
||||||
|
bail!("could not find fzf, is it installed?")
|
||||||
|
}
|
||||||
|
Err(e) => Err(e).context("could not launch fzf")?,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Fzf { child })
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn stdin(&mut self) -> &mut ChildStdin {
|
pub fn stdin(&mut self) -> &mut ChildStdin {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
mod app;
|
mod app;
|
||||||
mod cmd;
|
|
||||||
mod config;
|
mod config;
|
||||||
mod db;
|
mod db;
|
||||||
mod error;
|
mod error;
|
||||||
|
|
@ -7,8 +6,7 @@ mod fzf;
|
||||||
mod shell;
|
mod shell;
|
||||||
mod util;
|
mod util;
|
||||||
|
|
||||||
use crate::app::App;
|
use crate::app::{App, Run};
|
||||||
use crate::cmd::Run;
|
|
||||||
use crate::error::SilentExit;
|
use crate::error::SilentExit;
|
||||||
|
|
||||||
use clap::Clap;
|
use clap::Clap;
|
||||||
|
|
|
||||||
29
src/shell.rs
29
src/shell.rs
|
|
@ -60,13 +60,12 @@ mod tests {
|
||||||
for &resolve_symlinks in BOOLS {
|
for &resolve_symlinks in BOOLS {
|
||||||
for &hook in HOOKS {
|
for &hook in HOOKS {
|
||||||
for &cmd in CMDS {
|
for &cmd in CMDS {
|
||||||
let opt = Opts {
|
opts.push(Opts {
|
||||||
cmd,
|
cmd,
|
||||||
hook,
|
hook,
|
||||||
echo,
|
echo,
|
||||||
resolve_symlinks,
|
resolve_symlinks,
|
||||||
};
|
});
|
||||||
opts.push(opt);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -84,7 +83,7 @@ mod tests {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! generate_tests {
|
macro_rules! make_tests {
|
||||||
($N:literal) => {
|
($N:literal) => {
|
||||||
seq!(i in 0..$N {
|
seq!(i in 0..$N {
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -92,7 +91,7 @@ mod tests {
|
||||||
let opts = dbg!(&opts()[i]);
|
let opts = dbg!(&opts()[i]);
|
||||||
let source = Bash(opts).render().unwrap();
|
let source = Bash(opts).render().unwrap();
|
||||||
Command::new("bash")
|
Command::new("bash")
|
||||||
.args(&["-c", &source, "--noediting", "--noprofile", "--norc"])
|
.args(&["--noprofile", "--norc", "-c", &source])
|
||||||
.assert()
|
.assert()
|
||||||
.success()
|
.success()
|
||||||
.stdout("")
|
.stdout("")
|
||||||
|
|
@ -213,7 +212,7 @@ mod tests {
|
||||||
let opts = dbg!(&opts()[i]);
|
let opts = dbg!(&opts()[i]);
|
||||||
let source = Posix(opts).render().unwrap();
|
let source = Posix(opts).render().unwrap();
|
||||||
let assert = Command::new("bash")
|
let assert = Command::new("bash")
|
||||||
.args(&["--posix", "-c", &source, "--noediting", "--noprofile", "--norc"])
|
.args(&["--posix", "--noprofile", "--norc", "-c", &source])
|
||||||
.assert()
|
.assert()
|
||||||
.success()
|
.success()
|
||||||
.stderr("");
|
.stderr("");
|
||||||
|
|
@ -256,7 +255,6 @@ mod tests {
|
||||||
let opts = dbg!(&opts()[i]);
|
let opts = dbg!(&opts()[i]);
|
||||||
let mut source = Posix(opts).render().unwrap();
|
let mut source = Posix(opts).render().unwrap();
|
||||||
source.push('\n');
|
source.push('\n');
|
||||||
|
|
||||||
Command::new("shfmt")
|
Command::new("shfmt")
|
||||||
.args(&["-d", "-s", "-ln", "posix", "-i", "4", "-ci", "-"])
|
.args(&["-d", "-s", "-ln", "posix", "-i", "4", "-ci", "-"])
|
||||||
.write_stdin(source)
|
.write_stdin(source)
|
||||||
|
|
@ -271,7 +269,7 @@ mod tests {
|
||||||
let opts = dbg!(&opts()[i]);
|
let opts = dbg!(&opts()[i]);
|
||||||
let source = Powershell(opts).render().unwrap();
|
let source = Powershell(opts).render().unwrap();
|
||||||
Command::new("pwsh")
|
Command::new("pwsh")
|
||||||
.args(&["-Command", &source, "-NoLogo", "-NonInteractive", "-NoProfile"])
|
.args(&["-NoLogo", "-NonInteractive", "-NoProfile", "-Command", &source])
|
||||||
.assert()
|
.assert()
|
||||||
.success()
|
.success()
|
||||||
.stdout("")
|
.stdout("")
|
||||||
|
|
@ -316,14 +314,19 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
// Xonsh complains about type-hinting here, although it works fine in practice.
|
|
||||||
// <https://github.com/xonsh/xonsh/issues/3959>
|
|
||||||
#[ignore]
|
|
||||||
fn xonsh_xonsh_#i() {
|
fn xonsh_xonsh_#i() {
|
||||||
let opts = dbg!(&opts()[i]);
|
let opts = dbg!(&opts()[i]);
|
||||||
let source = Xonsh(opts).render().unwrap();
|
let source = Xonsh(opts).render().unwrap();
|
||||||
|
|
||||||
|
// We can't pass the source directly to `xonsh -c` due to
|
||||||
|
// a bug: <https://github.com/xonsh/xonsh/issues/3959>
|
||||||
Command::new("xonsh")
|
Command::new("xonsh")
|
||||||
.args(&["-c", &source, "--no-rc"])
|
.args(&[
|
||||||
|
"-c",
|
||||||
|
"import sys; execx(sys.stdin.read(), 'exec', __xonsh__.ctx, filename='zoxide')",
|
||||||
|
"--no-rc"
|
||||||
|
])
|
||||||
|
.write_stdin(source.as_bytes())
|
||||||
.assert()
|
.assert()
|
||||||
.success()
|
.success()
|
||||||
.stdout("")
|
.stdout("")
|
||||||
|
|
@ -360,5 +363,5 @@ mod tests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
with_opts_size!(generate_tests);
|
with_opts_size!(make_tests);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,71 @@
|
||||||
|
//! Syntax checking for auto-generated shell completions.
|
||||||
|
|
||||||
|
#![cfg(feature = "shell_tests")]
|
||||||
|
use assert_cmd::Command;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn completions_bash() {
|
||||||
|
let source = include_str!("../contrib/completions/zoxide.bash");
|
||||||
|
Command::new("bash")
|
||||||
|
.args(&["--noprofile", "--norc", "-c", source])
|
||||||
|
.assert()
|
||||||
|
.success()
|
||||||
|
.stdout("")
|
||||||
|
.stderr("");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Elvish: the completions file uses editor commands to add completions to the
|
||||||
|
// shell. However, Elvish does not support running editor commands from a
|
||||||
|
// script, so we can't create a test for this.
|
||||||
|
// <https://github.com/elves/elvish/issues/1299>
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn completions_fish() {
|
||||||
|
let source = include_str!("../contrib/completions/zoxide.fish");
|
||||||
|
let tempdir = tempfile::tempdir().unwrap();
|
||||||
|
let tempdir = tempdir.path().to_str().unwrap();
|
||||||
|
|
||||||
|
Command::new("fish")
|
||||||
|
.env("HOME", tempdir)
|
||||||
|
.args(&["--command", source, "--private"])
|
||||||
|
.assert()
|
||||||
|
.success()
|
||||||
|
.stdout("")
|
||||||
|
.stderr("");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn completions_powershell() {
|
||||||
|
let source = include_str!("../contrib/completions/_zoxide.ps1");
|
||||||
|
Command::new("pwsh")
|
||||||
|
.args(&[
|
||||||
|
"-NoLogo",
|
||||||
|
"-NonInteractive",
|
||||||
|
"-NoProfile",
|
||||||
|
"-Command",
|
||||||
|
source,
|
||||||
|
])
|
||||||
|
.assert()
|
||||||
|
.success()
|
||||||
|
.stdout("")
|
||||||
|
.stderr("");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn completions_zsh() {
|
||||||
|
let source = r#"
|
||||||
|
set -eu
|
||||||
|
completions='./contrib/completions'
|
||||||
|
test -d "$completions"
|
||||||
|
fpath=("$completions" $fpath)
|
||||||
|
autoload -Uz compinit
|
||||||
|
compinit -u
|
||||||
|
"#;
|
||||||
|
|
||||||
|
Command::new("zsh")
|
||||||
|
.args(&["-c", source, "--no-rcs"])
|
||||||
|
.assert()
|
||||||
|
.success()
|
||||||
|
.stdout("")
|
||||||
|
.stderr("");
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue