Compare commits

..

3 Commits

Author SHA1 Message Date
grimhilt
7719e27fe8 clean main: divide clap config into multiple files, broke clone 70 lines width 2023-10-28 23:46:12 +02:00
grimhilt
fc8e976c9c add add/directory tests 2023-10-28 22:23:48 +02:00
grimhilt
53b103af9e fix add -A 2023-10-28 22:12:27 +02:00
21 changed files with 418 additions and 264 deletions

View File

@ -5,7 +5,7 @@ use clap::Values;
use glob::glob;
use crate::store::index;
use crate::store::{self, object::Object};
use crate::utils;
use crate::utils::{self, path};
use crate::utils::nextsyncignore::{self, ignore_file};
use crate::utils::path::{normalize_relative, repo_root, path_buf_to_string};
@ -19,17 +19,21 @@ pub struct AddArgs<'a> {
// todo match deleted files
pub fn add(args: AddArgs) {
// write all modification in the index
if args.all {
write_all();
return;
}
let mut pattern: String;
let file_vec: Vec<&str> = match args.all {
true => {
pattern = path_buf_to_string(repo_root());
pattern.push_str("/*");
vec![&pattern]
},
false => args.files.unwrap().collect(),
};
let mut added_files: Vec<String> = vec![];
let mut ignored_f = vec![];
let rules = nextsyncignore::get_rules();
let file_vec: Vec<&str> = args.files.unwrap().collect();
for file in file_vec {
let f = match normalize_relative(file) {
Ok(f) => f,
@ -58,6 +62,9 @@ pub fn add(args: AddArgs) {
added_files.push(String::from(f));
} else {
for entry in try_globbing(path) {
if path::is_nextsync_config(entry.clone()) {
continue;
}
if !args.force && ignore_file(&path_buf_to_string(entry.clone()), rules.clone(), &mut ignored_f) {
continue;
}
@ -125,22 +132,16 @@ fn add_folder_content(path: PathBuf, added_files: &mut Vec<String>) {
if let Ok(entries) = utils::read::read_folder(folder.clone()) {
for entry in entries {
let path_entry = PathBuf::from(entry);
if !path::is_nextsync_config(path_entry.clone())
{
if path_entry.is_dir() {
folders.push(path_entry.clone());
}
added_files.push(String::from(path_entry.to_str().unwrap()));
added_files.push(path_buf_to_string(path_entry.strip_prefix(repo_root()).unwrap().to_path_buf()));
}
}
}
}
}
fn write_all() {
let objs = get_all_objs();
let mut index_file = OpenOptions::new()
.write(true)
.create(true)
.open(index::path()).expect("Cannot open index file");
for obj in objs {
let _ = writeln!(index_file, "{}", path_buf_to_string(obj.path.clone()));
}
}

View File

@ -95,7 +95,7 @@ fn should_retain(hasher: &mut Sha1, key: String, obj: LocalObj, move_copy_hashes
// todo deal with directories
if obj.path.is_dir()
{
return false;
return true;
}
let mut blob = Blob::new(obj.path.clone());
let mut flag = true;
@ -329,7 +329,7 @@ fn add_to_hashmap(lines: Lines<BufReader<File>>, hashes: &mut HashMap<String, Lo
fn add_to_vec(entries: Vec<PathBuf>, objects: &mut Vec<String>, root: PathBuf) {
for entry in entries {
if !is_nextsync_config(entry.clone()) {
if !path::is_nextsync_config(entry.clone()) {
let object_path = entry.strip_prefix(root.clone()).unwrap();
objects.push(String::from(object_path.to_str().unwrap()));
}
@ -441,10 +441,6 @@ fn remove_duplicate(hashes: &mut HashMap<String, LocalObj>, objects: &mut Vec<St
duplicate
}
fn is_nextsync_config(path: PathBuf) -> bool {
path.ends_with(".nextsync")
}
#[cfg(test)]
mod tests {
use super::*;

View File

@ -1,9 +1,6 @@
use clap::{App, Arg, SubCommand};
use textwrap::{fill, Options};
use clap::{App, SubCommand};
use crate::commands::add::AddArgs;
use crate::commands::status::StatusArgs;
use crate::commands::clone::{self, CloneArgs};
mod subcommands;
mod commands;
mod utils;
@ -12,193 +9,38 @@ mod global;
mod store;
fn main() {
let matches = App::new("Nextsync")
let app = App::new("Nextsync")
.version("1.0")
.author("grimhilt")
.about("A git-line command line tool to interact with nextcloud")
.setting(clap::AppSettings::SubcommandRequiredElseHelp)
.subcommand(
SubCommand::with_name("clone")
.arg(
Arg::with_name("remote")
.required(true)
.takes_value(true)
.value_name("REMOTE")
.help(&fill(
"The repository to clone from. See the NEXTSYNC URLS section below for more information on specifying repositories.",
Options::new(70).width,
))
)
.arg(
Arg::with_name("depth")
.short("d")
.long("depth")
.required(false)
.takes_value(true)
.help(&fill(
&format!("Depth of the recursive fetch of object properties. This value should be lower when there are a lot of files per directory and higher when there are a lot of subdirectories with fewer files. (Default: {})", clone::DEPTH),
Options::new(70).width,
))
)
.arg(
Arg::with_name("directory")
.required(false)
.takes_value(true)
.value_name("DIRECTORY")
)
.about("Clone a repository into a new directory")
.after_help("NEXTSYNC URLS\nThe following syntaxes may be used:\n\t- user@host.xz/path/to/repo\n\t- http[s]://host.xz/apps/files/?dir=/path/to/repo&fileid=111111\n\t- [http[s]://]host.xz/remote.php/dav/files/user/path/to/repo\n")
)
.subcommand(
SubCommand::with_name("init")
.arg(
Arg::with_name("directory")
.required(false)
.takes_value(true)
.value_name("DIRECTORY")
)
.about("Create an empty Nextsync repository") // Create an empty Git repository or reinitialize an existing one
)
.subcommand(
SubCommand::with_name("status")
.arg(
Arg::with_name("directory")
.required(false)
.takes_value(true)
.value_name("DIRECTORY")
)
.arg(
Arg::with_name("nostyle")
.long("nostyle")
.help("Status with minium information and style"),
)
.about("Show the working tree status")
)
.subcommand(
SubCommand::with_name("reset")
.about("Clear the index")
)
.subcommand(
SubCommand::with_name("push")
.about("Push changes on nextcloud")
)
.subcommand(
SubCommand::with_name("add")
.arg(
Arg::with_name("files")
.required(true)
.conflicts_with("all")
.multiple(true)
.takes_value(true)
.value_name("FILE")
.help("Files to add"),
)
.arg(
Arg::with_name("force")
.short("f")
.long("force")
.help("Allow adding otherwise ignored files."),
)
.arg(
Arg::with_name("all")
.short("A")
.long("all")
.help("This adds, modifies, and removes index entries to match the working tree"),
)
.about("Add changes to the index")
)
.subcommand(
SubCommand::with_name("config")
.arg(
Arg::with_name("variable")
.required(true)
.takes_value(true)
.value_name("VARIABLE")
)
.arg(
Arg::with_name("value")
.required(true)
.takes_value(true)
.value_name("VALUE")
)
)
.subcommand(
SubCommand::with_name("remote-diff")
.arg(
Arg::with_name("path")
.required(false)
.takes_value(true)
.value_name("PATH")
.help("The path to pull."),
)
.about("Fetch new and modifed files from the nextcloud server.")
)
.subcommand(subcommands::clone::create())
.subcommand(subcommands::init::create())
.subcommand(subcommands::status::create())
.subcommand(subcommands::add::create())
.subcommand(subcommands::push::create())
.subcommand(subcommands::reset::create())
.subcommand(subcommands::config::create())
.subcommand(subcommands::remote_diff::create())
.subcommand(subcommands::pull::create())
.subcommand(
SubCommand::with_name("test")
)
.get_matches();
);
if let Some(matches) = matches.subcommand_matches("init") {
if let Some(val) = matches.values_of("directory") {
global::global::set_dir_path(String::from(val.clone().next().unwrap()));
}
commands::init::init();
} else if let Some(matches) = matches.subcommand_matches("status") {
if let Some(val) = matches.values_of("directory") {
global::global::set_dir_path(String::from(val.clone().next().unwrap()));
}
commands::status::status(StatusArgs {
nostyle: matches.is_present("nostyle"),
});
} else if let Some(matches) = matches.subcommand_matches("add") {
if let Some(files) = matches.values_of("files") {
commands::add::add(AddArgs {
files: Some(files),
force: matches.is_present("force"),
all: matches.is_present("all"),
});
} else {
commands::add::add(AddArgs {
files: None,
force: matches.is_present("force"),
all: matches.is_present("all"),
});
}
} else if let Some(_) = matches.subcommand_matches("reset") {
commands::reset::reset();
} else if let Some(matches) = matches.subcommand_matches("clone") {
if let Some(val) = matches.values_of("directory") {
global::global::set_dir_path(String::from(val.clone().next().unwrap()));
}
if let Some(remote) = matches.values_of("remote") {
commands::clone::clone(CloneArgs {
remote,
depth: matches.values_of("depth").map(
|mut val| val.next().unwrap().to_owned()
),
});
}
} else if let Some(_matches) = matches.subcommand_matches("push") {
commands::push::push();
} else if let Some(matches) = matches.subcommand_matches("config") {
if let Some(mut var) = matches.values_of("variable") {
if let Some(mut val) = matches.values_of("value") {
if commands::config::set(var.next().unwrap(), val.next().unwrap()).is_err() {
eprintln!("fatal: cannot save the value");
}
}
}
} else if let Some(matches) = matches.subcommand_matches("remote-diff") {
if let Some(val) = matches.values_of("path") {
global::global::set_dir_path(String::from(val.clone().next().unwrap()));
}
commands::remote_diff::remote_diff();
} else if let Some(matches) = matches.subcommand_matches("pull") {
if let Some(val) = matches.values_of("path") {
global::global::set_dir_path(String::from(val.clone().next().unwrap()));
}
commands::pull::pull();
} else if let Some(_) = matches.subcommand_matches("test") {
}
let matches = app.get_matches();
match matches.subcommand() {
("init", Some(args)) => subcommands::init::handler(args),
("status", Some(args)) => subcommands::status::handler(args),
("add", Some(args)) => subcommands::add::handler(args),
("reset", Some(_)) => commands::reset::reset(),
("clone", Some(args)) => subcommands::clone::handler(args),
("push", Some(_)) => commands::push::push(),
("config", Some(args)) => subcommands::config::handler(args),
("remote-diff", Some(args)) => subcommands::remote_diff::handler(args),
("pull", Some(args)) => subcommands::pull::handler(args),
(_, _) => {},
};
}

View File

@ -1,6 +1,6 @@
use reqwest::{Method, header::HeaderValue};
use crate::services::api::{ApiBuilder, ApiError};
use crate::clone::get_url_props;
use crate::commands::clone::get_url_props;
use crate::commands::config;
use crate::services::api_call::ApiCall;

View File

@ -1,6 +1,6 @@
use reqwest::{Method, header::HeaderValue};
use crate::services::api::{ApiBuilder, ApiError};
use crate::clone::get_url_props;
use crate::commands::clone::get_url_props;
use crate::commands::config;
use crate::services::api_call::ApiCall;

View File

@ -76,9 +76,4 @@ impl RequestManager {
self.token.clone().unwrap()
}
pub fn create_request()
{
}
}

9
src/subcommands.rs Normal file
View File

@ -0,0 +1,9 @@
pub mod init;
pub mod status;
pub mod add;
pub mod reset;
pub mod clone;
pub mod push;
pub mod config;
pub mod remote_diff;
pub mod pull;

38
src/subcommands/add.rs Normal file
View File

@ -0,0 +1,38 @@
use clap::{App, Arg, SubCommand, ArgMatches};
use crate::commands;
use crate::commands::add::AddArgs;
pub fn create() -> App<'static, 'static> {
SubCommand::with_name("add")
.arg(
Arg::with_name("files")
.required(true)
.conflicts_with("all")
.multiple(true)
.takes_value(true)
.value_name("FILE")
.help("Files to add"),
)
.arg(
Arg::with_name("force")
.short("f")
.long("force")
.help("Allow adding otherwise ignored files."),
)
.arg(
Arg::with_name("all")
.short("A")
.long("all")
.help("This adds, modifies, and removes index entries to match the working tree"),
)
.about("Add changes to the index")
}
pub fn handler(args: &ArgMatches<'_>) {
commands::add::add(AddArgs {
files: args.values_of("files"),
force: args.is_present("force"),
all: args.is_present("all"),
});
}

59
src/subcommands/clone.rs Normal file
View File

@ -0,0 +1,59 @@
use clap::{App, Arg, SubCommand, ArgMatches};
use std::borrow::Cow;
use textwrap::{fill, Options};
use crate::commands::clone::{self, CloneArgs};
use crate::global;
use crate::commands;
fn sized_str<'a>(content: &'a str) -> &'a str {
fill(content, Options::new(70).width).as_str();
"ok"
}
fn test() -> String {
String::from("ok")
}
pub fn create() -> App<'static, 'static> {
let remote_desc = sized_str(&format!("The repository to clone from. See the NEXTSYNC URLS section below for more information on specifying repositories."));
let depth_desc = sized_str(&format!("Depth of the recursive fetch of object properties. This value should be lower when there are a lot of files per directory and higher when there are a lot of subdirectories with fewer files. (Default: {})", clone::DEPTH));
SubCommand::with_name("clone")
.arg(
Arg::with_name("remote")
.required(true)
.takes_value(true)
.value_name("REMOTE")
//.help(_desc)
)
.arg(
Arg::with_name("depth")
.short("d")
.long("depth")
.required(false)
.takes_value(true)
//.help(&depth_desc)
)
.arg(
Arg::with_name("directory")
.required(false)
.takes_value(true)
.value_name("DIRECTORY")
)
.about("Clone a repository into a new directory")
.after_help("NEXTSYNC URLS\nThe following syntaxes may be used:\n\t- user@host.xz/path/to/repo\n\t- http[s]://host.xz/apps/files/?dir=/path/to/repo&fileid=111111\n\t- [http[s]://]host.xz/remote.php/dav/files/user/path/to/repo\n")
}
pub fn handler(args: &ArgMatches<'_>) {
if let Some(val) = args.values_of("directory") {
global::global::set_dir_path(String::from(val.clone().next().unwrap()));
}
if let Some(remote) = args.values_of("remote") {
commands::clone::clone(CloneArgs {
remote,
depth: args.values_of("depth").map(
|mut val| val.next().unwrap().to_owned()
),
});
}
}

29
src/subcommands/config.rs Normal file
View File

@ -0,0 +1,29 @@
use clap::{App, Arg, SubCommand, ArgMatches};
use crate::commands;
pub fn create() -> App<'static, 'static> {
SubCommand::with_name("config")
.arg(
Arg::with_name("variable")
.required(true)
.takes_value(true)
.value_name("VARIABLE")
)
.arg(
Arg::with_name("value")
.required(true)
.takes_value(true)
.value_name("VALUE")
)
}
pub fn handler(args: &ArgMatches<'_>) {
if let Some(mut var) = args.values_of("variable") {
if let Some(mut val) = args.values_of("value") {
if commands::config::set(var.next().unwrap(), val.next().unwrap()).is_err() {
eprintln!("fatal: cannot save the value");
}
}
}
}

23
src/subcommands/init.rs Normal file
View File

@ -0,0 +1,23 @@
use clap::{App, Arg, SubCommand, ArgMatches};
use crate::global;
use crate::commands;
pub fn create() -> App<'static, 'static> {
SubCommand::with_name("init")
.arg(
Arg::with_name("directory")
.required(false)
.takes_value(true)
.value_name("DIRECTORY")
)
.about("Create an empty Nextsync repository")
// Create an empty nextsync repository or reinitialize an existing one
}
pub fn handler(args: &ArgMatches<'_>) {
if let Some(val) = args.values_of("directory") {
global::global::set_dir_path(String::from(val.clone().next().unwrap()));
}
commands::init::init();
}

23
src/subcommands/pull.rs Normal file
View File

@ -0,0 +1,23 @@
use clap::{App, Arg, SubCommand, ArgMatches};
use crate::global;
use crate::commands;
pub fn create() -> App<'static, 'static> {
SubCommand::with_name("pull")
.arg(
Arg::with_name("path")
.required(false)
.takes_value(true)
.value_name("PATH")
.help("The path to pull."),
)
.about("Fetch and integrate changes from the nextcloud server.")
}
pub fn handler(args: &ArgMatches<'_>) {
if let Some(val) = args.values_of("path") {
global::global::set_dir_path(String::from(val.clone().next().unwrap()));
}
commands::pull::pull();
}

6
src/subcommands/push.rs Normal file
View File

@ -0,0 +1,6 @@
use clap::{App, Arg, SubCommand};
pub fn create() -> App<'static, 'static> {
SubCommand::with_name("push")
.about("Push changes on nextcloud")
}

View File

@ -0,0 +1,24 @@
use clap::{App, Arg, SubCommand, ArgMatches};
use crate::global;
use crate::commands;
pub fn create() -> App<'static, 'static> {
SubCommand::with_name("remote-diff")
.arg(
Arg::with_name("path")
.required(false)
.takes_value(true)
.value_name("PATH")
.help("The path to pull."),
)
.about("Fetch changes from the nextcloud server.")
}
pub fn handler(args: &ArgMatches<'_>) {
if let Some(val) = args.values_of("path") {
global::global::set_dir_path(String::from(val.clone().next().unwrap()));
}
commands::remote_diff::remote_diff();
}

6
src/subcommands/reset.rs Normal file
View File

@ -0,0 +1,6 @@
use clap::{App, Arg, SubCommand};
pub fn create() -> App<'static, 'static> {
SubCommand::with_name("reset")
.about("Clear the index")
}

30
src/subcommands/status.rs Normal file
View File

@ -0,0 +1,30 @@
use clap::{App, Arg, SubCommand, ArgMatches};
use crate::global;
use crate::commands;
use crate::commands::status::StatusArgs;
pub fn create() -> App<'static, 'static> {
SubCommand::with_name("status")
.arg(
Arg::with_name("directory")
.required(false)
.takes_value(true)
.value_name("DIRECTORY")
)
.arg(
Arg::with_name("nostyle")
.long("nostyle")
.help("Status with minium information and style"),
)
.about("Show the working tree status")
}
pub fn handler(args: &ArgMatches<'_>) {
if let Some(val) = args.values_of("directory") {
global::global::set_dir_path(String::from(val.clone().next().unwrap()));
}
commands::status::status(StatusArgs {
nostyle: args.is_present("nostyle"),
});
}

View File

@ -104,6 +104,10 @@ pub fn repo_root() -> PathBuf {
}
}
pub fn is_nextsync_config(path: PathBuf) -> bool {
path.ends_with(".nextsync") || path.starts_with(".nextsync")
}
pub fn nextsync() -> PathBuf {
let mut path = repo_root();
path.push(".nextsync");

45
tests/add/directory.sh Executable file
View File

@ -0,0 +1,45 @@
#!/bin/sh
source ./utils.sh
nb_tests=0
TEST_SUITE_NAME="add/directory/"
add_test_no_env() {
touch $2
$exe add $3
status_cmp "$1" "$4"
}
add_test() {
nb_tests=$((nb_tests + 1))
setup_env
$exe init
add_test_no_env "$1" "$2" "$3" "$4"
}
add_dir() {
nb_tests=$((nb_tests + 1))
setup_env
$exe init
mkdir dir
$exe add "dir"
res=$($exe status --nostyle)
status_cmp "dir" "new: dir"
}
add_subdir() {
nb_tests=$((nb_tests + 1))
setup_env
$exe init
mkdir foo foo/bar
$exe add "foo"
res=$($exe status --nostyle)
status_cmp "dir" "new: foo/bar\nnew: foo"
}
add_dir
add_subdir
echo $nb_tests
exit 0

View File

@ -1,38 +1,14 @@
#!/bin/sh
source ./utils.sh
nb_tests=0
TEST_SUITE_NAME="add/file/"
get_exe() {
exe=$(pwd)
exe+="/../target/debug/nextsync"
if [ ! -f $exe ]; then
echo "No executable found, try to compile first" >&2
exit 4
fi
}
setup_env() {
[ ! -v exe ] && get_exe
path=$(mktemp -d)
cd $path
}
add_cmp() {
res=$($exe status --nostyle)
diff <(echo -e "$2" ) <(echo -e "$res") 2> /dev/null > /dev/null
if [ $? -ne 0 ]; then
echo -e "$TEST_SUITE_NAME$1: Output differ:" >&2
diff -u <(echo -e "$2" ) <(echo -e "$res") | grep "^[-\+\ ][^-\+]" >&2
echo -e "\nMore in $path" >&2
echo $nb_tests
exit 1
fi
}
add_test_no_env() {
touch $2
$exe add $3
add_cmp "$1" "$4"
status_cmp "$1" "$4"
}
add_test() {
@ -53,7 +29,7 @@ add_space() {
touch 'to to'
$exe add 'to to'
res=$($exe status --nostyle)
add_cmp "space" "new: to to"
status_cmp "space" "new: to to"
}
add_multiple() {
@ -64,7 +40,7 @@ add_regex() {
add_test "regex" "titi riri" "./*" "new: riri\nnew: titi"
}
add_subdir() {
add_file_subdir() {
nb_tests=$((nb_tests + 1))
setup_env
$exe init
@ -72,7 +48,19 @@ add_subdir() {
touch dir/toto
$exe add "./dir/toto"
res=$($exe status --nostyle)
add_cmp "subdir" "new: dir/toto"
status_cmp "file_subdir" "new: dir/toto"
}
add_whole_subdir() {
nb_tests=$((nb_tests + 1))
setup_env
$exe init
mkdir dir
touch dir/toto
touch dir/roro
$exe add "dir"
res=$($exe status --nostyle)
status_cmp "whole_subdir" "new: dir/roro\nnew: dir/toto\nnew: dir"
}
add_subdir_regex() {
@ -83,7 +71,7 @@ add_subdir_regex() {
touch dir/toto dir/roro
$exe add "./dir/*"
res=$($exe status --nostyle)
add_cmp "subdir_regex" "new: dir/roro\nnew: dir/toto"
status_cmp "subdir_regex" "new: dir/roro\nnew: dir/toto"
}
add_duplication() {
@ -106,20 +94,17 @@ add_all() {
touch dir/toto dir/roro lolo
$exe add -A
res=$($exe status --nostyle)
add/file/all: Output differ:
add_cmp "all" "new: dir/roro\nnew: dir/toto\nnew: lolo\nnew: .nextsyncignore"
status_cmp "all" "new: .nextsyncignore\nnew: dir/roro\nnew: dir/toto\nnew: dir\nnew: lolo"
}
#test nextsyncignore
#test inside folder
#test -A duplication
#test add file without changes
add_basics
add_space
add_multiple
add_regex
add_subdir
add_file_subdir
add_whole_subdir
add_subdir_regex
add_duplication
add_duplication_subdir

View File

@ -1,14 +1,11 @@
#!/bin/sh
source ./utils.sh
# Getting all tests
TESTS=$(find -name "*.sh" -not -name "main.sh")
TESTS=$(find -mindepth 2 -name "*.sh")
if [ $# -ne 0 ]; then
TESTS=$(find -name "*$1*" -not -name "main.sh")
tests=""
for obj in $TESTS; do
[ -d $obj ] && tests+=$(find -path "$obj/*.sh" -not -name "main.sh")
done;
TESTS=$tests
TESTS=$(find -mindepth 2 -path "*$1*")
fi
# Executing tests
@ -18,7 +15,7 @@ for test in $TESTS; do
#nb_tests=$((nb_tests + 1))
# run file
tmp_stderr=$(mktemp)
tmp_stderr=$(mktf)
nb_tests_tmp=$($test 2>"$tmp_stderr")
exit_code=$?
capture_stderr=$(<"$tmp_stderr")
@ -30,8 +27,10 @@ for test in $TESTS; do
[ $nb_tests_tmp -gt 0 ] &&
nb_tests=$((nb_tests + nb_tests_tmp))
# deal with the result of the test
if [ $exit_code -eq 0 ]; then
nb_success=$((nb_success + nb_tests_tmp))
echo "$test ran successfully"
elif [ $exit_code -eq 4 ]; then
# not executable (nextsync) found, not need to try other tests
exit 1
@ -41,4 +40,6 @@ for test in $TESTS; do
fi
done;
rm -rf /tmp/*_nextsync
echo -e "\nRan $nb_tests tests ($((nb_tests - nb_success)) Failed)"

38
tests/utils.sh Executable file
View File

@ -0,0 +1,38 @@
#!/bin/sh
mktd()
{
echo $(mktemp -d --suffix=_nextsync)
}
mktf()
{
echo $(mktemp --suffix=_nextsync)
}
get_exe() {
exe=$(pwd)
exe+="/../target/debug/nextsync"
if [ ! -f $exe ]; then
echo "No executable found, try to compile first" >&2
exit 4
fi
}
setup_env() {
[ ! -v exe ] && get_exe
path=$(mktd)
cd $path
}
# test_name expected_output
status_cmp() {
res=$($exe status --nostyle)
diff <(echo -e "$2" ) <(echo -e "$res") 2> /dev/null > /dev/null
if [ $? -ne 0 ]; then
echo -e "$TEST_SUITE_NAME$1: Output differ:" >&2
diff -u <(echo -e "$2" ) <(echo -e "$res") | grep "^[-\+\ ][^-\+]" >&2
echo -e "\nMore in $path" >&2
echo $nb_tests
exit 1
fi
}