diff --git a/README.md b/README.md
index a492cbf..c52057f 100644
--- a/README.md
+++ b/README.md
@@ -52,10 +52,11 @@ JuJu can only works on GNU/Linux OS with kernel version greater or equal
Advanced usage
--------------
+### Build image ###
You can build a new JuJu image from scratch by running the following command:
-
+```
# juju -b
-
+```
In this way the script will create a directory containing all the essentials
files in order to make JuJu working properly (such as pacman, yaourt, arch-chroot and proot).
Remember that the script to build the image must run in an ArchLinux OS with
@@ -66,6 +67,17 @@ After creating the image juju-x86\_64.tar.gz you can install it by running:
# juju -i juju-x86_64.tar.gz
+### Bind directories ###
+To bind and host directory to a guest location, you can use proot arguments:
+```
+ $ juju -p "-b /mnt/mydata:/home/user/mydata"
+```
+
+Check out the proot options with:
+```
+ $ juju -p "--help"
+```
+
Dependencies
------------
JuJu comes with a very short list of dependencies in order to be installed in most
diff --git a/bin/juju b/bin/juju
index 6f5e68c..83eef6a 100755
--- a/bin/juju
+++ b/bin/juju
@@ -18,8 +18,14 @@
# along with this program. If not, see .
#
-NAME='juju'
-VERSION='1.0'
+NAME='JuJu'
+CMD='juju'
+VERSION='2.5.6'
+CODE_NAME='Lion'
+DESCRIPTION='The GNU/Linux distribution container for non-root users'
+AUTHOR='Filippo Squillace '
+HOMEPAGE='https://github.com/fsquillace/juju'
+COPYRIGHT='2012-2014'
source "$(dirname $0)/../lib/core.sh"
@@ -28,22 +34,23 @@ source "$(dirname $0)/../lib/core.sh"
###################################
usage() {
- echo -e "JuJu: The portable GNU/Linux distribution"
- echo -e "Usage: $NAME [options]"
+ echo -e "$NAME: $DESCRIPTION"
+ echo -e "Usage: $CMD [options] [command]"
echo -e "Options:"
- echo -e "-i, --setup-from-file Setup the JuJu image in ${JUJU_HOME}"
- echo -e "-f, --fakeroot Run JuJu with fakeroot privileges"
- echo -e "-r, --root Run JuJu with root privileges"
- echo -e "-b, --build-image Build a JuJu image (must run in ArchLinux)"
- echo -e "-d, --delete Delete JuJu from ${JUJU_HOME}"
+ echo -e "-i, --setup-from-file Setup the $NAME image in ${JUJU_HOME}"
+ echo -e "-f, --fakeroot Run $NAME with fakeroot privileges"
+ echo -e "-r, --root Run $NAME with root privileges"
+ echo -e "-p, --proot-args Proot arguments"
+ echo -e "-b, --build-image Build a $NAME image (must run in ArchLinux)"
+ echo -e "-d, --delete Delete $NAME from ${JUJU_HOME}"
echo -e "-h, --help Show this help message"
echo -e "-v, --version Show the $NAME version"
}
version() {
- echo -e "JuJu ($VERSION): The portable GNU/Linux distribution"
- echo -e "Copyright (c) 2012-2014 Filippo Squillace "
- echo -e "Homepage: http://github.com/fsquillace/juju"
+ echo -e "$NAME $VERSION ($CODE_NAME): $DESCRIPTION"
+ echo -e "Copyright (c) $COPYRIGHT $AUTHOR"
+ echo -e "Homepage: $HOMEPAGE"
}
check_cli(){
@@ -60,7 +67,7 @@ check_cli(){
if $OPT_BUILD_IMAGE || $OPT_HELP || $OPT_VERSION || $OPT_SETUP_FROM_FILE || \
$OPT_FAKEROOT || $OPT_ROOT
then
- die "The JuJu delete option must be used exclusively"
+ die "The $NAME delete option must be used exclusively"
fi
fi
if $OPT_HELP
@@ -68,7 +75,7 @@ check_cli(){
if $OPT_BUILD_IMAGE || $OPT_DELETE || $OPT_VERSION || $OPT_SETUP_FROM_FILE || \
$OPT_FAKEROOT || $OPT_ROOT
then
- die "The JuJu help option must be used exclusively"
+ die "The $NAME help option must be used exclusively"
fi
fi
if $OPT_VERSION
@@ -76,91 +83,102 @@ check_cli(){
if $OPT_BUILD_IMAGE || $OPT_DELETE || $OPT_HELP || $OPT_SETUP_FROM_FILE || \
$OPT_FAKEROOT || $OPT_ROOT
then
- die "The JuJu version option must be used exclusively"
+ die "The $NAME version option must be used exclusively"
fi
fi
-
if $OPT_FAKEROOT && $OPT_ROOT
then
- die "You must access to JuJu with either fakeroot or root permissions"
+ die "You must access to $NAME with either fakeroot or root permissions"
+ fi
+ if $OPT_PROOT_ARGS
+ then
+ if $OPT_BUILD_IMAGE || $OPT_DELETE || $OPT_HELP || $OPT_SETUP_FROM_FILE || \
+ $OPT_ROOT || $OPT_VERSION
+ then
+ die "Invalid syntax: Proot args are not allowed with the other options"
+ fi
+ fi
+ if [ "$ARGS" != "" ]
+ then
+ if $OPT_BUILD_IMAGE || $OPT_DELETE || $OPT_HELP || $OPT_SETUP_FROM_FILE || \
+ $OPT_VERSION
+ then
+ die "No arguments are needed. For the CLI syntax run: $CMD --help"
+ fi
fi
-
- [ "$ARGS" != "" ] && die "No arguments are needed. For the CLI syntax run: $NAME --help"
return 0
}
-###################################
-### MAIN PROGRAM ###
-###################################
+function parse_arguments(){
+ TEMP=$(getopt -o drp:fbi:hv --long delete,root,proot-args:,fakeroot,build-image,setup-from-file:,help,version -n "$CMD" -- "$@")
+ eval set -- "$TEMP"
-TEMP=`getopt -o drfbi:hv --long delete,root,fakeroot,build-image,setup-from-file:,help,version -n 'juju' -- "$@"`
+ OPT_SETUP_FROM_FILE=false
+ IMAGE_FILE=""
+ OPT_FAKEROOT=false
+ OPT_ROOT=false
+ OPT_PROOT_ARGS=false
+ PROOT_ARGS=""
+ OPT_BUILD_IMAGE=false
+ OPT_DELETE=false
+ OPT_HELP=false
+ OPT_VERSION=false
+ while true ; do
+ case "$1" in
+ -i|--setup-from-file) OPT_SETUP_FROM_FILE=true ; shift ; IMAGE_FILE=$1 ; shift ;;
+ -f|--fakeroot) OPT_FAKEROOT=true ; shift ;;
+ -r|--root) OPT_ROOT=true ; shift ;;
+ -p|--proot-args) OPT_PROOT_ARGS=true ; shift ; PROOT_ARGS=$1; shift ;;
+ -b|--build-image) OPT_BUILD_IMAGE=true ; shift ;;
+ -d|--delete) OPT_DELETE=true ; shift ;;
+ -h|--help) OPT_HELP=true ; shift ;;
+ -v|--version) OPT_VERSION=true ; shift ;;
+ --) shift ; break ;;
+ *) die "Internal error!" ;;
+ esac
+ done
-if [ $? != 0 ] ; then error "Error on parsing the command line. Try juju -h." ; exit ; fi
+ ARGS=()
+ for arg do
+ ARGS+=($arg)
+ done
+}
-# Note the quotes around `$TEMP': they are essential!
-eval set -- "$TEMP"
+function execute_operation(){
+ $OPT_HELP && usage && return
+ $OPT_VERSION && version && return
-OPT_SETUP_FROM_FILE=false
-IMAGE_FILE=""
-OPT_FAKEROOT=false
-OPT_ROOT=false
-OPT_BUILD_IMAGE=false
-OPT_DELETE=false
-OPT_HELP=false
-OPT_VERSION=false
-while true ; do
- case "$1" in
- -i|--setup-from-file) OPT_SETUP_FROM_FILE=true ; shift ; IMAGE_FILE=$1 ; shift ;;
- -f|--fakeroot) OPT_FAKEROOT=true ; shift ;;
- -r|--root) OPT_ROOT=true ; shift ;;
- -b|--build-image) OPT_BUILD_IMAGE=true ; shift ;;
- -d|--delete) OPT_DELETE=true ; shift ;;
- -h|--help) OPT_HELP=true ; shift ;;
- -v|--version) OPT_VERSION=true ; shift ;;
- --) shift ; break ;;
- *) error "Internal error!" ; exit 1 ;;
- esac
-done
+ if $OPT_BUILD_IMAGE; then
+ build_image_juju
+ return
+ elif $OPT_DELETE; then
+ delete_juju
+ return
+ fi
-ARGS=()
-for arg do
- ARGS+=($arg)
-done
+ if ! is_juju_installed
+ then
+ if $OPT_SETUP_FROM_FILE; then
+ setup_from_file_juju $IMAGE_FILE
+ else
+ setup_juju
+ fi
+ elif $OPT_SETUP_FROM_FILE; then
+ die "Error: The image cannot be installed since $JUJU_HOME is not empty."
+ fi
-check_cli || exit 1
-
-################ DEFINE ACTION ########################
-
-$OPT_HELP && usage && exit
-$OPT_VERSION && version && exit
-
-if $OPT_BUILD_IMAGE; then
- build_image_juju
- exit
-elif $OPT_DELETE; then
- delete_juju
- exit
-fi
-
-if ! is_juju_installed
-then
- if $OPT_SETUP_FROM_FILE; then
- setup_from_file_juju $IMAGE_FILE
- else
- setup_juju
- fi
-elif $OPT_SETUP_FROM_FILE; then
- die "Error: The image cannot be installed since $JUJU_HOME is not empty."
-fi
-
-if $OPT_FAKEROOT; then
- run_juju_as_fakeroot
-elif $OPT_ROOT; then
- run_juju_as_root
-else
- run_juju_as_user
-fi
+ if $OPT_FAKEROOT; then
+ run_juju_as_fakeroot "${PROOT_ARGS}" ${ARGS[@]}
+ elif $OPT_ROOT; then
+ run_juju_as_root ${ARGS[@]}
+ else
+ run_juju_as_user "${PROOT_ARGS}" ${ARGS[@]}
+ fi
+}
+parse_arguments $@
+check_cli
+execute_operation
# vim: set ts=4 sw=4 noet:
diff --git a/lib/core.sh b/lib/core.sh
index 925696d..11a1f69 100644
--- a/lib/core.sh
+++ b/lib/core.sh
@@ -70,11 +70,15 @@ then
SH="/bin/sh --login"
elif [ "$JUJU_ENV" == "1" ]
then
- PROOT="$LD_LIB"
- SH="/bin/sh"
+ die "Error: Nested JuJu environments are not allowed"
else
die "The variable JUJU_ENV is not properly set"
fi
+
+CHROOT=${JUJU_HOME}/usr/bin/arch-chroot
+TRUE=${JUJU_HOME}/usr/bin/true
+ID="${JUJU_HOME}/usr/bin/id -u"
+
################################# MAIN FUNCTIONS ##############################
function is_juju_installed(){
@@ -83,7 +87,7 @@ function is_juju_installed(){
}
-function cleanup_build_directory(){
+function _cleanup_build_directory(){
# $1: maindir (optional) - str: build directory to get rid
local maindir=$1
builtin cd $ORIGIN_WD
@@ -92,7 +96,7 @@ function cleanup_build_directory(){
}
-function prepare_build_directory(){
+function _prepare_build_directory(){
trap - QUIT EXIT ABRT KILL TERM INT
trap "rm -rf ${maindir}; die \"Error occurred when installing JuJu\"" EXIT QUIT ABRT KILL TERM INT
}
@@ -110,10 +114,9 @@ function _setup_juju(){
function setup_juju(){
# Setup the JuJu environment
- [ "$JUJU_ENV" == "1" ] && die "Error: The operation is not allowed inside JuJu environment"
local maindir=$(TMPDIR=$JUJU_TEMPDIR mktemp -d -t juju.XXXXXXXXXX)
- prepare_build_directory
+ _prepare_build_directory
info "Downloading JuJu..."
builtin cd ${maindir}
@@ -123,13 +126,12 @@ function setup_juju(){
info "Installing JuJu..."
_setup_juju ${maindir}/${imagefile}
- cleanup_build_directory ${maindir}
+ _cleanup_build_directory ${maindir}
}
function setup_from_file_juju(){
# Setup from file the JuJu environment
- [ "$JUJU_ENV" == "1" ] && die "Error: The operation is not allowed inside JuJu environment"
local imagefile=$1
[ ! -e ${imagefile} ] && die "Error: The JuJu image file ${imagefile} does not exist"
@@ -141,37 +143,54 @@ function setup_from_file_juju(){
}
-function run_juju_as_root(){
- [ "$JUJU_ENV" == "1" ] && die "Error: The operation is not allowed inside JuJu environment"
+function _define_chroot_args(){
+ local comm=${SH}
+ [ "$1" != "" ] && comm="$@"
+ echo $comm
+}
+
+function _define_proot_args(){
+ local proot_args="$1"
+ shift
+ local comm=$(_define_chroot_args "$@")
+ echo "$proot_args" "${comm[@]}"
+}
+
+
+function run_juju_as_root(){
mkdir -p ${JUJU_HOME}/${HOME}
- ${JUJU_HOME}/usr/bin/arch-chroot $JUJU_HOME /usr/bin/bash -c 'mkdir -p /run/lock && /bin/sh'
+ JUJU_ENV=1 ${CHROOT} $JUJU_HOME /usr/bin/bash -c "mkdir -p /run/lock && $(_define_chroot_args "$@")"
}
function _run_juju_with_proot(){
- if ${PROOT} ${JUJU_HOME}/usr/bin/true &> /dev/null
- then
- JUJU_ENV=1 ${PROOT} $@ ${JUJU_HOME} ${SH}
- else
- JUJU_ENV=1 PROOT_NO_SECCOMP=1 ${PROOT} $@ ${JUJU_HOME} ${SH}
- fi
+ ${PROOT} ${TRUE} &> /dev/null || export PROOT_NO_SECCOMP=1
+
+ [ "$(${PROOT} ${ID} 2> /dev/null )" == "0" ] && \
+ die "You cannot access with root privileges. Use --root option instead."
+
+ JUJU_ENV=1 ${PROOT} $@
+ local ret=$?
+ export -n PROOT_NO_SECCOMP
+
+ return $ret
}
function run_juju_as_fakeroot(){
- _run_juju_with_proot "-S"
+ local comm=$(_define_proot_args "$@")
+ _run_juju_with_proot "-S" ${JUJU_HOME} ${comm}
}
function run_juju_as_user(){
- _run_juju_with_proot "-R"
+ local comm=$(_define_proot_args "$@")
+ _run_juju_with_proot "-R" ${JUJU_HOME} ${comm}
}
function delete_juju(){
- [ "$JUJU_ENV" == "1" ] && die "Error: The operation is not allowed inside JuJu environment"
-
! ask "Are you sure to delete JuJu located in ${JUJU_HOME}" "N" && return
if mountpoint -q ${JUJU_HOME}
then
@@ -212,7 +231,7 @@ function build_image_juju(){
_check_package git
local maindir=$(TMPDIR=$JUJU_TEMPDIR mktemp -d -t juju.XXXXXXXXXX)
mkdir -p ${maindir}/root
- prepare_build_directory
+ _prepare_build_directory
info "Installing pacman and its dependencies..."
pacstrap -d ${maindir}/root pacman arch-install-scripts binutils libunistring
@@ -248,9 +267,15 @@ function build_image_juju(){
echo 'export PATH=$PATH:/opt/juju/bin' > ${maindir}/root/etc/profile.d/juju.sh
chmod +x ${maindir}/root/etc/profile.d/juju.sh
+ info "Validating JuJu image..."
+ arch-chroot ${maindir}/root pacman -Qi pacman &> /dev/null
+ arch-chroot ${maindir}/root yaourt -V &> /dev/null
+ arch-chroot ${maindir}/root proot --help &> /dev/null
+ arch-chroot ${maindir}/root arch-chroot --help &> /dev/null
+
builtin cd ${ORIGIN_WD}
local imagefile="juju-${ARCH}.tar.gz"
info "Compressing image to ${imagefile}..."
tar -zcpf ${imagefile} -C ${maindir}/root .
- cleanup_build_directory ${maindir}
+ _cleanup_build_directory ${maindir}
}
diff --git a/lib/util.sh b/lib/util.sh
index 15fd48c..dc88506 100644
--- a/lib/util.sh
+++ b/lib/util.sh
@@ -17,7 +17,9 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-echoerr() { echo "$@" 1>&2; }
+function echoerr(){
+ echo "$@" 1>&2
+}
function die(){
# $@: msg (mandatory) - str: Message to print
error $@
diff --git a/tests/test_all.sh b/tests/test_all.sh
new file mode 100755
index 0000000..684e6fd
--- /dev/null
+++ b/tests/test_all.sh
@@ -0,0 +1,5 @@
+
+for tst in $(ls $(dirname $0)/test_* | grep -v $(basename $0))
+do
+ $tst
+done
diff --git a/tests/test_core.sh b/tests/test_core.sh
new file mode 100755
index 0000000..1a4be0c
--- /dev/null
+++ b/tests/test_core.sh
@@ -0,0 +1,162 @@
+#!/bin/bash
+
+source "$(dirname $0)/utils.sh"
+CURRPWD=$PWD
+JUJU_MAIN_HOME=/tmp/jujutesthome
+[ -e $JUJU_MAIN_HOME ] || JUJU_HOME=$JUJU_MAIN_HOME bash --rcfile "$(dirname $0)/../lib/core.sh" -ic "setup_juju"
+JUJU_HOME=""
+
+function install_mini_juju(){
+ cp -rfa $JUJU_MAIN_HOME/* $JUJU_HOME
+}
+
+function set_up(){
+ cd $CURRPWD
+ JUJU_HOME=$(TMPDIR=/tmp mktemp -d -t jujuhome.XXXXXXXXXX)
+ source "$(dirname $0)/../lib/core.sh"
+ ORIGIN_WD=$(TMPDIR=/tmp mktemp -d -t jujuowd.XXXXXXXXXX)
+ cd $ORIGIN_WD
+ JUJU_TEMPDIR=$(TMPDIR=/tmp mktemp -d -t jujutemp.XXXXXXXXXX)
+
+ trap - QUIT EXIT ABRT KILL TERM INT
+ trap "rm -rf ${JUJU_HOME}; rm -rf ${ORIGIN_WD}; rm -rf ${JUJU_TEMPDIR}" EXIT QUIT ABRT KILL TERM INT
+}
+
+
+function tear_down(){
+ rm -rf $JUJU_HOME
+ rm -rf $ORIGIN_WD
+ rm -rf $JUJU_TEMPDIR
+ trap - QUIT EXIT ABRT KILL TERM INT
+}
+
+function test_is_juju_installed(){
+ is_juju_installed
+ is_equal $? 1 || return 1
+ touch $JUJU_HOME/just_file
+ is_juju_installed
+ is_equal $? 0 || return 1
+}
+
+
+function test_setup_juju(){
+ wget_mock(){
+ # Proof that the setup is happening
+ # inside $JUJU_TEMPDIR
+ local cwd=${PWD#${JUJU_TEMPDIR}}
+ local parent_dir=${PWD%${cwd}}
+ is_equal $JUJU_TEMPDIR ${parent_dir} || return 1
+ touch file
+ tar -czvf juju-${ARCH}.tar.gz file
+ }
+ WGET=wget_mock
+ setup_juju 1> /dev/null
+ [ -e $JUJU_HOME/file ] || return 1
+ [ -e $JUJU_HOME/run/lock ] || return 1
+}
+
+
+function test_setup_from_file_juju(){
+ touch file
+ tar -czvf juju-${ARCH}.tar.gz file 1> /dev/null
+ setup_from_file_juju juju-${ARCH}.tar.gz 1> /dev/null
+ [ -e $JUJU_HOME/file ] || return 1
+ [ -e $JUJU_HOME/run/lock ] || return 1
+
+ export -f setup_from_file_juju
+ export -f die
+ bash -ic "setup_from_file_juju noexist.tar.gz" &> /dev/null
+ is_equal $? 1 || return 1
+}
+
+
+function test_run_juju_as_root(){
+ install_mini_juju
+ CHROOT="sudo $CHROOT"
+ SH="type -t type"
+ local output=$(run_juju_as_root)
+ is_equal $output "builtin" || return 1
+ local output=$(run_juju_as_root pwd)
+ is_equal $output "/" || return 1
+ run_juju_as_root "[ -e /run/lock ]"
+ is_equal $? 0 || return 1
+ [ -e $JUJU_HOME/${HOME} ] || return 1
+}
+
+function test_run_juju_as_user(){
+ install_mini_juju
+ local output=$(run_juju_as_user "" "mkdir -v /newdir2" | awk -F: '{print $1}')
+ is_equal "$output" "/usr/bin/mkdir" || return 1
+ [ -e $JUJU_HOME/newdir2 ]
+ is_equal $? 0 || return 1
+
+ SH="mkdir -v /newdir"
+ local output=$(run_juju_as_user "" | awk -F: '{print $1}')
+ is_equal "$output" "/usr/bin/mkdir" || return 1
+ [ -e $JUJU_HOME/newdir ]
+ is_equal $? 0 || return 1
+}
+
+function test_run_juju_as_user_proot_args(){
+ install_mini_juju
+ run_juju_as_user "--help" "" 1> /dev/null
+ is_equal $? 0 || return 1
+ run_juju_as_user "--helps" "" &> /dev/null
+ is_equal $? 1 || return 1
+
+ mkdir $JUJU_TEMPDIR/newdir
+ touch $JUJU_TEMPDIR/newdir/newfile
+ run_juju_as_user "-b $JUJU_TEMPDIR/newdir:/newdir" "ls -l /newdir/newfile" 1> /dev/null
+ is_equal $? 0 || return 1
+
+ export -f _run_juju_with_proot
+ export PROOT
+ export TRUE
+ ID="/usr/bin/echo 0" bash -ic "_run_juju_with_proot" &> /dev/null
+ is_equal $? 1 || return 1
+ export -n _run_juju_with_proot
+ unset _run_juju_with_proot
+ export -n PROOT
+ export -n TRUE
+}
+
+function test_run_juju_as_user_seccomp(){
+ install_mini_juju
+ PROOT=""
+ local output=$(_run_juju_with_proot "" "env" | grep "PROOT_NO_SECCOMP")
+ is_equal $output "" || return 1
+
+ TRUE="/usr/bin/false"
+ local output=$(_run_juju_with_proot "" "env" | grep "PROOT_NO_SECCOMP")
+ is_equal $output "PROOT_NO_SECCOMP=1" || return 1
+}
+
+function test_run_juju_as_fakeroot(){
+ install_mini_juju
+ local output=$(run_juju_as_fakeroot "" "id" | awk '{print $1}')
+ is_equal "$output" "uid=0(root)" || return 1
+}
+
+function test_delete_juju(){
+ install_mini_juju
+ echo "N" | delete_juju 1> /dev/null
+ is_juju_installed
+ is_equal $? 0 || return 1
+ echo "Y" | delete_juju 1> /dev/null
+ is_juju_installed
+ is_equal $? 1 || return 1
+}
+
+function test_nested_juju(){
+ install_mini_juju
+ JUJU_ENV=1 bash -ic "source $CURRPWD/$(dirname $0)/../lib/core.sh" &> /dev/null
+ is_equal $? 1 || return 1
+}
+
+
+for func in $(declare -F | grep test_ | awk '{print $3}' | xargs)
+do
+ set_up
+ $func && echo -e "${func}...\033[1;32mOK\033[0m" || echo -e "${func}...\033[1;31mFAIL\033[0m"
+ tear_down
+done
diff --git a/tests/test_juju.sh b/tests/test_juju.sh
new file mode 100755
index 0000000..38806f7
--- /dev/null
+++ b/tests/test_juju.sh
@@ -0,0 +1,141 @@
+#!/bin/bash
+source "$(dirname $0)/utils.sh"
+source $(dirname $0)/../bin/juju -h &> /dev/null
+## Mock functions ##
+function usage(){
+ echo "usage"
+}
+function version(){
+ echo "version"
+}
+function build_image_juju(){
+ echo "build_image_juju"
+}
+function delete_juju(){
+ echo "delete_juju"
+}
+function is_juju_installed(){
+ return 0
+}
+function setup_from_file_juju(){
+ echo "setup_from_file_juju $@"
+}
+function setup_juju(){
+ echo "setup_juju"
+}
+function run_juju_as_fakeroot(){
+ echo "run_juju_as_fakeroot $@"
+}
+function run_juju_as_root(){
+ echo "run_juju_as_root $@"
+}
+function run_juju_as_user(){
+ echo "run_juju_as_user $@"
+}
+
+function wrap_juju(){
+ parse_arguments $@
+ check_cli
+ execute_operation
+}
+
+
+function set_up(){
+ echo > /dev/null
+}
+
+function tear_down(){
+ echo > /dev/null
+}
+
+
+function test_help(){
+ local output=$(wrap_juju -h)
+ is_equal $output "usage" || return 1
+ local output=$(wrap_juju --help)
+ is_equal $output "usage" || return 1
+}
+function test_version(){
+ local output=$(wrap_juju -v)
+ is_equal $output "version" || return 1
+ local output=$(wrap_juju --version)
+ is_equal $output "version" || return 1
+}
+function test_build_image_juju(){
+ local output=$(wrap_juju -b)
+ is_equal $output "build_image_juju" || return 1
+ local output=$(wrap_juju --build-image)
+ is_equal $output "build_image_juju" || return 1
+}
+function test_delete_juju(){
+ local output=$(wrap_juju -d)
+ is_equal $output "delete_juju" || return 1
+ local output=$(wrap_juju --delete)
+ is_equal $output "delete_juju" || return 1
+}
+function test_run_juju_as_fakeroot(){
+ local output=$(wrap_juju -f)
+ is_equal $output "run_juju_as_fakeroot" || return 1
+ local output=$(wrap_juju --fakeroot)
+ is_equal $output "run_juju_as_fakeroot" || return 1
+
+ local output=$(wrap_juju -f -p "-b arg")
+ is_equal "${output[@]}" "run_juju_as_fakeroot -b arg" || return 1
+ local output=$(wrap_juju -f -p "-b arg" -- command)
+ is_equal "${output[@]}" "run_juju_as_fakeroot -b arg command" || return 1
+ local output=$(wrap_juju -f command)
+ is_equal "${output[@]}" "run_juju_as_fakeroot command" || return 1
+}
+function test_run_juju_as_user(){
+ local output=$(wrap_juju)
+ is_equal $output "run_juju_as_user" || return 1
+
+ local output=$(wrap_juju -p "-b arg")
+ is_equal "${output[@]}" "run_juju_as_user -b arg" || return 1
+ local output=$(wrap_juju -p "-b arg" -- command)
+ is_equal "${output[@]}" "run_juju_as_user -b arg command" || return 1
+ local output=$(wrap_juju command)
+ is_equal "${output[@]}" "run_juju_as_user command" || return 1
+}
+function test_run_juju_as_root(){
+ local output=$(wrap_juju -r)
+ is_equal $output "run_juju_as_root" || return 1
+
+ local output=$(wrap_juju -r command)
+ is_equal "${output[@]}" "run_juju_as_root command" || return 1
+}
+
+function test_check_cli(){
+ export -f check_cli
+ export -f parse_arguments
+ export -f execute_operation
+ export -f wrap_juju
+ export -f die
+ bash -ic "wrap_juju -b -h" &> /dev/null
+ is_equal $? 1 || return 1
+ bash -ic "wrap_juju -d -r" &> /dev/null
+ is_equal $? 1 || return 1
+ bash -ic "wrap_juju -h -f" &> /dev/null
+ is_equal $? 1 || return 1
+ bash -ic "wrap_juju -v -i fsd" &> /dev/null
+ is_equal $? 1 || return 1
+ bash -ic "wrap_juju -f -r" &> /dev/null
+ is_equal $? 1 || return 1
+ bash -ic "wrap_juju -p args -v" &> /dev/null
+ is_equal $? 1 || return 1
+ bash -ic "wrap_juju -d args" &> /dev/null
+ is_equal $? 1 || return 1
+ export -n check_cli
+ export -n parse_arguments
+ export -n execute_operation
+ export -n wrap_juju
+ export -n die
+ unset die
+}
+
+for func in $(declare -F | grep test_ | awk '{print $3}' | xargs)
+do
+ set_up
+ $func && echo -e "${func}...\033[1;32mOK\033[0m" || echo -e "${func}...\033[1;31mFAIL\033[0m"
+ tear_down
+done
diff --git a/tests/test_util.sh b/tests/test_util.sh
new file mode 100755
index 0000000..e7cfc1b
--- /dev/null
+++ b/tests/test_util.sh
@@ -0,0 +1,66 @@
+#!/bin/bash
+source "$(dirname $0)/utils.sh"
+source "$(dirname $0)/../lib/util.sh"
+
+function test_echoerr(){
+ local actual=$(echoerr "Test" 2>&1)
+ is_equal "$actual" "Test" || return 1
+ return 0
+}
+
+function test_error(){
+ local actual=$(error "Test" 2>&1)
+ local expected=$(echo -e "\033[1;31mTest\033[0m")
+ is_equal "$actual" "$expected" || return 1
+ return 0
+}
+
+function test_warn(){
+ local actual=$(warn "Test" 2>&1)
+ local expected=$(echo -e "\033[1;33mTest\033[0m")
+ is_equal "$actual" "$expected" || return 1
+ return 0
+}
+
+function test_info(){
+ local actual=$(info "Test")
+ local expected=$(echo -e "\033[1;37mTest\033[0m")
+ is_equal "$actual" "$expected" || return 1
+ return 0
+}
+
+function test_die(){
+ local actual=$(die "Test" 2>&1)
+ local expected=$(echo -e "\033[1;31mTest\033[0m")
+ is_equal "$actual" "$expected" || return 1
+ export -f die
+ bash -ic "die Dying" &> /dev/null
+ is_equal $? 1 || return 1
+ export -n die
+ unset die
+ return 0
+}
+
+function test_ask(){
+ echo "Y" | ask "Test" &> /dev/null
+ is_equal $? 0 || return 1
+ echo "y" | ask "Test" &> /dev/null
+ is_equal $? 0 || return 1
+ echo "N" | ask "Test" &> /dev/null
+ is_equal $? 1 || return 1
+ echo "n" | ask "Test" &> /dev/null
+ is_equal $? 1 || return 1
+ echo -e "\n" | ask "Test" &> /dev/null
+ is_equal $? 0 || return 1
+ echo -e "\n" | ask "Test" "N" &> /dev/null
+ is_equal $? 1 || return 1
+ echo -e "asdf\n\n" | ask "Test" "N" &> /dev/null
+ is_equal $? 1 || return 1
+ return 0
+}
+
+
+for func in $(declare -F | grep test_ | awk '{print $3}' | xargs)
+do
+ $func && echo -e "${func}...\033[1;32mOK\033[0m" || echo -e "${func}...\033[1;31mFAIL\033[0m"
+done
diff --git a/tests/utils.sh b/tests/utils.sh
new file mode 100644
index 0000000..094c055
--- /dev/null
+++ b/tests/utils.sh
@@ -0,0 +1,11 @@
+function is_equal(){
+ if [ "$1" == "$2" ]
+ then
+ return 0
+ else
+ echo "$1!=$2"
+ return 1
+ fi
+}
+
+