diff --git a/common.sh b/common.sh
new file mode 100644
index 0000000000000000000000000000000000000000..913e1bfc3a57549fe8e68453dade06e4fc3ad60c
--- /dev/null
+++ b/common.sh
@@ -0,0 +1,319 @@
+# Source me!
+# I need ROOTDIR, PN and PV to be defined! (see bottom of file)
+# vim: set sts=4 sw=4 et :
+
+# Detect whether stdout is to a terminal
+if tty -s <&1; then
+    IS_TTY="true"
+fi
+
+is_tty() {
+    [ "${IS_TTY}" = true ] && return 0
+    return 1
+}
+
+##########
+# Murder #
+##########
+_kill_daemons() {
+    pkill dconf-service >/dev/null 2>&1 || true
+    return 0 # Never fail
+}
+
+##########
+# Output #
+##########
+cry_n() {
+    if is_tty; then
+        # Cry tears of blood
+        /bin/echo -n -e "\033[01;31m#\033[00m $@"
+    else
+        /bin/echo -n -e "# $@"
+    fi
+}
+
+cry() {
+    cry_n "$@"
+    echo
+}
+
+say_n() {
+    if is_tty; then
+        # Speak in green
+        /bin/echo -n -e "\033[01;32m#\033[00m $@"
+    else
+        /bin/echo -n -e "# $@"
+    fi
+}
+
+say() {
+    say_n "$@"
+    echo
+}
+
+whine_n() {
+    if is_tty; then
+        # Whine in yellow
+        /bin/echo -n -e "\033[01;33m#\033[00m $@"
+    else
+        /bin/echo -n -e "# $@"
+    fi
+}
+
+whine() {
+    whine_n "$@"
+    echo
+}
+
+echo_red() {
+    if is_tty; then
+        # Print text in red, without an implicit newline
+        /bin/echo -n -e "\033[01;31m$@\033[00m"
+    else
+        /bin/echo -n -e "$@"
+    fi
+}
+
+echo_green() {
+    if is_tty; then
+        # Print text in green, without an implicit newline
+        /bin/echo -n -e "\033[01;32m$@\033[00m"
+    else
+        /bin/echo -n -e "$@"
+    fi
+}
+
+###################
+# Status Messages #
+###################
+test_success() {
+    say "All tests PASSED successfully!"
+    _kill_daemons
+    trap - ERR 2>/dev/null || true
+    trap - EXIT
+    exit 0
+}
+
+test_failure() {
+    [ $? -eq 0 ] && exit
+    cry "Tests FAILED!"
+    _kill_daemons
+    whine "Work directory was: ${WORKDIR}"
+    exit 0
+}
+
+setup_success() {
+    say "Test setup successfully!"
+    _kill_daemons
+    trap - ERR 2>/dev/null || true
+    trap - EXIT
+    return 0
+}
+
+setup_failure() {
+    [ $? -eq 0 ] && exit
+    cry "Test setup failed!"
+    _kill_daemons
+    exit 1
+}
+
+#############
+# Utilities #
+#############
+create_temp_workdir() {
+    local tempdir="$(mktemp -d -p ${WORKDIR})"
+    echo "${tempdir}" 1>&2
+    _realpath "${tempdir}"
+}
+
+# Takes an IFS delimited list of files from stdin, checks if each exists, and
+# passes it on if it does. If the file doesn't exist, it prefixes the filename
+# with "DNE", which is caught by _src_test, which then marks it as a failed test
+check_file_exists_tee() {
+    while read i; do
+        if [ -e "$i" ]; then
+            echo "$i"
+        else
+            echo "DNE: $i"
+        fi
+    done
+}
+
+check_not_root() {
+    if [ "$(id -ru)" = 0 ]; then
+        cry "Do not run this test as root!"
+        return 1
+    fi
+    return 0
+}
+
+check_have_root() {
+    if [ "$(id -ru)" != 0 ]; then
+        cry "Need root to run this test successfully!"
+        return 1
+    fi
+    return 0
+}
+
+arch_is_arm() {
+    case "$(uname -m)" in
+        arm*)
+            return 0
+            ;;
+    esac
+    return 1
+}
+
+# Check if a session bus is running otherwise fail
+ensure_dbus_session() {
+    local dbus_socket
+
+    dbus_socket="/run/user/$(id -ru)/bus"
+    if [ ! -e "${dbus_socket}" ]; then
+        cry "Could not find session bus..."
+        return 1
+    fi
+
+    return 0
+}
+
+######################
+# Internal functions #
+######################
+# Sleep, unless specified otherwise
+_sleep() {
+    if [ -z "${QUICK}" ]; then
+        /bin/sleep "$@"
+    else
+        /bin/sleep 0.2
+    fi
+}
+
+# Run external utilities with this
+# All output is suppressed by default as LAVA doesn't cope well with very large logs.
+# Use DEBUG=1 to enable all output when running tests locally on developer devices.
+# Short-circuiting with a return is needed to prevent unexpected exiting due to
+# a non-zero return code when set -e is enabled.
+_run_cmd() {
+    if [ "${DEBUG}" = 1 ]
+    then
+        "$@" || return
+    else
+        "$@" >/dev/null 2>&1 || return
+    fi
+}
+
+_expect_pass() {
+    _run_cmd "$@"
+}
+
+_expect_fail() {
+    ! _run_cmd "$@"
+}
+
+_src_test() {
+    # Reads a list of tests to run via stdin, and executes them one by one
+    # All these are supposed to pass
+    local i
+    local failed=
+    local expect=$1
+    local prefix=""
+    shift
+
+    if [ -n "${APERTIS_TESTS_NAME_PREFIX}" ]; then
+        prefix="${APERTIS_TESTS_NAME_PREFIX}"
+    fi
+
+    while read i; do
+        case $i in
+          [#]*)
+            echo_red "${prefix}$i: skip\n"
+            continue
+            ;;
+          # See check_file_exists_tee()
+          DNE*)
+            echo_red "${prefix}$i: fail\n"
+            whine "Got an invalid executable name '$i'!"
+            failed="$failed $i"
+            continue
+            ;;
+        esac
+        say "Running test '$i' ..."
+        if ! "$expect" "$i" "$@"; then
+            failed="$failed $i"
+            echo_red "${prefix}$i: fail\n"
+        else
+            echo_green "${prefix}$i: pass\n"
+        fi
+    done
+    if [ -n "$failed" ]; then
+        whine "The following tests failed: "
+        for i in $failed; do
+            whine "\t$i"
+        done
+        return 1
+    fi
+}
+
+# Let's not depend on realpath; we don't need it
+_realpath() {
+    cd "$1"
+    echo "$PWD"
+}
+
+##########
+# Phases #
+##########
+src_test_pass() {
+    _src_test _expect_pass "$@"
+}
+
+src_test_fail() {
+    _src_test _expect_fail "$@"
+}
+
+src_unpack() {
+    mkdir -p "${WORKDIR}"
+}
+
+src_cleanup() {
+    rm -rf "${WORKDIR}"
+}
+
+src_copy() {
+    # Copy $1 $2 .. $N-1 to $N
+    # Essentially just a wrapper around `cp` right now
+    cp -v -L -r "$@" 1>&2
+}
+
+src_copy_contents() {
+    # Copy the contents of $1 to $2
+    # FIXME: This ignores dot-files. Use rsync or something?
+    cp -v -L -r "$1"/* "$2" 1>&2
+}
+
+#############
+# Variables #
+#############
+# Fix this to not flood /var/tmp with temporary directories
+BASEWORKDIR="/var/tmp/chaiwala-tests"
+mkdir -p "${BASEWORKDIR}"
+WORKDIR="$(mktemp -d -p ${BASEWORKDIR} "$(date +%Y%m%d-%H%M%S)-XXXXXXXXXX")"
+# Tests might be run as chaiwala, or as root, or some other user
+# Everyone should be able to write here
+chown 1000 "${BASEWORKDIR}"
+chmod 777 "${BASEWORKDIR}" || true
+chmod 777 "${WORKDIR}" || true
+sync
+
+# Wrappers for external commands used
+WGET="${WGET:-wget -c}"
+# We disable apt-get, and just do a pass-through because these tests are
+# integrated into LAVA now
+#APT_GET="$(type -P true)"
+GDBUS="${GDBUS:-gdbus}"
+
+# 0 = no output
+# 1 = stderr
+# 2 = stdout + stderr
+DEBUG="${DEBUG:-0}"
diff --git a/inherit-config.sh b/inherit-config.sh
new file mode 100644
index 0000000000000000000000000000000000000000..050b5f1054870804866d823c8f805e55ff0ef462
--- /dev/null
+++ b/inherit-config.sh
@@ -0,0 +1,21 @@
+# This file should be symlinked inside your test directory,
+# and sourced from config.sh
+# It will be modified and copied to the install directory by make
+
+. "${TESTDIR}/common/common.sh"
+
+## These variables are properties of the image itself
+LIBEXECDIR="${LIBEXECDIR:-/usr/lib}"
+
+## These variables get modified by `make` during install
+# Directory where scripts and other non-binary data is installed
+TESTDATADIR="${TESTDIR}"
+# Binary executable install directory
+TESTLIBDIR="${TESTDIR}"
+
+REALTESTDIR="$(_realpath ${TESTDIR})"
+# Directory where test data/resources are installed
+RESOURCE_DIR="${REALTESTDIR}/common/resources"
+
+## These are derived from the above
+MEDIA_RESOURCE_DIR="${RESOURCE_DIR}/media"