This commit is contained in:
Félix Saparelli 2022-01-22 17:40:09 +13:00
parent 089122821d
commit 093b443dbc
9 changed files with 542 additions and 532 deletions

View File

@ -1,12 +1,21 @@
root = true
[*]
indent_style = space
indent_style = tab
indent_size = 4
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.yml]
[tests/snapshots/*]
indent_style = space
trim_trailing_whitespace = false
[*.{md,ronn}]
indent_style = space
indent_size = 4
[*.{cff,yml}]
indent_size = 2
indent_style = space

1
.rustfmt.toml Normal file
View File

@ -0,0 +1 @@
hard_tabs = true

View File

@ -2,11 +2,11 @@ use clap::{App, AppSettings, Arg, ArgMatches, ErrorKind, SubCommand};
use std::{env, process};
pub fn parse() -> ArgMatches<'static> {
let footnote = "You can use the `-- command` style instead, note you'll need to use full commands, it won't prefix `cargo` for you.\n\nBy default, your entire project is watched, except for the target/ and .git/ folders, and your .ignore and .gitignore files are used to filter paths.".to_owned();
let footnote = "You can use the `-- command` style instead, note you'll need to use full commands, it won't prefix `cargo` for you.\n\nBy default, your entire project is watched, except for the target/ and .git/ folders, and your .ignore and .gitignore files are used to filter paths.".to_owned();
#[cfg(windows)] let footnote = format!("{}\n\nOn Windows, patterns given to -i have forward slashes (/) automatically converted to backward ones (\\) to ease command portability.", footnote);
#[cfg(windows)] let footnote = format!("{}\n\nOn Windows, patterns given to -i have forward slashes (/) automatically converted to backward ones (\\) to ease command portability.", footnote);
let mut app = App::new(env!("CARGO_PKG_NAME"))
let mut app = App::new(env!("CARGO_PKG_NAME"))
.bin_name("cargo")
.version(env!("CARGO_PKG_VERSION"))
.help_message("")
@ -207,32 +207,32 @@ pub fn parse() -> ArgMatches<'static> {
.after_help(footnote.as_str()),
);
// Allow invocation of cargo-watch with both `cargo-watch watch ARGS`
// (as invoked by cargo) and `cargo-watch ARGS`.
let mut args: Vec<String> = env::args().collect();
args.insert(1, "watch".into());
// Allow invocation of cargo-watch with both `cargo-watch watch ARGS`
// (as invoked by cargo) and `cargo-watch ARGS`.
let mut args: Vec<String> = env::args().collect();
args.insert(1, "watch".into());
let matches = match app.get_matches_from_safe_borrow(args) {
Ok(matches) => matches,
Err(err) => {
match err.kind {
ErrorKind::HelpDisplayed => {
println!("{}", err);
process::exit(0);
}
let matches = match app.get_matches_from_safe_borrow(args) {
Ok(matches) => matches,
Err(err) => {
match err.kind {
ErrorKind::HelpDisplayed => {
println!("{}", err);
process::exit(0);
}
ErrorKind::VersionDisplayed => {
// Unlike HelpDisplayed, VersionDisplayed emits the output
// by itself (clap-rs/clap#1390). It also does so without a
// trailing newline, so we print one ourselves.
println!();
process::exit(0);
}
ErrorKind::VersionDisplayed => {
// Unlike HelpDisplayed, VersionDisplayed emits the output
// by itself (clap-rs/clap#1390). It also does so without a
// trailing newline, so we print one ourselves.
println!();
process::exit(0);
}
_ => app.get_matches(),
}
}
};
_ => app.get_matches(),
}
}
};
matches.subcommand.unwrap().matches
matches.subcommand.unwrap().matches
}

View File

@ -8,43 +8,43 @@ mod root;
mod watch;
fn main() -> Result<()> {
let matches = args::parse();
let matches = args::parse();
let debug = matches.is_present("log:debug");
let info = matches.is_present("log:info");
let quiet = matches.is_present("log:quiet");
let testing = matches.is_present("once");
let debug = matches.is_present("log:debug");
let info = matches.is_present("log:info");
let quiet = matches.is_present("log:quiet");
let testing = matches.is_present("once");
stderrlog::new()
.quiet(quiet)
.show_module_names(debug)
.verbosity(if debug {
3
} else if info {
2
} else {
1
})
.timestamp(if testing {
Timestamp::Off
} else {
Timestamp::Millisecond
})
.init()
.unwrap();
stderrlog::new()
.quiet(quiet)
.show_module_names(debug)
.verbosity(if debug {
3
} else if info {
2
} else {
1
})
.timestamp(if testing {
Timestamp::Off
} else {
Timestamp::Millisecond
})
.init()
.unwrap();
root::change_dir(
matches
.value_of("workdir")
.map(Utf8PathBuf::from)
.unwrap_or_else(root::project_root),
);
root::change_dir(
matches
.value_of("workdir")
.map(Utf8PathBuf::from)
.unwrap_or_else(root::project_root),
);
if let Some(b) = matches.value_of("rust-backtrace") {
std::env::set_var("RUST_BACKTRACE", b);
}
if let Some(b) = matches.value_of("rust-backtrace") {
std::env::set_var("RUST_BACKTRACE", b);
}
let opts = options::get_options(&matches);
let handler = watch::CwHandler::new(opts, quiet, matches.is_present("notif"))?;
watch(&handler)
let opts = options::get_options(&matches);
let handler = watch::CwHandler::new(opts, quiet, matches.is_present("notif"))?;
watch(&handler)
}

View File

@ -3,260 +3,260 @@ use std::{env, path::MAIN_SEPARATOR, time::Duration};
use clap::{value_t, values_t, ArgMatches};
use log::{debug, warn};
use watchexec::{
config::{Config, ConfigBuilder},
run::OnBusyUpdate,
Shell,
config::{Config, ConfigBuilder},
run::OnBusyUpdate,
Shell,
};
pub fn set_commands(builder: &mut ConfigBuilder, matches: &ArgMatches) {
let mut commands: Vec<String> = Vec::new();
let mut commands: Vec<String> = Vec::new();
// --features are injected just after applicable cargo subcommands
// and before the remaining arguments
let features = value_t!(matches, "features", String).ok();
// --features are injected just after applicable cargo subcommands
// and before the remaining arguments
let features = value_t!(matches, "features", String).ok();
if matches.is_present("cmd:trail") {
debug!("trailing command is present, ignore all other command options");
commands = vec![values_t!(matches, "cmd:trail", String)
.unwrap_or_else(|e| e.exit())
.into_iter()
.map(|arg| shell_escape::escape(arg.into()))
.collect::<Vec<_>>()
.join(" ")];
} else {
let command_order = env::args().filter_map(|arg| match arg.as_str() {
"-x" | "--exec" => Some("cargo"),
"-s" | "--shell" => Some("shell"),
_ => None,
});
if matches.is_present("cmd:trail") {
debug!("trailing command is present, ignore all other command options");
commands = vec![values_t!(matches, "cmd:trail", String)
.unwrap_or_else(|e| e.exit())
.into_iter()
.map(|arg| shell_escape::escape(arg.into()))
.collect::<Vec<_>>()
.join(" ")];
} else {
let command_order = env::args().filter_map(|arg| match arg.as_str() {
"-x" | "--exec" => Some("cargo"),
"-s" | "--shell" => Some("shell"),
_ => None,
});
let mut cargos = if matches.is_present("cmd:cargo") {
values_t!(matches, "cmd:cargo", String).unwrap_or_else(|e| e.exit())
} else {
Vec::new()
}
.into_iter();
let mut shells = if matches.is_present("cmd:shell") {
values_t!(matches, "cmd:shell", String).unwrap_or_else(|e| e.exit())
} else {
Vec::new()
}
.into_iter();
let mut cargos = if matches.is_present("cmd:cargo") {
values_t!(matches, "cmd:cargo", String).unwrap_or_else(|e| e.exit())
} else {
Vec::new()
}
.into_iter();
let mut shells = if matches.is_present("cmd:shell") {
values_t!(matches, "cmd:shell", String).unwrap_or_else(|e| e.exit())
} else {
Vec::new()
}
.into_iter();
for c in command_order {
match c {
"cargo" => {
commands.push(cargo_command(
cargos
.next()
.expect("Argument-order mismatch, this is a bug"),
&features,
));
}
"shell" => {
commands.push(
shells
.next()
.expect("Argument-order mismatch, this is a bug"),
);
}
_ => {}
}
}
}
for c in command_order {
match c {
"cargo" => {
commands.push(cargo_command(
cargos
.next()
.expect("Argument-order mismatch, this is a bug"),
&features,
));
}
"shell" => {
commands.push(
shells
.next()
.expect("Argument-order mismatch, this is a bug"),
);
}
_ => {}
}
}
}
// Default to `cargo check`
if commands.is_empty() {
let mut cmd: String = "cargo check".into();
if let Some(features) = features.as_ref() {
cmd.push_str(" --features ");
cmd.push_str(features);
}
commands.push(cmd);
}
// Default to `cargo check`
if commands.is_empty() {
let mut cmd: String = "cargo check".into();
if let Some(features) = features.as_ref() {
cmd.push_str(" --features ");
cmd.push_str(features);
}
commands.push(cmd);
}
debug!("Commands: {:?}", commands);
builder.cmd(commands);
debug!("Commands: {:?}", commands);
builder.cmd(commands);
}
fn cargo_command(cargo: String, features: &Option<String>) -> String {
let mut cmd = String::from("cargo ");
let mut cmd = String::from("cargo ");
let cargo = cargo.trim_start();
if let Some(features) = features.as_ref() {
if cargo.starts_with('b')
|| cargo.starts_with("check")
|| cargo.starts_with("doc")
|| cargo.starts_with('r')
|| cargo.starts_with("test")
|| cargo.starts_with("install")
{
// Split command into first word and the arguments
let word_boundary = cargo
.find(|c: char| c.is_whitespace())
.unwrap_or_else(|| cargo.len());
let cargo = cargo.trim_start();
if let Some(features) = features.as_ref() {
if cargo.starts_with('b')
|| cargo.starts_with("check")
|| cargo.starts_with("doc")
|| cargo.starts_with('r')
|| cargo.starts_with("test")
|| cargo.starts_with("install")
{
// Split command into first word and the arguments
let word_boundary = cargo
.find(|c: char| c.is_whitespace())
.unwrap_or_else(|| cargo.len());
// Find returns the byte index, and split_at takes a byte offset.
// This means the splitting is unicode-safe.
let (subcommand, args) = cargo.split_at(word_boundary);
cmd.push_str(subcommand);
cmd.push_str(" --features ");
cmd.push_str(features);
cmd.push(' ');
cmd.push_str(args);
} else {
cmd.push_str(&cargo);
}
} else {
cmd.push_str(&cargo);
}
// Find returns the byte index, and split_at takes a byte offset.
// This means the splitting is unicode-safe.
let (subcommand, args) = cargo.split_at(word_boundary);
cmd.push_str(subcommand);
cmd.push_str(" --features ");
cmd.push_str(features);
cmd.push(' ');
cmd.push_str(args);
} else {
cmd.push_str(&cargo);
}
} else {
cmd.push_str(&cargo);
}
cmd
cmd
}
pub fn set_ignores(builder: &mut ConfigBuilder, matches: &ArgMatches) {
if matches.is_present("ignore-nothing") {
debug!("Ignoring nothing");
if matches.is_present("ignore-nothing") {
debug!("Ignoring nothing");
builder.no_vcs_ignore(true);
builder.no_ignore(true);
return;
}
builder.no_vcs_ignore(true);
builder.no_ignore(true);
return;
}
let novcs = matches.is_present("no-gitignore");
builder.no_vcs_ignore(novcs);
debug!("Load Git/VCS ignores: {:?}", !novcs);
let novcs = matches.is_present("no-gitignore");
builder.no_vcs_ignore(novcs);
debug!("Load Git/VCS ignores: {:?}", !novcs);
let noignore = matches.is_present("no-ignore");
builder.no_ignore(noignore);
debug!("Load .ignore ignores: {:?}", !noignore);
let noignore = matches.is_present("no-ignore");
builder.no_ignore(noignore);
debug!("Load .ignore ignores: {:?}", !noignore);
let mut list = vec![
// Mac
format!("*{}.DS_Store", MAIN_SEPARATOR),
// Vim
"*.sw?".into(),
"*.sw?x".into(),
// Emacs
"#*#".into(),
".#*".into(),
// Kate
".*.kate-swp".into(),
// VCS
format!("*{s}.hg{s}**", s = MAIN_SEPARATOR),
format!("*{s}.git{s}**", s = MAIN_SEPARATOR),
format!("*{s}.svn{s}**", s = MAIN_SEPARATOR),
// SQLite
"*.db".into(),
"*.db-*".into(),
format!("*{s}*.db-journal{s}**", s = MAIN_SEPARATOR),
// Rust
format!("*{s}target{s}**", s = MAIN_SEPARATOR),
];
let mut list = vec![
// Mac
format!("*{}.DS_Store", MAIN_SEPARATOR),
// Vim
"*.sw?".into(),
"*.sw?x".into(),
// Emacs
"#*#".into(),
".#*".into(),
// Kate
".*.kate-swp".into(),
// VCS
format!("*{s}.hg{s}**", s = MAIN_SEPARATOR),
format!("*{s}.git{s}**", s = MAIN_SEPARATOR),
format!("*{s}.svn{s}**", s = MAIN_SEPARATOR),
// SQLite
"*.db".into(),
"*.db-*".into(),
format!("*{s}*.db-journal{s}**", s = MAIN_SEPARATOR),
// Rust
format!("*{s}target{s}**", s = MAIN_SEPARATOR),
];
debug!("Default ignores: {:?}", list);
debug!("Default ignores: {:?}", list);
if matches.is_present("ignore") {
for ignore in values_t!(matches, "ignore", String).unwrap_or_else(|e| e.exit()) {
#[cfg(windows)]
let ignore = ignore.replace("/", &MAIN_SEPARATOR.to_string());
list.push(ignore);
}
}
if matches.is_present("ignore") {
for ignore in values_t!(matches, "ignore", String).unwrap_or_else(|e| e.exit()) {
#[cfg(windows)]
let ignore = ignore.replace("/", &MAIN_SEPARATOR.to_string());
list.push(ignore);
}
}
debug!("All ignores: {:?}", list);
builder.ignores(list);
debug!("All ignores: {:?}", list);
builder.ignores(list);
}
pub fn set_debounce(builder: &mut ConfigBuilder, matches: &ArgMatches) {
if matches.is_present("delay") {
let debounce = value_t!(matches, "delay", f32).unwrap_or_else(|e| e.exit());
debug!("File updates debounce: {} seconds", debounce);
if matches.is_present("delay") {
let debounce = value_t!(matches, "delay", f32).unwrap_or_else(|e| e.exit());
debug!("File updates debounce: {} seconds", debounce);
let d = Duration::from_millis((debounce * 1000.0) as u64);
builder.poll_interval(d).debounce(d);
}
let d = Duration::from_millis((debounce * 1000.0) as u64);
builder.poll_interval(d).debounce(d);
}
}
pub fn set_watches(builder: &mut ConfigBuilder, matches: &ArgMatches) {
let mut opts = Vec::new();
if matches.is_present("watch") {
for watch in values_t!(matches, "watch", String).unwrap_or_else(|e| e.exit()) {
opts.push(watch.into());
}
}
let mut opts = Vec::new();
if matches.is_present("watch") {
for watch in values_t!(matches, "watch", String).unwrap_or_else(|e| e.exit()) {
opts.push(watch.into());
}
}
if opts.is_empty() {
opts.push(".".into());
}
if opts.is_empty() {
opts.push(".".into());
}
debug!("Watches: {:?}", opts);
builder.paths(opts);
debug!("Watches: {:?}", opts);
builder.paths(opts);
}
pub fn get_options(matches: &ArgMatches) -> Config {
let mut builder = ConfigBuilder::default();
builder
.poll(matches.is_present("poll"))
.clear_screen(matches.is_present("clear"))
.run_initially(!matches.is_present("postpone"))
.no_environment(true);
let mut builder = ConfigBuilder::default();
builder
.poll(matches.is_present("poll"))
.clear_screen(matches.is_present("clear"))
.run_initially(!matches.is_present("postpone"))
.no_environment(true);
// TODO in 8.0: remove --watch-when-idle and switch --no-restart behaviour to DoNothing
builder.on_busy_update(if matches.is_present("no-restart") {
OnBusyUpdate::Queue
} else if matches.is_present("watch-when-idle") {
OnBusyUpdate::DoNothing
} else {
OnBusyUpdate::Restart
});
// TODO in 8.0: remove --watch-when-idle and switch --no-restart behaviour to DoNothing
builder.on_busy_update(if matches.is_present("no-restart") {
OnBusyUpdate::Queue
} else if matches.is_present("watch-when-idle") {
OnBusyUpdate::DoNothing
} else {
OnBusyUpdate::Restart
});
builder.shell(if let Some(s) = matches.value_of("use-shell") {
if s.eq_ignore_ascii_case("powershell") {
Shell::Powershell
} else if s.eq_ignore_ascii_case("none") {
warn!("--use-shell=none is non-sensical for cargo-watch, ignoring");
default_shell()
} else if s.eq_ignore_ascii_case("cmd") {
cmd_shell(s.into())
} else {
Shell::Unix(s.into())
}
} else {
// in 8.0, just rely on default watchexec behaviour
default_shell()
});
builder.shell(if let Some(s) = matches.value_of("use-shell") {
if s.eq_ignore_ascii_case("powershell") {
Shell::Powershell
} else if s.eq_ignore_ascii_case("none") {
warn!("--use-shell=none is non-sensical for cargo-watch, ignoring");
default_shell()
} else if s.eq_ignore_ascii_case("cmd") {
cmd_shell(s.into())
} else {
Shell::Unix(s.into())
}
} else {
// in 8.0, just rely on default watchexec behaviour
default_shell()
});
set_ignores(&mut builder, matches);
set_debounce(&mut builder, matches);
set_watches(&mut builder, matches);
set_commands(&mut builder, matches);
set_ignores(&mut builder, matches);
set_debounce(&mut builder, matches);
set_watches(&mut builder, matches);
set_commands(&mut builder, matches);
let mut args = builder.build().unwrap();
args.once = matches.is_present("once");
let mut args = builder.build().unwrap();
args.once = matches.is_present("once");
debug!("Watchexec arguments: {:?}", args);
args
debug!("Watchexec arguments: {:?}", args);
args
}
// until 8.0
#[cfg(windows)]
fn default_shell() -> Shell {
Shell::Cmd
Shell::Cmd
}
#[cfg(not(windows))]
fn default_shell() -> Shell {
Shell::default()
Shell::default()
}
// because Shell::Cmd is only on windows
#[cfg(windows)]
fn cmd_shell(_: String) -> Shell {
Shell::Cmd
Shell::Cmd
}
#[cfg(not(windows))]
fn cmd_shell(s: String) -> Shell {
Shell::Unix(s)
Shell::Unix(s)
}

View File

@ -4,24 +4,24 @@ use log::debug;
use std::{env::set_current_dir, process::Command};
pub fn project_root() -> Utf8PathBuf {
Command::new("cargo")
.arg("locate-project")
.arg("--message-format")
.arg("plain")
.output()
.map_err(|err| err.to_string())
.and_then(|out| String::from_utf8(out.stdout).map_err(|err| err.to_string()))
.map(Utf8PathBuf::from)
.and_then(|path| {
path.parent()
.ok_or_else(|| String::from("project root does not exist"))
.map(ToOwned::to_owned)
})
.unwrap_or_else(|err| Error::with_description(&err, ErrorKind::Io).exit())
Command::new("cargo")
.arg("locate-project")
.arg("--message-format")
.arg("plain")
.output()
.map_err(|err| err.to_string())
.and_then(|out| String::from_utf8(out.stdout).map_err(|err| err.to_string()))
.map(Utf8PathBuf::from)
.and_then(|path| {
path.parent()
.ok_or_else(|| String::from("project root does not exist"))
.map(ToOwned::to_owned)
})
.unwrap_or_else(|err| Error::with_description(&err, ErrorKind::Io).exit())
}
pub fn change_dir(dir: Utf8PathBuf) {
debug!("change directory to: {}", dir);
set_current_dir(dir)
.unwrap_or_else(|err| Error::with_description(&err.to_string(), ErrorKind::Io).exit())
debug!("change directory to: {}", dir);
set_current_dir(dir)
.unwrap_or_else(|err| Error::with_description(&err.to_string(), ErrorKind::Io).exit())
}

View File

@ -1,80 +1,80 @@
use watchexec::{
config::Config,
error::Result,
pathop::PathOp,
run::{ExecHandler, Handler},
config::Config,
error::Result,
pathop::PathOp,
run::{ExecHandler, Handler},
};
pub struct CwHandler {
cmd: String,
once: bool,
quiet: bool,
notify: bool,
inner: ExecHandler,
cmd: String,
once: bool,
quiet: bool,
notify: bool,
inner: ExecHandler,
}
impl Handler for CwHandler {
fn args(&self) -> Config {
self.inner.args()
}
fn args(&self) -> Config {
self.inner.args()
}
fn on_manual(&self) -> Result<bool> {
if self.once {
Ok(true)
} else {
self.start();
self.inner.on_manual()
}
}
fn on_manual(&self) -> Result<bool> {
if self.once {
Ok(true)
} else {
self.start();
self.inner.on_manual()
}
}
fn on_update(&self, ops: &[PathOp]) -> Result<bool> {
self.start();
self.inner.on_update(ops).map(|o| {
#[cfg(not(target_os = "freebsd"))]
if self.notify {
notify_rust::Notification::new()
.summary("Cargo Watch observed a change")
.body("Cargo Watch has seen a change, the command may have restarted.")
.show()
.map(drop)
.unwrap_or_else(|err| {
log::warn!("Failed to send desktop notification: {}", err);
});
}
fn on_update(&self, ops: &[PathOp]) -> Result<bool> {
self.start();
self.inner.on_update(ops).map(|o| {
#[cfg(not(target_os = "freebsd"))]
if self.notify {
notify_rust::Notification::new()
.summary("Cargo Watch observed a change")
.body("Cargo Watch has seen a change, the command may have restarted.")
.show()
.map(drop)
.unwrap_or_else(|err| {
log::warn!("Failed to send desktop notification: {}", err);
});
}
o
})
}
o
})
}
}
impl CwHandler {
pub fn new(mut args: Config, quiet: bool, notify: bool) -> Result<Self> {
let cmd = args.cmd.join(" && ");
let mut final_cmd = cmd.clone();
if !quiet {
#[cfg(unix)]
final_cmd.push_str(r#"; echo "[Finished running. Exit status: $?]""#);
#[cfg(windows)]
final_cmd.push_str(r#" & echo "[Finished running. Exit status: %ERRORLEVEL%]""#);
#[cfg(not(any(unix, windows)))]
final_cmd.push_str(r#" ; echo "[Finished running]""#);
// ^ could be wrong depending on the platform, to be fixed on demand
}
pub fn new(mut args: Config, quiet: bool, notify: bool) -> Result<Self> {
let cmd = args.cmd.join(" && ");
let mut final_cmd = cmd.clone();
if !quiet {
#[cfg(unix)]
final_cmd.push_str(r#"; echo "[Finished running. Exit status: $?]""#);
#[cfg(windows)]
final_cmd.push_str(r#" & echo "[Finished running. Exit status: %ERRORLEVEL%]""#);
#[cfg(not(any(unix, windows)))]
final_cmd.push_str(r#" ; echo "[Finished running]""#);
// ^ could be wrong depending on the platform, to be fixed on demand
}
args.cmd = vec![final_cmd];
args.cmd = vec![final_cmd];
Ok(Self {
once: args.once,
cmd,
inner: ExecHandler::new(args)?,
quiet,
notify,
})
}
Ok(Self {
once: args.once,
cmd,
inner: ExecHandler::new(args)?,
quiet,
notify,
})
}
fn start(&self) {
if !self.quiet {
println!("[Running '{}']", self.cmd);
}
}
fn start(&self) {
if !self.quiet {
println!("[Running '{}']", self.cmd);
}
}
}

View File

@ -1,197 +1,197 @@
use assert_cmd::prelude::*;
use std::{
fs::OpenOptions,
io::{self, Write},
path::PathBuf,
process::{Command, Stdio},
thread::sleep,
time::{Duration, Instant},
fs::OpenOptions,
io::{self, Write},
path::PathBuf,
process::{Command, Stdio},
thread::sleep,
time::{Duration, Instant},
};
use wait_timeout::ChildExt;
fn touch(n: u8) -> io::Result<()> {
let path: PathBuf = format!("./tests/touchdata/{}.txt", n).into();
let mut file = OpenOptions::new().create(true).write(true).open(path)?;
let path: PathBuf = format!("./tests/touchdata/{}.txt", n).into();
let mut file = OpenOptions::new().create(true).write(true).open(path)?;
writeln!(&mut file, "{:?}", Instant::now())?;
Ok(())
writeln!(&mut file, "{:?}", Instant::now())?;
Ok(())
}
fn std_to_string<T: io::Read>(handle: &mut Option<T>) -> String {
if let Some(ref mut handle) = handle {
let mut buf = String::with_capacity(1024);
handle.read_to_string(&mut buf).unwrap();
buf
} else {
unreachable!()
}
if let Some(ref mut handle) = handle {
let mut buf = String::with_capacity(1024);
handle.read_to_string(&mut buf).unwrap();
buf
} else {
unreachable!()
}
}
// fsevents has trouble
#[cfg(not(target_os = "macos"))]
#[test]
fn without_poll() {
let mut main = Command::cargo_bin("cargo-watch")
.unwrap()
.stderr(Stdio::piped())
.stdout(Stdio::piped())
.args(&[
"--testing-only--once",
"--no-gitignore",
"-w",
"./tests/touchdata/",
"-s",
"echo it runs",
])
.spawn()
.unwrap();
let mut main = Command::cargo_bin("cargo-watch")
.unwrap()
.stderr(Stdio::piped())
.stdout(Stdio::piped())
.args(&[
"--testing-only--once",
"--no-gitignore",
"-w",
"./tests/touchdata/",
"-s",
"echo it runs",
])
.spawn()
.unwrap();
sleep(Duration::from_secs(2));
touch(0).unwrap();
sleep(Duration::from_secs(2));
touch(0).unwrap();
if main
.wait_timeout(Duration::from_secs(30))
.unwrap()
.is_none()
{
main.kill().unwrap();
}
if main
.wait_timeout(Duration::from_secs(30))
.unwrap()
.is_none()
{
main.kill().unwrap();
}
main.wait_with_output().unwrap().assert().success();
main.wait_with_output().unwrap().assert().success();
}
#[test]
fn with_poll() {
let mut main = Command::cargo_bin("cargo-watch")
.unwrap()
.stderr(Stdio::piped())
.stdout(Stdio::piped())
.args(&[
"--testing-only--once",
"--no-gitignore",
"--poll",
"-w",
"./tests/touchdata/",
"-s",
"echo it runs",
])
.spawn()
.unwrap();
let mut main = Command::cargo_bin("cargo-watch")
.unwrap()
.stderr(Stdio::piped())
.stdout(Stdio::piped())
.args(&[
"--testing-only--once",
"--no-gitignore",
"--poll",
"-w",
"./tests/touchdata/",
"-s",
"echo it runs",
])
.spawn()
.unwrap();
sleep(Duration::from_secs(2));
touch(1).unwrap();
sleep(Duration::from_secs(2));
touch(1).unwrap();
if main
.wait_timeout(Duration::from_secs(30))
.unwrap()
.is_none()
{
main.kill().unwrap();
}
if main
.wait_timeout(Duration::from_secs(30))
.unwrap()
.is_none()
{
main.kill().unwrap();
}
main.wait_with_output().unwrap().assert().success();
main.wait_with_output().unwrap().assert().success();
}
#[test]
#[cfg(not(windows))] // annoyingly, theres some kind of encoding or extra bytes getting added here, needs debugging
fn with_announce() {
let mut main = Command::cargo_bin("cargo-watch")
.unwrap()
.stderr(Stdio::piped())
.stdout(Stdio::piped())
.args(&[
"--testing-only--once",
"--no-gitignore",
"--poll",
"-w",
"./tests/touchdata/",
"-s",
"echo with announce",
])
.spawn()
.unwrap();
let mut main = Command::cargo_bin("cargo-watch")
.unwrap()
.stderr(Stdio::piped())
.stdout(Stdio::piped())
.args(&[
"--testing-only--once",
"--no-gitignore",
"--poll",
"-w",
"./tests/touchdata/",
"-s",
"echo with announce",
])
.spawn()
.unwrap();
sleep(Duration::from_secs(2));
touch(2).unwrap();
sleep(Duration::from_secs(2));
touch(2).unwrap();
if main
.wait_timeout(Duration::from_secs(30))
.unwrap()
.is_none()
{
main.kill().unwrap();
}
if main
.wait_timeout(Duration::from_secs(30))
.unwrap()
.is_none()
{
main.kill().unwrap();
}
insta::assert_snapshot!("with_announce.stderr", std_to_string(&mut main.stderr));
insta::assert_snapshot!("with_announce.stdout", std_to_string(&mut main.stdout));
insta::assert_snapshot!("with_announce.stderr", std_to_string(&mut main.stderr));
insta::assert_snapshot!("with_announce.stdout", std_to_string(&mut main.stdout));
}
#[test]
fn without_announce() {
let mut main = Command::cargo_bin("cargo-watch")
.unwrap()
.stderr(Stdio::piped())
.stdout(Stdio::piped())
.args(&[
"--testing-only--once",
"--no-gitignore",
"--quiet",
"--poll",
"-w",
"./tests/touchdata/",
"-s",
"echo without announce",
])
.spawn()
.unwrap();
let mut main = Command::cargo_bin("cargo-watch")
.unwrap()
.stderr(Stdio::piped())
.stdout(Stdio::piped())
.args(&[
"--testing-only--once",
"--no-gitignore",
"--quiet",
"--poll",
"-w",
"./tests/touchdata/",
"-s",
"echo without announce",
])
.spawn()
.unwrap();
sleep(Duration::from_secs(2));
touch(3).unwrap();
sleep(Duration::from_secs(2));
touch(3).unwrap();
if main
.wait_timeout(Duration::from_secs(30))
.unwrap()
.is_none()
{
main.kill().unwrap();
}
if main
.wait_timeout(Duration::from_secs(30))
.unwrap()
.is_none()
{
main.kill().unwrap();
}
insta::assert_snapshot!("without_announce.stderr", std_to_string(&mut main.stderr));
insta::assert_snapshot!("without_announce.stdout", std_to_string(&mut main.stdout));
insta::assert_snapshot!("without_announce.stderr", std_to_string(&mut main.stderr));
insta::assert_snapshot!("without_announce.stdout", std_to_string(&mut main.stdout));
}
#[cfg(unix)]
#[test]
fn with_error() {
let mut main = Command::cargo_bin("cargo-watch")
.unwrap()
.stderr(Stdio::piped())
.stdout(Stdio::piped())
.args(&[
"--testing-only--once",
"--no-gitignore",
"--poll",
"-w",
"./tests/touchdata/",
"-s",
"echo with error",
"-s",
"false",
])
.spawn()
.unwrap();
let mut main = Command::cargo_bin("cargo-watch")
.unwrap()
.stderr(Stdio::piped())
.stdout(Stdio::piped())
.args(&[
"--testing-only--once",
"--no-gitignore",
"--poll",
"-w",
"./tests/touchdata/",
"-s",
"echo with error",
"-s",
"false",
])
.spawn()
.unwrap();
sleep(Duration::from_secs(2));
touch(4).unwrap();
sleep(Duration::from_secs(2));
touch(4).unwrap();
if main
.wait_timeout(Duration::from_secs(30))
.unwrap()
.is_none()
{
main.kill().unwrap();
}
if main
.wait_timeout(Duration::from_secs(30))
.unwrap()
.is_none()
{
main.kill().unwrap();
}
insta::assert_snapshot!("with_error.stderr", std_to_string(&mut main.stderr));
insta::assert_snapshot!("with_error.stdout", std_to_string(&mut main.stdout));
insta::assert_snapshot!("with_error.stderr", std_to_string(&mut main.stderr));
insta::assert_snapshot!("with_error.stdout", std_to_string(&mut main.stdout));
}

View File

@ -1,69 +1,69 @@
use assert_cmd::prelude::*;
use predicates::str::is_match;
use std::{
process::{Command, Stdio},
time::Duration,
process::{Command, Stdio},
time::Duration,
};
use wait_timeout::ChildExt;
#[test]
fn with_cargo() {
let mut main = Command::new("cargo")
.stderr(Stdio::piped())
.stdout(Stdio::piped())
.args(&["watch", "--version"])
.spawn()
.unwrap();
let mut main = Command::new("cargo")
.stderr(Stdio::piped())
.stdout(Stdio::piped())
.args(&["watch", "--version"])
.spawn()
.unwrap();
if main.wait_timeout(Duration::from_secs(1)).unwrap().is_none() {
main.kill().unwrap();
}
if main.wait_timeout(Duration::from_secs(1)).unwrap().is_none() {
main.kill().unwrap();
}
main.wait_with_output()
.unwrap()
.assert()
.success()
.stdout(is_match(r"cargo-watch \d+\.\d+\.\d+\n").unwrap());
main.wait_with_output()
.unwrap()
.assert()
.success()
.stdout(is_match(r"cargo-watch \d+\.\d+\.\d+\n").unwrap());
}
#[test]
fn without_cargo() {
let mut main = Command::cargo_bin("cargo-watch")
.unwrap()
.stderr(Stdio::piped())
.stdout(Stdio::piped())
.args(&["watch", "--version"])
.spawn()
.unwrap();
let mut main = Command::cargo_bin("cargo-watch")
.unwrap()
.stderr(Stdio::piped())
.stdout(Stdio::piped())
.args(&["watch", "--version"])
.spawn()
.unwrap();
if main.wait_timeout(Duration::from_secs(1)).unwrap().is_none() {
main.kill().unwrap();
}
if main.wait_timeout(Duration::from_secs(1)).unwrap().is_none() {
main.kill().unwrap();
}
main.wait_with_output()
.unwrap()
.assert()
.success()
.stdout(is_match(r"cargo-watch \d+\.\d+\.\d+\n").unwrap());
main.wait_with_output()
.unwrap()
.assert()
.success()
.stdout(is_match(r"cargo-watch \d+\.\d+\.\d+\n").unwrap());
}
#[test]
fn without_watch() {
let mut main = Command::cargo_bin("cargo-watch")
.unwrap()
.stderr(Stdio::piped())
.stdout(Stdio::piped())
.args(&["--version"])
.spawn()
.unwrap();
let mut main = Command::cargo_bin("cargo-watch")
.unwrap()
.stderr(Stdio::piped())
.stdout(Stdio::piped())
.args(&["--version"])
.spawn()
.unwrap();
if main.wait_timeout(Duration::from_secs(1)).unwrap().is_none() {
main.kill().unwrap();
}
if main.wait_timeout(Duration::from_secs(1)).unwrap().is_none() {
main.kill().unwrap();
}
main.wait_with_output()
.unwrap()
.assert()
.success()
.stdout(is_match(r"cargo-watch \d+\.\d+\.\d+\n").unwrap());
main.wait_with_output()
.unwrap()
.assert()
.success()
.stdout(is_match(r"cargo-watch \d+\.\d+\.\d+\n").unwrap());
}