diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..f345238
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,5 @@
+test:
+ test/run.sh
+
+.SILENT:
+.PHONY: test
diff --git a/test/assert.sh b/test/assert.sh
new file mode 100644
index 0000000..f3d03d8
--- /dev/null
+++ b/test/assert.sh
@@ -0,0 +1,139 @@
+#!/bin/bash
+# assert.sh 1.0 - bash unit testing framework
+# Copyright (C) 2009, 2010, 2011, 2012 Robert Lehmann
+#
+# http://github.com/lehmannro/assert.sh
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published
+# by the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program. If not, see .
+
+export DISCOVERONLY=${DISCOVERONLY:-}
+export DEBUG=${DEBUG:-}
+export STOP=${STOP:-}
+export INVARIANT=${INVARIANT:-}
+
+args="$(getopt -n "$0" -l verbose,help,stop,discover,invariant vhxdi $*)" \
+|| exit -1
+for arg in $args; do
+ case "$arg" in
+ -h)
+ echo "$0 [-vxid] [--verbose] [--stop] [--invariant] [--discover]"
+ echo "`sed 's/./ /g' <<< "$0"` [-h] [--help]"
+ exit 0;;
+ --help)
+ cat < [stdin]
+ (( tests_ran++ ))
+ [[ -n "$DISCOVERONLY" ]] && return
+ # printf required for formatting
+ printf -v expected "x${2:-}" # x required to overwrite older results
+ result="$(eval 2>/dev/null $1 <<< ${3:-})"
+ # Note: $expected is already decorated
+ if [[ "x$result" == "$expected" ]]; then
+ [[ -n "$DEBUG" ]] && echo -n .
+ return
+ fi
+ [[ -n "$DEBUG" ]] && echo -n X
+ result="$(sed -e :a -e '$!N;s/\n/\\n/;ta' <<< "$result")"
+ [[ -z "$result" ]] && result="nothing" || result="\"$result\""
+ [[ -z "$2" ]] && expected="nothing" || expected="\"$2\""
+ failure="expected $expected${_indent}got $result"
+ report="test #$tests_ran \"$1${3:+ <<< $3}\" failed:${_indent}$failure"
+ tests_errors[$tests_failed]="$report"
+ (( tests_failed++ ))
+ if [[ -n "$STOP" ]]; then
+ [[ -n "$DEBUG" ]] && echo
+ echo "$report"
+ exit 1
+ fi
+}
+
+assert_raises() {
+ # assert_raises [stdin]
+ (( tests_ran++ ))
+ [[ -n "$DISCOVERONLY" ]] && return
+ (eval $1 <<< ${3:-}) > /dev/null 2>&1
+ status=$?
+ expected=${2:-0}
+ if [[ "$status" -eq "$expected" ]]; then
+ [[ -n "$DEBUG" ]] && echo -n .
+ return
+ fi
+ [[ -n "$DEBUG" ]] && echo -n X
+ failure="program terminated with code $status instead of $expected"
+ report="test #$tests_ran \"$1${3:+ <<< $3}\" failed:${_indent}$failure"
+ tests_errors[$tests_failed]="$report"
+ (( tests_failed++ ))
+ if [[ -n "$STOP" ]]; then
+ [[ -n "$DEBUG" ]] && echo
+ echo "$report"
+ exit 1
+ fi
+}
+
+_assert_reset
diff --git a/test/run.sh b/test/run.sh
new file mode 100755
index 0000000..1988533
--- /dev/null
+++ b/test/run.sh
@@ -0,0 +1,30 @@
+#! /usr/bin/env bash
+
+resolve_link() {
+ $(type -p greadlink readlink | head -1) $1
+}
+
+abs_dirname() {
+ local cwd="$(pwd)"
+ local path="$1"
+
+ while [ -n "$path" ]; do
+ cd "${path%/*}"
+ local name="${path##*/}"
+ path="$(resolve_link "$name" || true)"
+ done
+
+ pwd
+ cd "$cwd"
+}
+
+
+testdir="$(abs_dirname "$0")"
+testfiles="$(find "$testdir" -name "*-test.sh")"
+
+for testfile in $testfiles; do
+ echo "running: ${testfile/#$(dirname "$testdir")\//}"
+ cd "$(dirname "$testfile")"
+ "$testfile"
+ echo ""
+done