Merge pull request #136 from matthiasbeyer/bin/imag-store/init

bin/imag-store/init
This commit is contained in:
Matthias Beyer 2016-02-09 20:24:40 +01:00
commit e9528fdd33
15 changed files with 1104 additions and 8 deletions

View file

@ -11,7 +11,7 @@ matrix:
before_install: before_install:
- | - |
c=$(git diff $TRAVIS_BRANCH..$TRAVIS_COMMIT --name-only | cut -d "/" -f 1 | uniq) c=$(git diff $(git merge-base master $TRAVIS_COMMIT)..$TRAVIS_COMMIT --name-only | cut -d "/" -f 1 | uniq)
if [[ "$c" == "doc" ]]; then if [[ "$c" == "doc" ]]; then
echo "Only changes in DOC, exiting 0" echo "Only changes in DOC, exiting 0"
exit 0 exit 0
@ -28,12 +28,13 @@ before_script:
script: script:
- | - |
changes_in() { changes_in() {
[[ $(git diff --name-only $TRAVIS_BRANCH..$TRAVIS_COMMIT | \ [[ $(git diff --name-only $(git merge-base master $TRAVIS_COMMIT)..$TRAVIS_COMMIT | \
cut -d "/" -f 1 | \ cut -d "/" -f 1 | \
grep "$n") ]] > /dev/null grep "$n") ]] > /dev/null
} }
travis_cargo_run_in() { travis_cargo_run_in() {
echo ":: Trying to run cargo in $1"
[[ -d "$1" ]] && [[ -d "$1" ]] &&
cd "$1" && cd "$1" &&
{ {
@ -45,14 +46,27 @@ script:
} || exit 1 } || exit 1
} }
run_sh_test() {
echo "-- Running test script: $1"
bash $1 || { echo "-- Test failed. Exiting"; exit 1; }
echo "-- Test script $1 executed successfully"
}
[[ $(changes_in "doc") ]] && echo "Changes in ./doc are not build by CI" [[ $(changes_in "doc") ]] && echo "Changes in ./doc are not build by CI"
for d in $(find -name "Cargo.toml" | grep -vE "^.$"); do for d in $(find -name "Cargo.toml" | grep -vE "^./Cargo.toml$"); do
echo ":: Working on $d"
dir=$(dirname $d) dir=$(dirname $d)
{ { \
changes_in $dir && \ changes_in $dir && \
echo -e "\nRunning in $d\n" && \ echo -e "\nRunning in $d\n" && \
travis_cargo_run_in $dir travis_cargo_run_in $dir && \
tree -I "*doc*" $dir && \
echo "-- Running test scripts..." && \
for testsh in $(find $dir -iname "*test.sh"); do
run_sh_test $testsh
done && \
echo "-- Done with test scripts..."
} || true } || true
done done
@ -62,6 +76,7 @@ addons:
- libcurl4-openssl-dev - libcurl4-openssl-dev
- libelf-dev - libelf-dev
- libdw-dev - libdw-dev
- tree
after_success: after_success:
- travis-cargo --only stable doc-upload - travis-cargo --only stable doc-upload

21
imag-store/Cargo.toml Normal file
View file

@ -0,0 +1,21 @@
[package]
name = "imag-store"
version = "0.1.0"
authors = ["Matthias Beyer <mail@beyermatthias.de>"]
[dependencies]
clap = "1.5.5"
log = "0.3.5"
version = "2.0.1"
semver = "0.2.1"
toml = "0.1.25"
[dependencies.libimagstore]
path = "../libimagstore"
[dependencies.libimagrt]
path = "../libimagrt"
[dependencies.libimagutil]
path = "../libimagutil"

149
imag-store/src/create.rs Normal file
View file

