diff --git a/sbs b/sbs
index a87952cb663b4950c237f13d8e23f7b3b71275af..63633effaa25754537f8b5a7217dd1865f471bff 100755
--- a/sbs
+++ b/sbs
@@ -35,18 +35,18 @@ print_banner() {
 |  | |_) | |_| | | | (_| |  ___) | |_| \\__ \\ ||  __/ | | | | |  |
 |  |____/ \\__,_|_|_|\\__,_| |____/ \\__, |___/\\__\\___|_| |_| |_|  |
 +----------------------------------|___/-------------------v3.2-+
-
 EOF
 }
 
 ohai() {
-    printf "${TTY_BLUE}==>${TTY_RESET}${TTY_BOLD} %s${TTY_RESET}\n" "$@";
+    printf "\n${TTY_BLUE}==>${TTY_RESET}${TTY_BOLD} %s${TTY_RESET}\n" "$@";
 }
 
 init_dirs() {
     sbs_base="$(cd -- "$(dirname "$0")" > /dev/null 2>&1 && pwd -P)"
     source_dir="$PWD"
-    build_dir="$source_dir/build"
+    build_default_dir="$source_dir/$BUILD_DEFAULT_DIRNAME"
+    build_host_dir="$source_dir/$BUILD_HOST_DIRNAME"
     toolchain_file="$sbs_base/libs/miosix-kernel/miosix/_tools/toolchain.cmake"
 }
 
@@ -74,25 +74,32 @@ find_deps() {
     printf "Found clang-format: ";        [ "$found_clangformat" = true ] && echo "yes" || echo "no"
     printf "Found flasher: ";             [ "$found_stflash" = true ]     && echo "st-flash" \
                                             || { [ "$found_stlink" = true ] && echo "st-link" || echo "no"; }
-    echo
 
     [ "$found_cmake" = true ]     || { echo "Error: CMake must be installed"; exit 1; }
     [ "$found_miosixgpp" = true ] || { echo "Error: arm-miosix-eabi-g++ must be installed"; exit 1; }
 }
 
