diff --git a/test/run-test b/test/run-test
new file mode 100755
index 0000000..d8e4873
--- /dev/null
+++ b/test/run-test
@@ -0,0 +1,42 @@
+#!/bin/bash
+# Prepare and run a test
+#
+# Copyright (C) 2014 Mike Gerwitz
+#
+# This file is part of pkgsh.
+#
+# pkgsh is free software: you can redistribute it and/or modify
+# it under the terms of the GNU 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+#
+# This sets up the environment prior to running a test.
+##
+
+# change wd to root to place us in a known state for sourcing
+declare -r __wd_orig="$( pwd )"
+cd "$( dirname "$0" )/../" || {
+ echo "fatal: could not determine root directory" >&2
+ exit 1
+}
+
+
+main()
+{
+ local tpath="$1"
+
+ # fail on any sort of command failure
+ set -e
+ source "$__wd_orig/$tpath"
+}
+
+main "${1?Missing test path}"
+
diff --git a/test/runner b/test/runner
new file mode 100755
index 0000000..6d50173
--- /dev/null
+++ b/test/runner
@@ -0,0 +1,50 @@
+#!/bin/bash
+# Test runner
+#
+# Copyright (C) 2014 Mike Gerwitz
+#
+# This file is part of pkgsh.
+#
+# pkgsh is free software: you can redistribute it and/or modify
+# it under the terms of the GNU 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+#
+# You should invoke all tests using this runner rather than executing the
+# tests directly.
+##
+
+# determine our wd, but don't change directories (so that run-test can do
+# its own magic)
+__mypath="$( dirname "$0" )" || {
+ echo "fatal: could not determine runner directory" >&2
+ exit 1
+}
+readonly __mypath
+
+
+main()
+{
+ local -i fail=0
+
+ # run each test in an isolated process with a fresh environment
+ for tcase in "$@"; do
+ env -i "$__mypath/run-test" "$tcase" || {
+ echo "failure [$?]: $tcase" >&2
+ fail=1
+ }
+ done
+
+ test "$fail" -eq 0
+}
+
+main "$@"
+