@ -0,0 +1,149 @@
use std::path::PathBuf;
use std::io::stdin;
use std::fs::OpenOptions;
use std::result::Result as RResult;
use std::io::Read;
use std::ops::DerefMut;
use std::io::Write;
use std::io::stderr;
use std::process::exit;
use clap::ArgMatches;
use libimagrt::runtime::Runtime;
use libimagstore::store::Entry;
use libimagstore::store::EntryHeader;
use error::StoreError;
use error::StoreErrorKind;
use util::build_entry_path;
use util::build_toml_header;
type Result<T> = RResult<T, StoreError>;
pub fn create(rt: &Runtime) {
rt.cli()
.subcommand_matches("create")
.map(|scmd| {
debug!("Found 'create' subcommand...");
// unwrap is safe as value is required
let path = scmd.value_of("path").or_else(|| scmd.value_of("id"));
if path.is_none() {
warn!("No ID / Path provided. Exiting now");
write!(stderr(), "No ID / Path provided. Exiting now");
exit(1);
}
let path = build_entry_path(rt, path.unwrap());
debug!("path = {:?}", path);
if scmd.subcommand_matches("entry").is_some() {
create_from_cli_spec(rt, scmd, &path)
.or_else(|_| create_from_source(rt, scmd, &path))
.or_else(|_| create_with_content_and_header(rt,
&path,
String::new(),
EntryHeader::new()))
} else {
create_with_content_and_header(rt, &path, String::new(), EntryHeader::new())
}
.unwrap_or_else(|e| debug!("Error building Entry: {:?}", e))
});
}
fn create_from_cli_spec(rt: &Runtime, matches: &ArgMatches, path: &PathBuf) -> Result<()> {
let content = matches.subcommand_matches("entry")
.map(|entry_subcommand| {
debug!("Found entry subcommand, parsing content");
entry_subcommand
.value_of("content")
.map(String::from)
.unwrap_or_else(|| {
entry_subcommand
.value_of("content-from")
.map(|src| string_from_raw_src(src))
.unwrap_or(String::new())
})
})
.unwrap_or_else(|| {
debug!("Didn't find entry subcommand, getting raw content");
matches.value_of("from-raw")
.map(|raw_src| string_from_raw_src(raw_src))
.unwrap_or(String::new())
});
debug!("Got content with len = {}", content.len());
let header = matches.subcommand_matches("entry")
.map(|entry_matches| build_toml_header(entry_matches, EntryHeader::new()))
.unwrap_or(EntryHeader::new());
create_with_content_and_header(rt, path, content, header)
}
fn create_from_source(rt: &Runtime, matches: &ArgMatches, path: &PathBuf) -> Result<()> {
let content = matches
.value_of("from-raw")
.ok_or(StoreError::new(StoreErrorKind::NoCommandlineCall, None))
.map(|raw_src| string_from_raw_src(raw_src));
if content.is_err() {
return content.map(|_| ());
}
let content = content.unwrap();
debug!("Content with len = {}", content.len());
Entry::from_str(path.clone(), &content[..])
.map(|mut new_e| {
rt.store()
.create(path.clone())
.map(|mut old_e| {
*old_e.deref_mut() = new_e;
});
debug!("Entry build");
})
.map_err(|serr| StoreError::new(StoreErrorKind::BackendError, Some(Box::new(serr))))
}
fn create_with_content_and_header(rt: &Runtime,
path: &PathBuf,
content: String,
header: EntryHeader) -> Result<()>
{
debug!("Creating entry with content");
rt.store()
.create(PathBuf::from(path))
.map(|mut element| {
{
let mut e_content = element.get_content_mut();
*e_content = content;
debug!("New content set");
}
{
let mut e_header = element.get_header_mut();
*e_header = header;
debug!("New header set");
}
})
.map_err(|e| StoreError::new(StoreErrorKind::BackendError, Some(Box::new(e))))
}
fn string_from_raw_src(raw_src: &str) -> String {
let mut content = String::new();
if raw_src == "-" {
debug!("Reading entry from stdin");
let res = stdin().read_to_string(&mut content);
debug!("Read {:?} bytes", res);
} else {
debug!("Reading entry from file at {:?}", raw_src);
OpenOptions::new()
.read(true)
.write(false)
.create(false)
.open(raw_src)
.and_then(|mut f| f.read_to_string(&mut content));
}
content
}

33
imag-store/src/delete.rs Normal file
View file