+# Workaround: Disable tests in excluded subdirectories
+# See: https://gitlab.kitware.com/cmake/cmake/-/issues/20212
+cmake_disable_excluded_tests() {
+    [ ! -f "$1/$CTEST_FILENAME" ] || sed -i.bak 's/^subdirs/# subdirs/' "$1/$CTEST_FILENAME"
+}
+
 configure() {
+    declare build_dir="$1"
+
     ohai "Configure"
 
     [ -f "$toolchain_file" ] || { echo "Error: CMake Toolchain File for Miosix was not found"; exit 1; }
 
-    local defs=(-DCMAKE_EXPORT_COMPILE_COMMANDS=ON)
+    declare -a defs=(-DCMAKE_EXPORT_COMPILE_COMMANDS=ON)
     defs+=(-DCMAKE_C_FLAGS=-fdiagnostics-color=always -DCMAKE_CXX_FLAGS=-fdiagnostics-color=always)
-    defs+=(-DCMAKE_TOOLCHAIN_FILE="$toolchain_file")
+    [ "$config_host" = false ]   && defs+=(-DCMAKE_TOOLCHAIN_FILE="$toolchain_file" -DBUILD_TESTING=OFF)
     [ "$found_ccache" = true ]   && defs+=(-DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache)
-    [ "$config_debug" = true ]   && defs+=(-DCMAKE_BUILD_TYPE=Debug) || defs+=( -DCMAKE_BUILD_TYPE=Release )
+    [ "$config_debug" = true ]   && defs+=(-DCMAKE_BUILD_TYPE=Debug) || defs+=(-DCMAKE_BUILD_TYPE=Release)
     [ "$config_verbose" = true ] && defs+=(-DCMAKE_VERBOSE_MAKEFILE=ON)
 
-    local gen
+    declare gen
     [ "$found_ninja" = true ] && gen=-GNinja || gen=-G"Unix Makefiles"
 
     if cmake -B"$build_dir" "${defs[@]}" "$gen" "$source_dir"; then
@@ -102,7 +109,9 @@ configure() {
 }
 
 check_configured() {
-    local to_reconfigure=false
+    declare build_dir="$1"
+
+    declare to_reconfigure=false
 
     if [ ! -d "$build_dir" ]; then
         to_reconfigure=true
@@ -119,62 +128,99 @@ check_configured() {
     fi
 
     if [ "$to_reconfigure" = true ]; then
-        configure && echo
+        configure "$build_dir"
     else
         true
     fi
 }
 
 build() {
-    if check_configured; then
+    declare build_dir="$1"
+    declare target="$2"
+
+    if check_configured "$build_dir"; then
         ohai "Build"
 
-        local opts
+        declare -a opts
         get_build_opts opts
 
-        cmake --build "$build_dir" "${opts[@]}" --target "$1"
+        cmake --build "$build_dir" "${opts[@]}" --target "$target"
     fi
 }
 
+build_all() {
+    declare build_dir="$build_default_dir"
+
+    build "$build_dir" all
+}
+
 clean() {
-    ohai "Clean"
+    declare build_dir_name="$1"
+    declare build_dir="$2"
+
+    ohai "Clean ($build_dir_name)"
 
     if [ -f "$build_dir/$CMAKE_FILENAME" ]; then
-        local opts
+        declare -a opts
         get_build_opts opts
 
-        cmake --build "$build_dir" "${opts[@]}" --target clean && echo
+        cmake --build "$build_dir" "${opts[@]}" --target clean
     fi
 
     echo "Removing build folder..."
     rm -rf "$build_dir"
 }
 
+clean_all() {
+    clean "Default" "$build_default_dir"
+    clean "Host" "$build_host_dir"
+}
+
 flash() {
-    if build "$1"; then
-        echo
+    declare target="$1"
+
+    declare build_dir="$build_default_dir"
+
+    if build "$build_dir" "$target"; then
         ohai "Flash"
 
-        if [ ! -f "$build_dir/$1.bin" ]; then
-            echo "Error: target '$1' is not flashable"
+        if [ ! -f "$build_dir/$target.bin" ]; then
+            echo "Error: target '$target' is not flashable"
             return 1
         fi
 
         if [ "$found_stflash" = true ]; then
-            st-flash --reset write "$build_dir/$1.bin" 0x8000000
+            st-flash --reset write "$build_dir/$target.bin" 0x8000000
         elif [ "$found_stlink" = true ]; then
-            ST-LINK_CLI.exe -P "$build_dir/$1.bin" 0x8000000 -V -Rst
+            ST-LINK_CLI.exe -P "$build_dir/$target.bin" 0x8000000 -V -Rst
         else
             echo "Error: No flashing software found!"
         fi
     fi
 }
 
+run_tests() {
+    declare target="$1"
+
+    declare build_dir="$build_host_dir"
+
+    config_host=true
+
+    if build "$build_dir" "$target"; then
+        ohai "Test"
+
+        cmake_disable_excluded_tests "$build_dir"
+        ( cd "$build_dir"; ctest ) || return
+    fi
+}
+
 list() {
-    if check_configured; then
+    declare build_dir="$build_default_dir"
+
+    if check_configured "$build_dir"; then
         ohai "List targets"
 
-        local opts
+        declare -a opts
         get_build_opts opts
 
         echo "[1/1] All SBS targets available:"
@@ -184,10 +230,12 @@ list() {
 }
 
 boards() {
-    if check_configured; then
+    declare build_dir="$build_default_dir"
+
+    if check_configured "$build_dir"; then
         ohai "List boards"
 
-        local opts
+        declare -a opts
         get_build_opts opts
 
         cmake --build "$build_dir" "${opts[@]}" --target help-boards
@@ -198,66 +246,67 @@ lint_copyright() {
     ohai "Lint (Copyright)"
 
     "$sbs_base/scripts/linter.py" --copyright "$source_dir/src"
-    echo
 }
 
 lint_find() {
     ohai "Lint (Find)"
 
     "$sbs_base/scripts/linter.py" --find "$source_dir/src"
-    echo
 }
 
 lint_clangtidy() {
-    if check_configured; then
+    declare build_dir="$1"
+
+    if check_configured "$build_dir"; then
         ohai "Lint (clang-tidy)"
 
         defs=(--extra-arg=-D_MIOSIX=1 --extra-arg=-D_MIOSIX_GCC_PATCH_MINOR=1 --extra-arg=-D_MIOSIX_GCC_PATCH_MAJOR=3 --extra-arg=-D__LINT__)
         IFS=$'\n' read -rd '' -a incs < <(arm-miosix-eabi-g++ -E -Wp,-v -xc++ /dev/null 2>&1 | sed -n "s/^ /--extra-arg=-isystem/p")
 
-        local opts=()
-        [ "$lint_edit" = true ] && opts+=(--fix-notes --fix-errors)
+        declare opts=()
+        [ "$to_edit" = true ] && opts+=(--fix-notes --fix-errors)
 
         find "$source_dir/src" \
             -type f \( -iname "*.cpp" -o -iname "*.h" -o -iname "*.c" \) \
             -exec clang-tidy --header-filter=".*" -p="$build_dir" "${defs[@]}" "${incs[@]}" "${opts[@]}" {} \;
-        echo
     fi
 }
 
 lint_cppcheck() {
     ohai "Lint (Cppcheck)"
 
-    local opts
-    get_cppcheck_opts opts
+    declare -a opts=()
+    [ -n "$jobs" ] && opts+=("-j $jobs")
 
     cppcheck --language=c++ --std=c++11 --enable=all --inline-suppr \
         --suppress=unmatchedSuppression --suppress=unusedFunction \
         --suppress=missingInclude --error-exitcode=1 "${opts[@]}" \
         "$source_dir/src"
-    echo
 }
 
 lint_clangformat() {
+    declare to_edit="$1"
+
     ohai "Lint (clang-format)"
 
-    local opts=(--style=file --Werror)
-    [ "$lint_edit" = true ] && opts+=(-i) || opts+=(--dry-run)
+    declare -a opts=(--style=file --Werror)
+    [ "$to_edit" = true ] && opts+=(-i) || opts+=(--dry-run)
 
     find "$source_dir/src" \
         -type f \( -iname "*.cpp" -o -iname "*.h" -o -iname "*.c" \) \
         -exec clang-format "${opts[@]}" {} \;
-    echo
 }
 
 lint() {
+    declare to_edit="$1"
+
     if [ "$found_python" = true ]; then
         lint_copyright
         lint_find
     fi
 
     if [ "$found_clangtidy" = true ] && [ "$lint_clangtidy" = true ]; then
-        lint_clangtidy
+        lint_clangtidy "$build_default_dir"
     fi
 
     if [ "$found_cppcheck" = true ]; then
@@ -265,19 +314,10 @@ lint() {
     fi
 
     if [ "$found_clangformat" = true ]; then
-        lint_clangformat
+        lint_clangformat "$to_edit"
     fi
 }
 
-edit() {
-    lint_edit=true
-    lint
-}
-
-build_all() {
-    build all
-}
-
 set_debug() {
     config_debug=true
 }
@@ -291,20 +331,16 @@ set_jobs() {
 }
 
 get_build_opts() {
-    local -n build_opts=$1
+    declare -n build_opts=$1
     [ -n "$jobs" ] && build_opts=("-j $jobs")
 }
 
-get_cppcheck_opts() {
-    local -n cppcheck_opts=$1
-    [ -n "$jobs" ] && cppcheck_opts=("-j $jobs")
-}
-
 set_clangtidy() {
     lint_clangtidy=true
 }
 
 usage() {
+    echo
     cat <<EOF
 Usage: $(basename "$0") [OPTIONS]
 
@@ -320,6 +356,7 @@ OPTIONS:
                           Build a specific target
     -f TARGET, --flash TARGET
                           Build and flash a specific target
+    -t TEST, --test TEST  Build a specific test natively and run it
     -c, --clean           Clean the working tree
     -u, --configure       Force configure and do not build
     -d, --debug           Enable debug
@@ -333,15 +370,19 @@ EOF
 }
 
 CMAKE_FILENAME="CMakeCache.txt"
+CTEST_FILENAME="CTestTestfile.cmake"
 DEBUG_FILENAME=".sbs_debug"
 VERBOSE_FILENAME=".sbs_verbose"
+BUILD_DEFAULT_DIRNAME="build"
+BUILD_HOST_DIRNAME="cmake-build-host"
 TTY_BLUE="\033[34m"
 TTY_BOLD="\033[1m"
 TTY_RESET="\033[0m"
 
 sbs_base=
 source_dir=
-build_dir=
+build_default_dir=
+build_host_dir=
 toolchain_file=
 found_cmake=false
 found_miosixgpp=false
@@ -355,8 +396,8 @@ found_stflash=false
 found_stlink=false
 config_debug=false
 config_verbose=false
+config_host=false
 jobs=
-lint_edit=false
 lint_clangtidy=false
 
 print_banner
@@ -377,24 +418,26 @@ for arg in "$@"; do
         --jobs)       set -- "$@" "-j";;
         --lint)       set -- "$@" "-n";;
         --list)       set -- "$@" "-l";;
+        --test)       set -- "$@" "-t";;
         --verbose)    set -- "$@" "-v";;
         *)            set -- "$@" "$arg"
     esac
 done
 
-while getopts b:cdef:hj:lnruv opt; do
+while getopts b:cdef:hj:lnrt:uv opt; do
     case "$opt" in
-        b) find_deps; build "$OPTARG"; exit;;
-        c) find_deps; clean; exit;;
+        b) find_deps; build "$build_default_dir" "$OPTARG"; exit;;
+        c) find_deps; clean_all; exit;;
         d) set_debug;;
-        e) find_deps; edit; exit;;
+        e) find_deps; lint true; exit;;
         f) find_deps; flash "$OPTARG"; exit;;
         h) usage; exit 0;;
         j) set_jobs "$OPTARG";;
         l) find_deps; list; exit;;
-        n) find_deps; lint; exit;;
+        n) find_deps; lint false; exit;;
         r) find_deps; boards; exit;;
-        u) find_deps; configure; exit;;
+        t) find_deps; run_tests "$OPTARG"; exit;;
+        u) find_deps; configure "$build_default_dir"; exit;;
         v) set_verbose;;
         ?) usage; exit 2;;
     esac