Skip to content

Commit

Permalink
Add test for bash completion functionality
Browse files Browse the repository at this point in the history
This change adds a test for the previously introduced bash completion
functionality. To test the generated completion script, we spin up a
bash instance, source the script, and then perform a completion as the
shell would do it. It seems impossible to convince compgen to do the
heavy lifting for us and so we invoke the completion function with the
expected environment variables present.
  • Loading branch information
d-e-s-o committed Apr 2, 2020
1 parent 6464181 commit d65bd4e
Showing 1 changed file with 107 additions and 2 deletions.
109 changes: 107 additions & 2 deletions var/shell-complete.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,113 @@ pub struct Args {
pub command: String,
}

fn generate_bash<W>(command: &str, output: &mut W)
where
W: io::Write,
{
let mut app = nitrocli::Args::clap();
app.gen_completions_to(command, clap::Shell::Bash, output);
}

fn main() {
let args = Args::from_args();
let mut app = nitrocli::Args::clap();
app.gen_completions_to(&args.command, clap::Shell::Bash, &mut io::stdout());
generate_bash(&args.command, &mut io::stdout())
}

#[cfg(test)]
mod tests {
use super::*;

use std::io;
use std::ops::Add as _;
use std::process;

/// Separate the given words by newlines.
fn lines<'w, W>(mut words: W) -> String
where
W: Iterator<Item = &'w str>,
{
let first = words.next().unwrap_or("");
words
.fold(first.to_string(), |words, word| {
format!("{}\n{}", words, word)
})
.add("\n")
}

/// Check if `bash` is present on the system.
fn has_bash() -> bool {
match process::Command::new("bash").arg("-c").arg("exit").spawn() {
// We deliberately only indicate that bash does not exist if we
// get a file-not-found error. We don't expect any other error but
// should there be one things will blow up later.
Err(ref err) if err.kind() == io::ErrorKind::NotFound => false,
_ => true,
}
}

/// Perform a bash completion of the given arguments to nitrocli.
fn complete_bash<'w, W>(words: W) -> Vec<u8>
where
W: ExactSizeIterator<Item = &'w str>,
{
let mut buffer = Vec::new();
generate_bash("nitrocli", &mut buffer);

let script = String::from_utf8(buffer).unwrap();
let command = format!(
"
set -e;
eval '{script}';
export COMP_WORDS=({words});
export COMP_CWORD={index};
_nitrocli;
echo -n ${{COMPREPLY}}
",
index = words.len(),
words = lines(Some("nitrocli").into_iter().chain(words)),
script = script
);

let output = process::Command::new("bash")
.arg("-c")
.arg(command)
.output()
.unwrap();

output.stdout
}

#[test]
fn array_lines() {
assert_eq!(&lines(vec![].into_iter()), "\n");
assert_eq!(&lines(vec!["first"].into_iter()), "first\n");
assert_eq!(
&lines(vec!["first", "second"].into_iter()),
"first\nsecond\n"
);
assert_eq!(
&lines(vec!["first", "second", "third"].into_iter()),
"first\nsecond\nthird\n"
);
}

#[test]
fn complete_all_the_things() {
if !has_bash() {
return;
}

assert_eq!(complete_bash(vec!["stat"].into_iter()), b"status");
assert_eq!(
complete_bash(vec!["status", "--ver"].into_iter()),
b"--version"
);
assert_eq!(complete_bash(vec!["--version"].into_iter()), b"--version");
assert_eq!(complete_bash(vec!["--model", "s"].into_iter()), b"storage");
assert_eq!(
complete_bash(vec!["otp", "get", "--model", "p"].into_iter()),
b"pro"
);
}
}

0 comments on commit d65bd4e

Please sign in to comment.