@ -0,0 +1,33 @@
use std::path::PathBuf;
use libimagrt::runtime::Runtime;
use util::build_entry_path;
pub fn delete(rt: &Runtime) {
use std::process::exit;
rt.cli()
.subcommand_matches("delete")
.map(|sub| {
sub.value_of("id")
.map(|id| {
debug!("Deleting file at {:?}", id);
rt.store()
.delete(build_entry_path(rt, id))
.map_err(|e| {
warn!("Error: {:?}", e);
exit(1);
})
})
.or_else(|| {
warn!("No ID passed. Will exit now");
exit(1);
})
})
.or_else(|| {
warn!("No subcommand 'delete'. Will exit now");
exit(1);
});
}

81
imag-store/src/error.rs Normal file
View file

@ -0,0 +1,81 @@
use std::error::Error;
use std::fmt::Error as FmtError;
use std::clone::Clone;
use std::fmt::{Display, Formatter};
/**
* Kind of store error
*/
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum StoreErrorKind {
BackendError,
NoCommandlineCall,
// maybe more
}
fn store_error_type_as_str(e: &StoreErrorKind) -> &'static str {
match e {
&StoreErrorKind::BackendError => "Backend Error",
&StoreErrorKind::NoCommandlineCall => "No commandline call",
}
}
impl Display for StoreErrorKind {
fn fmt(&self, fmt: &mut Formatter) -> Result<(), FmtError> {
try!(write!(fmt, "{}", store_error_type_as_str(self)));
Ok(())
}
}
#[derive(Debug)]
pub struct StoreError {
err_type: StoreErrorKind,
cause: Option<Box<Error>>,
}
impl StoreError {
/**
* Build a new StoreError from an StoreErrorKind, optionally with cause
*/
pub fn new(errtype: StoreErrorKind, cause: Option<Box<Error>>)
-> StoreError
{
StoreError {
err_type: errtype,
cause: cause,
}
}
/**
* Get the error type of this StoreError
*/
pub fn err_type(&self) -> StoreErrorKind {
self.err_type.clone()
}
}
impl Display for StoreError {
fn fmt(&self, fmt: &mut Formatter) -> Result<(), FmtError> {
try!(write!(fmt, "[{}]", store_error_type_as_str(&self.err_type.clone())));
Ok(())
}
}
impl Error for StoreError {
fn description(&self) -> &str {
store_error_type_as_str(&self.err_type.clone())
}
fn cause(&self) -> Option<&Error> {
self.cause.as_ref().map(|e| &**e)
}
}

72
imag-store/src/main.rs Normal file
View file

@ -0,0 +1,72 @@
extern crate clap;
#[macro_use] extern crate log;
extern crate semver;
extern crate toml;
#[macro_use] extern crate version;
extern crate libimagrt;
extern crate libimagstore;
extern crate libimagutil;
use libimagrt::runtime::Runtime;
use std::process::exit;
mod error;
mod ui;
mod create;
mod retrieve;
mod update;
mod delete;
mod util;
use ui::build_ui;
use create::create;
use retrieve::retrieve;
use update::update;
use delete::delete;
fn main() {
let name = "imag-store";
let version = &version!()[..];
let about = "Direct interface to the store. Use with great care!";
let ui = build_ui(Runtime::get_default_cli_builder(name, version, about));
let rt = {
let rt = Runtime::new(ui);
if rt.is_ok() {
rt.unwrap()
} else {
println!("Could not set up Runtime");
println!("{:?}", rt.err().unwrap());
exit(1);
}
};
rt.init_logger();
debug!("Hello. Logging was just enabled");
debug!("I already set up the Runtime object and build the commandline interface parser.");
debug!("Lets get rollin' ...");
rt.cli()
.subcommand_name()
.map_or_else(
|| {
debug!("No command");
// More error handling
},
|name| {
debug!("Call: {}", name);
match name {
"create" => create(&rt),
"retrieve" => retrieve(&rt),
"update" => update(&rt),
"delete" => delete(&rt),
_ => {
debug!("Unknown command");
// More error handling
},
};
}
)
}

View file

