diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,34 @@
+Copyright (c) 2022 Torsten Schmits
+
+Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
+following conditions are met:
+
+  1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following
+  disclaimer.
+  2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
+  disclaimer in the documentation and/or other materials provided with the distribution.
+
+Subject to the terms and conditions of this license, each copyright holder and contributor hereby grants to those
+receiving rights under this license a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except
+for failure to satisfy the conditions of this license) patent license to make, have made, use, offer to sell, sell,
+import, and otherwise transfer this software, where such license applies only to those patent claims, already acquired
+or hereafter acquired, licensable by such copyright holder or contributor that are necessarily infringed by:
+
+  (a) their Contribution(s) (the licensed copyrights of copyright holders and non-copyrightable additions of
+  contributors, in source or binary form) alone; or
+  (b) combination of their Contribution(s) with the work of authorship to which such Contribution(s) was added by such
+  copyright holder or contributor, if, at the time the Contribution is added, such addition causes such combination to
+  be necessarily infringed. The patent license shall not apply to any other combinations which include the Contribution.
+
+Except as expressly stated above, no rights or licenses from any copyright holder or contributor is granted under this
+license, whether expressly, by implication, estoppel or otherwise.
+
+DISCLAIMER
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/lib/Ribosome/Host/Test/Data/TestConfig.hs b/lib/Ribosome/Host/Test/Data/TestConfig.hs
new file mode 100644
--- /dev/null
+++ b/lib/Ribosome/Host/Test/Data/TestConfig.hs
@@ -0,0 +1,14 @@
+module Ribosome.Host.Test.Data.TestConfig where
+
+import Ribosome.Host.Data.HostConfig (HostConfig (HostConfig), dataLogConc)
+
+data TestConfig =
+  TestConfig {
+    freezeTime :: Bool,
+    host :: HostConfig
+  }
+  deriving stock (Eq, Show, Generic)
+
+instance Default TestConfig where
+  def =
+    TestConfig False (HostConfig def { dataLogConc = False })
diff --git a/lib/Ribosome/Host/Test/Run.hs b/lib/Ribosome/Host/Test/Run.hs
new file mode 100644
--- /dev/null
+++ b/lib/Ribosome/Host/Test/Run.hs
@@ -0,0 +1,135 @@
+module Ribosome.Host.Test.Run where
+
+import qualified Chronos
+import Conc (GatesIO, Restoration, interpretGates, interpretMaskFinal, interpretRace, interpretUninterruptibleMaskFinal)
+import Hedgehog.Internal.Property (Failure)
+import Log (Severity (Debug, Trace, Warn), interpretLogStderrLevelConc)
+import Polysemy.Chronos (ChronosTime, interpretTimeChronos, interpretTimeChronosConstant)
+import Polysemy.Test (Hedgehog, Test, TestError (TestError), UnitTest, runTestAuto)
+import Time (mkDatetime)
+
+import Ribosome.Host.Data.BootError (BootError (unBootError))
+import Ribosome.Host.Data.HostConfig (setStderr)
+import Ribosome.Host.Data.RpcHandler (RpcHandler)
+import Ribosome.Host.Effect.Rpc (Rpc)
+import Ribosome.Host.Embed (HostEmbedStack, embedNvim, embedNvim_)
+import Ribosome.Host.IOStack (LogConfStack, interpretLogConfStack)
+import Ribosome.Host.Test.Data.TestConfig (TestConfig (TestConfig), host)
+
+type TestIOStack =
+  [
+    Log,
+    Mask Restoration,
+    UninterruptibleMask Restoration,
+    GatesIO,
+    Race,
+    Async,
+    Error BootError,
+    Test,
+    Fail,
+    Error TestError,
+    Hedgehog IO,
+    Error Failure,
+    Embed IO,
+    Resource,
+    Final IO
+  ]
+
+type TestConfStack =
+  LogConfStack ++ '[ChronosTime]
+
+type TestStack =
+  TestConfStack ++ TestIOStack
+
+type EmbedTestStack =
+  HostEmbedStack ++ TestStack
+
+testTime :: Chronos.Time
+testTime =
+  Chronos.datetimeToTime (mkDatetime 2025 6 15 12 30 30)
+
+runUnitTest ::
+  HasCallStack =>
+  Sem TestIOStack () ->
+  UnitTest
+runUnitTest =
+  runTestAuto .
+  mapError (TestError . unBootError) .
+  asyncToIOFinal .
+  interpretRace .
+  interpretGates .
+  interpretUninterruptibleMaskFinal .
+  interpretMaskFinal .
+  interpretLogStderrLevelConc (Just Warn)
+
+runTestLogConf ::
+  Members [Error BootError, Resource, Race, Async, Embed IO] r =>
+  TestConfig ->
+  InterpretersFor TestConfStack r
+runTestLogConf (TestConfig freezeTime conf) =
+  (if freezeTime then interpretTimeChronosConstant testTime else interpretTimeChronos) .
+  interpretLogConfStack conf
+
+runTestConf ::
+  HasCallStack =>
+  TestConfig ->
+  Sem TestStack () ->
+  UnitTest
+runTestConf conf =
+  runUnitTest .
+  runTestLogConf conf
+
+runTest ::
+  HasCallStack =>
+  Sem TestStack () ->
+  UnitTest
+runTest =
+  runTestConf def
+
+runTestLevel ::
+  HasCallStack =>
+  Severity ->
+  Sem TestStack () ->
+  UnitTest
+runTestLevel level =
+  runTestConf def { host = setStderr level def }
+
+runTestDebug ::
+  HasCallStack =>
+  Sem TestStack () ->
+  UnitTest
+runTestDebug =
+  runTestLevel Debug
+
+runTestTrace ::
+  HasCallStack =>
+  Sem TestStack () ->
+  UnitTest
+runTestTrace =
+  runTestLevel Trace
+
+embedTestConf ::
+  HasCallStack =>
+  TestConfig ->
+  [RpcHandler EmbedTestStack] ->
+  Sem (Rpc : EmbedTestStack) () ->
+  UnitTest
+embedTestConf conf handlers =
+  runTestConf conf .
+  embedNvim handlers
+
+embedTest ::
+  HasCallStack =>
+  [RpcHandler EmbedTestStack] ->
+  Sem (Rpc : EmbedTestStack) () ->
+  UnitTest
+embedTest =
+  embedTestConf def
+
+embedTest_ ::
+  HasCallStack =>
+  Sem (Rpc : EmbedTestStack) () ->
+  UnitTest
+embedTest_ =
+  runTest .
+  embedNvim_
diff --git a/ribosome-host-test.cabal b/ribosome-host-test.cabal
new file mode 100644
--- /dev/null
+++ b/ribosome-host-test.cabal
@@ -0,0 +1,104 @@
+cabal-version: 2.2
+
+-- This file has been generated from package.yaml by hpack version 0.34.6.
+--
+-- see: https://github.com/sol/hpack
+
+name:           ribosome-host-test
+version:        0.9.9.9
+synopsis:       Test tools for Ribosome
+description:    See https://hackage.haskell.org/package/ribosome-host-test/docs/Ribosome-Host-Test.html
+category:       Neovim
+author:         Torsten Schmits
+maintainer:     hackage@tryp.io
+copyright:      2022 Torsten Schmits
+license:        BSD-2-Clause-Patent
+license-file:   LICENSE
+build-type:     Simple
+
+library
+  exposed-modules:
+      Ribosome.Host.Test.Data.TestConfig
+      Ribosome.Host.Test.Run
+  hs-source-dirs:
+      lib
+  default-extensions:
+      AllowAmbiguousTypes
+      ApplicativeDo
+      BangPatterns
+      BinaryLiterals
+      BlockArguments
+      ConstraintKinds
+      DataKinds
+      DefaultSignatures
+      DeriveAnyClass
+      DeriveDataTypeable
+      DeriveFoldable
+      DeriveFunctor
+      DeriveGeneric
+      DeriveLift
+      DeriveTraversable
+      DerivingStrategies
+      DerivingVia
+      DisambiguateRecordFields
+      DoAndIfThenElse
+      DuplicateRecordFields
+      EmptyCase
+      EmptyDataDecls
+      ExistentialQuantification
+      FlexibleContexts
+      FlexibleInstances
+      FunctionalDependencies
+      GADTs
+      GeneralizedNewtypeDeriving
+      InstanceSigs
+      KindSignatures
+      LambdaCase
+      LiberalTypeSynonyms
+      MultiParamTypeClasses
+      MultiWayIf
+      NamedFieldPuns
+      OverloadedLabels
+      OverloadedLists
+      OverloadedStrings
+      PackageImports
+      PartialTypeSignatures
+      PatternGuards
+      PatternSynonyms
+      PolyKinds
+      QuantifiedConstraints
+      QuasiQuotes
+      RankNTypes
+      RecordWildCards
+      RecursiveDo
+      RoleAnnotations
+      ScopedTypeVariables
+      StandaloneDeriving
+      TemplateHaskell
+      TupleSections
+      TypeApplications
+      TypeFamilies
+      TypeFamilyDependencies
+      TypeOperators
+      TypeSynonymInstances
+      UndecidableInstances
+      UnicodeSyntax
+      ViewPatterns
+      StandaloneKindSignatures
+      OverloadedLabels
+  ghc-options: -Wall -Wredundant-constraints -Wincomplete-uni-patterns -Wmissing-deriving-strategies -Widentities -Wunused-packages -fplugin=Polysemy.Plugin
+  build-depends:
+      base >=4.12 && <5
+    , chronos
+    , hedgehog
+    , polysemy
+    , polysemy-chronos
+    , polysemy-plugin
+    , polysemy-test
+    , prelate >=0.1
+    , ribosome-host
+  mixins:
+      base hiding (Prelude)
+    , prelate (Prelate as Prelude)
+    , prelate hiding (Prelate)
+  default-language: Haskell2010