@ -0,0 +1,84 @@
use std::path::PathBuf;
use std::ops::Deref;
use std::fmt::Display;
use clap::ArgMatches;
use toml::Value;
use libimagstore::store::FileLockEntry;
use libimagrt::runtime::Runtime;
use util::build_entry_path;
pub fn retrieve(rt: &Runtime) {
rt.cli()
.subcommand_matches("retrieve")
.map(|scmd| {
let path = scmd.value_of("id").map(|id| build_entry_path(rt, id)).unwrap();
debug!("path = {:?}", path);
rt.store()
// "id" must be present, enforced via clap spec
.retrieve(path)
.map(|e| print_entry(rt, scmd, e))
.map_err(|e| {
debug!("No entry.");
debug!("{}", e);
})
});
}
fn print_entry(rt: &Runtime, scmd: &ArgMatches, e: FileLockEntry) {
if do_print_raw(scmd) {
debug!("Printing raw content...");
println!("{}", e.deref().to_str());
} else if do_filter(scmd) {
debug!("Filtering...");
warn!("Filtering via header specs is currently now supported.");
warn!("Will fail now!");
unimplemented!()
} else {
debug!("Printing structured...");
let entry = e.deref();
if do_print_header(scmd) {
debug!("Printing header...");
if do_print_header_as_json(rt.cli()) {
debug!("Printing header as json...");
warn!("Printing as JSON currently not supported.");
warn!("Will fail now!");
unimplemented!()
} else {
debug!("Printing header as TOML...");
// We have to Value::Table() for Display
println!("{}", Value::Table(entry.get_header().toml().clone()))
}
}
if do_print_content(scmd) {
debug!("Printing content...");
println!("{}", entry.get_content());
}
}
}
fn do_print_header(m: &ArgMatches) -> bool {
m.is_present("header")
}
fn do_print_header_as_json(m: &ArgMatches) -> bool {
m.is_present("header-json")
}
fn do_print_content(m: &ArgMatches) -> bool {
m.is_present("content")
}
fn do_print_raw(m: &ArgMatches) -> bool {
m.is_present("raw")
}
fn do_filter(m: &ArgMatches) -> bool {
m.subcommand_matches("filter-header").is_some()
}

120
imag-store/src/ui.rs Normal file
View file

@ -0,0 +1,120 @@
use clap::{Arg, App, SubCommand};
pub fn build_ui<'a>(app: App<'a, 'a, 'a, 'a, 'a, 'a>) -> App<'a, 'a, 'a, 'a, 'a, 'a> {
app.subcommand(SubCommand::with_name("create")
.about("Create an entry from the store")
.version("0.1")
.arg(Arg::with_name("path")
.long("path")
.short("p")
.takes_value(true)
.required(false)
.help("Create at this store path"))
.arg(Arg::with_name("id")
.long("id")
.short("i")
.takes_value(true)
.required(false)
.help("Same as --path, for consistency"))
.arg(Arg::with_name("from-raw")
.long("from-raw")
.takes_value(true)
.help("Create a new entry by reading this file ('-' for stdin)"))
.subcommand(SubCommand::with_name("entry")
.about("Create an entry via commandline")
.version("0.1")
.arg(Arg::with_name("content")
.long("content")
.short("c")
.takes_value(true)
.help("Content for the Entry from commandline"))
.arg(Arg::with_name("content-from")
.long("content-from")
.short("f")
.takes_value(true)
.help("Content for the Entry from this file ('-' for stdin)"))
.arg(Arg::with_name("header")
.long("header")
.short("h")
.takes_value(true)
.multiple(true)
.help("Set a header field. Specify as 'header.field.value=value', multiple allowed"))
)
)
.subcommand(SubCommand::with_name("retrieve")
.about("Get an entry from the store")
.version("0.1")
.arg(Arg::with_name("id")
.long("id")
.short("i")
.takes_value(true)
.required(true)
.help("Retreive by Store Path, where root (/) is the store itself"))
.arg(Arg::with_name("content")
.long("content")
.short("c")
.help("Print content"))
.arg(Arg::with_name("header")
.long("header")
.short("h")
.help("Print header"))
.arg(Arg::with_name("header-json")
.long("header-json")
.short("j")
.help("Print header as json"))
.arg(Arg::with_name("raw")
.long("raw")
.short("r")
.help("Print Entries as they are in the store"))
.subcommand(SubCommand::with_name("filter-header")
.about("Retrieve Entries by filtering")
.version("0.1")
.arg(Arg::with_name("header-field-where")
.long("where")
.short("w")
.takes_value(true)
.help("Filter with 'header.field=foo' where the header field 'header.field' equals 'foo'")
)
.arg(Arg::with_name("header-field-grep")
.long("grep")
.short("g")
.takes_value(true)
.help("Filter with 'header.field=[a-zA-Z0-9]*' where the header field 'header.field' matches '[a-zA-Z0-9]*'"))
)
)
.subcommand(SubCommand::with_name("update")
.about("Get an entry from the store")
.version("0.1")
.arg(Arg::with_name("id")
.long("id")
.short("i")
.takes_value(true)
.required(true)
.help("Update Store Entry with this path. Root (/) is the store itself"))
.arg(Arg::with_name("content")
.long("content")
.short("c")
.takes_value(true)
.help("Take the content for the new Entry from this file ('-' for stdin)"))
.arg(Arg::with_name("header")
.long("header")
.short("h")
.takes_value(true)
.multiple(true)
.help("Set a header field. Specify as 'header.field.value=value', multiple allowed"))
)
.subcommand(SubCommand::with_name("delete")
.about("Delete an entry from the store")
.version("0.1")
.arg(Arg::with_name("id")
.long("id")
.short("i")
.takes_value(true)
.required(true)
.help("Remove Store Entry with this path. Root (/) is the store itself"))
)
}

29
imag-store/src/update.rs Normal file
View file

@ -0,0 +1,29 @@
use std::path::PathBuf;
use std::ops::DerefMut;
use libimagrt::runtime::Runtime;
use util::build_toml_header;
use util::build_entry_path;
pub fn update(rt: &Runtime) {
rt.cli()
.subcommand_matches("update")
.map(|scmd| {
rt.store()
.retrieve(scmd.value_of("id").map(|id| build_entry_path(rt, id)).unwrap())
.map(|mut locked_e| {
let mut e = locked_e.deref_mut();
scmd.value_of("content")
.map(|new_content| {
*e.get_content_mut() = String::from(new_content);
debug!("New content set");
});
*e.get_header_mut() = build_toml_header(scmd, e.get_header().clone());
debug!("New header set");
})
});
}

132
imag-store/src/util.rs Normal file
View file

@ -0,0 +1,132 @@
use std::collections::BTreeMap;
use std::path::PathBuf;
use std::str::Split;
use clap::ArgMatches;
use semver::Version;
use toml::Value;
use libimagstore::store::EntryHeader;
use libimagrt::runtime::Runtime;
use libimagutil::key_value_split::IntoKeyValue;
pub fn build_entry_path(rt: &Runtime, path_elem: &str) -> PathBuf {
debug!("Checking path element for version");
{
let contains_version = {
path_elem.split("~")
.last()
.map(|version| Version::parse(version).is_ok())
.unwrap_or(false)
};
if !contains_version {
debug!("Version cannot be parsed inside {:?}", path_elem);
warn!("Path does not contain version. Will panic now!");
panic!("No version in path");
}
}
debug!("Version checking succeeded");
debug!("Building path from {:?}", path_elem);
let mut path = rt.store().path().clone();
if path_elem.chars().next() == Some('/') {
path.push(&path_elem[1..path_elem.len()]);
} else {
path.push(path_elem);
}
path
}
pub fn build_toml_header(matches: &ArgMatches, mut header: EntryHeader) -> EntryHeader {
debug!("Building header from cli spec");
if let Some(headerspecs) = matches.values_of("header") {
let mut main = header.toml_mut();
debug!("headerspec = {:?}", headerspecs);
let kvs = headerspecs.into_iter()
.filter_map(|hs| {
debug!("- Processing: '{}'", hs);
let kv = String::from(hs).into_kv();
debug!("- got: '{:?}'", kv);
kv
});
for tpl in kvs {
let (key, value) = tpl.into();
debug!("Splitting: {:?}", key);
let mut split = key.split(".");
let current = split.next();
if current.is_some() {
insert_key_into(String::from(current.unwrap()), &mut split, value, &mut main);
}
}
}
debug!("Header = {:?}", header);
header
}
fn insert_key_into(current: String,
rest_path: &mut Split<&str>,
value: String,
map: &mut BTreeMap<String, Value>) {
let next = rest_path.next();
if next.is_none() {
debug!("Inserting into {:?} = {:?}", current, value);
map.insert(current, parse_value(value));
} else {
debug!("Inserting into {:?} ... = {:?}", current, value);
if map.contains_key(&current) {
match map.get_mut(&current).unwrap() {
&mut Value::Table(ref mut t) => {
insert_key_into(String::from(next.unwrap()), rest_path, value, t);
},
_ => unreachable!(),
}
} else {
let mut submap = BTreeMap::new();
insert_key_into(String::from(next.unwrap()), rest_path, value, &mut submap);
debug!("Inserting submap = {:?}", submap);
map.insert(current, Value::Table(submap));
}
}
}
fn parse_value(value: String) -> Value {
use std::str::FromStr;
fn is_ary(v: &String) -> bool {
v.chars().next() == Some('[') && v.chars().last() == Some(']') && v.len() >= 3
}
if value == "true" {
debug!("Building Boolean out of: {:?}...", value);
Value::Boolean(true)
} else if value == "false" {
debug!("Building Boolean out of: {:?}...", value);
Value::Boolean(false)
} else if is_ary(&value) {
debug!("Building Array out of: {:?}...", value);
let sub = &value[1..(value.len()-1)];
Value::Array(sub.split(",").map(|v| parse_value(String::from(v))).collect())
} else {
FromStr::from_str(&value[..])
.map(|i: i64| {
debug!("Building Integer out of: {:?}...", value);
Value::Integer(i)
})
.unwrap_or_else(|_| {
FromStr::from_str(&value[..])
.map(|f: f64| {
debug!("Building Float out of: {:?}...", value);
Value::Float(f)
})
.unwrap_or_else(|_| {
debug!("Building String out of: {:?}...", value);
Value::String(value)
})
})
}
}

View file

@ -0,0 +1,174 @@
#!/usr/bin/env bash
source $(dirname ${BASH_SOURCE[0]})/utils.sh
test_call() {
imag-store create -p /test-call~0.1.0
if [[ ! $? -eq 0 ]]; then
err "Return value should be zero, was non-zero"
return 1;
fi
}
test_call_id() {
imag-store create -i /test-call~0.1.0
if [[ ! $? -eq 0 ]]; then
err "Return value should be zero, was non-zero"
return 1;
fi
}
test_call_no_id() {
imag-store create
if [[ ! $? -eq 1 ]]; then
err "Return value should be zero, was non-zero"
return 1;
fi
}
test_mkstore() {
imag-store create -p /test-mkstore~0.1.0 || { err "Calling imag failed"; return 1; }
if [[ -d ${STORE} ]]; then
out "Store exists."
else
err "No store created"
return 1
fi
}
test_std_header() {
local expected=$(cat <<EOS
---
[imag]
links = []
version = "0.1.0"
---
EOS
)
imag-store create -p /test-std-header~0.1.0
local result=$(cat ${STORE}/test-std-header~0.1.0)
if [[ "$expected" == "$result" ]]; then
out "Expected store entry == result"
else
err "${STORE}/test-std-header~0.1.0 differs from expected"
return 1
fi
}
test_std_header_plus_custom() {
local expected=$(cat <<EOS
---
[imag]
links = []
version = "0.1.0"
[zzz]
zzz = "z"
---
EOS
)
imag-store create -p /test-std-header-plus-custom~0.1.0 entry -h zzz.zzz=z
local result=$(cat ${STORE}/test-std-header-plus-custom~0.1.0)
if [[ "$expected" == "$result" ]]; then
out "Expected store entry == result"
else
err "${STORE}/test differs from expected"
return 1
fi
}
test_std_header_plus_custom_multiheader() {
local expected=$(cat <<EOS
---
[foo]
bar = "baz"
[imag]
links = []
version = "0.1.0"
[zzz]
zzz = "z"
---
EOS
)
local filename="test-std-header-plus-custom-multiheader~0.1.0"
imag-store create -p /$filename entry -h zzz.zzz=z foo.bar=baz
local result=$(cat ${STORE}/$filename)
if [[ "$expected" == "$result" ]]; then
out "Expected store entry == result"
else
err "${STORE}/$filename differs from expected"
return 1
fi
}
test_std_header_plus_custom_multiheader_same_section() {
local expected=$(cat <<EOS
---
[imag]
links = []
version = "0.1.0"
[zzz]
bar = "baz"
zzz = "z"
---
EOS
)
local filename="test-std-header-plus-custom-mutliheader-same-section~0.1.0"
imag-store create -p /$filename entry -h zzz.zzz=z zzz.bar=baz
local result=$(cat ${STORE}/$filename)
if [[ "$expected" == "$result" ]]; then
out "Expected store entry == result"
else
err "${STORE}/$filename differs from expected"
return 1
fi
}
test_std_header_plus_custom_and_content() {
local expected=$(cat <<EOS
---
[imag]
links = []
version = "0.1.0"
[zzz]
zzz = "z"
---
content
EOS
)
local name="test-std-header-plus-custom-and-content~0.1.0"
imag-store create -p /$name entry -h zzz.zzz=z -c content
local result=$(cat ${STORE}/$name)
if [[ "$expected" == "$result" ]]; then
out "Expected store entry == result"
else
err "${STORE}/test differs from expected"
return 1
fi
}
invoke_tests \
test_call \
test_call_id \
test_call_no_id \
test_mkstore \
test_std_header \
test_std_header_plus_custom \
test_std_header_plus_custom_multiheader \
test_std_header_plus_custom_multiheader_same_section \
test_std_header_plus_custom_and_content

View file

@ -0,0 +1,84 @@
#!/usr/bin/env bash
source $(dirname ${BASH_SOURCE[0]})/utils.sh
std_header() {
cat <<EOS
---
[imag]
links = []
version = "0.1.0"
---
EOS
}
retrieve() {
silent imag-store retrieve $*
}
test_retrieve_nothing() {
local id="test-retrieve_nothing~0.1.0"
imag-store create -p /${id} || { err "create failed"; return 1; }
out "Going to test the retrieve functionality now"
local zero_out="$(retrieve --id /${id})"
out "Retrieving for zero_out finished"
if [[ ! -z "$zero_out" ]]; then
err "Expected zero output, got '$zero_out'"
return 1
fi
}
test_retrieve_content() {
local id="test-retrieve_simple~0.1.0"
imag-store create -p /${id} || { err "create failed"; return 1; }
out "Going to test the retrieve functionality now"
local content_out="$(retrieve --id /${id} --content)"
out "Retrieving for content_out finished"
if [[ ! -z "$content_out" ]]; then
err "Expected content output == zero output, got '$content_out'"
return 1
fi
}
test_retrieve_header() {
local id="test-retrieve_simple~0.1.0"
imag-store create -p /${id} || { err "create failed"; return 1; }
out "Going to test the retrieve functionality now"
local header_out="$(retrieve --id /${id} --header)"
out "Retrieving for header_out finished"
if [[ ! "$header_out" != "$(std_header)" ]]; then
err "Expected header as output, got '$header_out'"
return 1
fi
}
test_retrieve_raw() {
local id="test-retrieve_simple~0.1.0"
imag-store create -p /${id} || { err "create failed"; return 1; }
out "Going to test the retrieve functionality now"
local both_out="$(retrieve --id /${id} --raw)"
out "Retrieving for both_out finished"
if [[ "$both_out" != "$(std_header)" ]]; then
err "Expected "$(std_header)" as output, got '$both_out'"
return 1
fi
}
invoke_tests \
test_retrieve_nothing \
test_retrieve_content \
test_retrieve_header \
test_retrieve_raw

View file

@ -0,0 +1,29 @@
#!/usr/bin/env bash
source $(dirname ${BASH_SOURCE[0]})/utils.sh
create() {
imag-store create $*
}
delete() {
imag-store delete $*
}
test_delete_simple() {
local name="test~0.1.0"
create -p /$name
delete --id /$name
local n=$($(find ${STORE}/ -type f | wc -l))
if [[ $n -eq 0 ]]; then
success "Deleting worked"
else
err "There are still $n files in the store"
fi
}
invoke_tests \
test_delete_simple

65
imag-store/tests/utils.sh Normal file
View file

@ -0,0 +1,65 @@
#!/usr/bin/env bash
COLOR_OFF='\e[0m' # Text Reset
RED='\e[0;31m' # Red
YELLOW='\e[0;33m' # Yellow
GREEN='\e[0;32m' # Green
RUNTIME="/tmp"
STORE="${RUNTIME}/store"
out() {
[[ -z "$DEBUG_OUTPUT_OFF" ]] && echo -e "${YELLOW}:: $*${COLOR_OFF}"
}
success() {
[[ -z "$DEBUG_OUTPUT_OFF" ]] && echo -e "${GREEN}>> $*${COLOR_OFF}"
}
err() {
[[ -z "$DEBUG_OUTPUT_OFF" ]] && echo -e "${RED}!! $*${COLOR_OFF}"
}
silent() {
DEBUG_OUTPUT_OFF=1 $*
}
imag-store() {
local searchdir=$(dirname ${BASH_SOURCE[0]})/../target/debug/
[[ -d $searchdir ]] || { err "FATAL: No directory $searchdir"; exit 1; }
local bin=$(find $searchdir -iname imag-store -type f -executable)
local flags="--debug --rtp $RUNTIME"
out "Calling '$bin $flags $*'"
$bin $flags $*
}
reset_store() {
rm -r "${STORE}"
}
call_test() {
out "-- TESTING: '$1' --"
$1
result=$?
if [[ -z "$DONT_RESET_STORE" ]]; then
out "Reseting store"
reset_store
out "Store reset done"
fi
[[ $result -eq 0 ]] || { err "-- FAILED: '$1'. Exiting."; exit 1; }
success "-- SUCCESS: '$1' --"
}
invoke_tests() {
out "Invoking tests."
if [[ ! -z "$INVOKE_TEST" ]]; then
out "Invoking only $INVOKE_TEST"
call_test "$INVOKE_TEST"
else
out "Invoking $*"
for t in $*; do
call_test "$t"
done
fi
}

View file

@ -130,6 +130,7 @@ impl Store {
/// Creates the Entry at the given location (inside the entry) /// Creates the Entry at the given location (inside the entry)
pub fn create<'a>(&'a self, id: StoreId) -> Result<FileLockEntry<'a>> { pub fn create<'a>(&'a self, id: StoreId) -> Result<FileLockEntry<'a>> {
if !self.id_in_store(&id) { if !self.id_in_store(&id) {
debug!("'{:?}' seems not to be in '{:?}'", id, self.location);
return Err(StoreError::new(StoreErrorKind::StorePathOutsideStore, None)); return Err(StoreError::new(StoreErrorKind::StorePathOutsideStore, None));
} }
@ -153,6 +154,7 @@ impl Store {
/// dropped, the new Entry is written to disk /// dropped, the new Entry is written to disk
pub fn retrieve<'a>(&'a self, id: StoreId) -> Result<FileLockEntry<'a>> { pub fn retrieve<'a>(&'a self, id: StoreId) -> Result<FileLockEntry<'a>> {
if !self.id_in_store(&id) { if !self.id_in_store(&id) {
debug!("'{:?}' seems not to be in '{:?}'", id, self.location);
return Err(StoreError::new(StoreErrorKind::StorePathOutsideStore, None)); return Err(StoreError::new(StoreErrorKind::StorePathOutsideStore, None));
} }
@ -213,6 +215,7 @@ impl Store {
/// Delete an entry /// Delete an entry
pub fn delete(&self, id: StoreId) -> Result<()> { pub fn delete(&self, id: StoreId) -> Result<()> {
if !self.id_in_store(&id) { if !self.id_in_store(&id) {
debug!("'{:?}' seems not to be in '{:?}'", id, self.location);
return Err(StoreError::new(StoreErrorKind::StorePathOutsideStore, None)); return Err(StoreError::new(StoreErrorKind::StorePathOutsideStore, None));
} }
@ -238,9 +241,14 @@ impl Store {
.map(|can| { .map(|can| {
can.starts_with(&self.location) can.starts_with(&self.location)
}) })
.unwrap_or(false) .unwrap_or(path.starts_with(&self.location))
// we return false, as fs::canonicalize() returns an Err(..) on filesystem errors // we return false, as fs::canonicalize() returns an Err(..) on filesystem errors
} }
/// Gets the path where this store is on the disk
pub fn path(&self) -> &PathBuf {
&self.location
}
} }
impl Drop for Store { impl Drop for Store {