diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,14 @@
 Changes
 =======
 
+Version 0.4.1.0
+---------------
+
+* Add `unliftio` lower bound (#106)
+* Change the tests back to a test-suite to avoid building for library users. (#107)
+* Fix up Windows compatibility.
+* Export `WatchConfig` type (#108)
+
 Version 0.4.0.1
 ---------------
 
diff --git a/fsnotify.cabal b/fsnotify.cabal
--- a/fsnotify.cabal
+++ b/fsnotify.cabal
@@ -1,11 +1,11 @@
 cabal-version: 1.12
 
--- This file has been generated from package.yaml by hpack version 0.34.4.
+-- This file has been generated from package.yaml by hpack version 0.35.0.
 --
 -- see: https://github.com/sol/hpack
 
 name:           fsnotify
-version:        0.4.0.1
+version:        0.4.1.0
 synopsis:       Cross platform library for file change notification.
 description:    Cross platform library for file creation, modification, and deletion notification. This library builds upon existing libraries for platform-specific Windows, Mac, and Linux filesystem event notification.
 category:       Filesystem
@@ -45,10 +45,11 @@
     , text >=0.11.0
     , time >=1.1
     , unix-compat >=0.2
+  default-language: Haskell2010
   if os(linux)
     cpp-options: -DOS_Linux
   if os(windows)
-    cpp-options: -DOS_Windows
+    cpp-options: -DOS_Win32
   if os(darwin)
     cpp-options: -DOS_Mac
   if os(freebsd) || os(netbsd) || os(openbsd)
@@ -73,9 +74,9 @@
         System.FSNotify.OSX
     build-depends:
         hfsevents >=0.1.3
-  default-language: Haskell2010
 
-executable tests
+test-suite tests
+  type: exitcode-stdio-1.0
   main-is: Main.hs
   other-modules:
       FSNotify.Test.EventTests
@@ -94,14 +95,14 @@
     , random
     , retry
     , safe-exceptions
-    , sandwich
     , temporary
     , unix-compat
-    , unliftio
+    , unliftio >=0.2.20
+  default-language: Haskell2010
   if os(linux)
     cpp-options: -DOS_Linux
   if os(windows)
-    cpp-options: -DOS_Windows
+    cpp-options: -DOS_Win32
   if os(darwin)
     cpp-options: -DOS_Mac
   if os(freebsd) || os(netbsd) || os(openbsd)
@@ -109,4 +110,7 @@
   if os(windows)
     build-depends:
         Win32
-  default-language: Haskell2010
+      , sandwich >=0.1.1.1
+  else
+    build-depends:
+        sandwich
diff --git a/src/System/FSNotify.hs b/src/System/FSNotify.hs
--- a/src/System/FSNotify.hs
+++ b/src/System/FSNotify.hs
@@ -2,9 +2,17 @@
 -- Copyright (c) 2012 Mark Dittmer - http://www.markdittmer.org
 -- Developed for a Google Summer of Code project - http://gsoc2012.markdittmer.org
 --
-{-# LANGUAGE CPP, ScopedTypeVariables, ExistentialQuantification, RankNTypes, LambdaCase, OverloadedStrings, MultiWayIf, FlexibleContexts, RecordWildCards, NamedFieldPuns #-}
+{-# LANGUAGE CPP #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE ExistentialQuantification #-}
+{-# LANGUAGE RankNTypes #-}
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE RecordWildCards #-}
+{-# LANGUAGE NamedFieldPuns #-}
 
--- | NOTE: This library does not currently report changes made to directories,
+-- | This library does not currently report changes made to directories,
 -- only files within watched directories.
 --
 -- Minimal example:
@@ -27,42 +35,40 @@
 -- >    -- sleep forever (until interrupted)
 -- >    forever $ threadDelay 1000000
 
-module System.FSNotify
-       (
-
-       -- * Events
-         Event(..)
-       , EventIsDirectory(..)
-       , EventChannel
-       , Action
-       , ActionPredicate
-
-       -- * Starting/Stopping
-       , WatchManager
-       , withManager
-       , startManager
-       , stopManager
+module System.FSNotify (
+  -- * Events
+    Event(..)
+  , EventIsDirectory(..)
+  , EventChannel
+  , Action
+  , ActionPredicate
 
-       -- * Configuration
-       , defaultConfig
-       , confWatchMode
-       , confThreadingMode
-       , confOnHandlerException
-       , WatchMode(..)
-       , ThreadingMode(..)
+  -- * Starting/Stopping
+  , WatchManager
+  , withManager
+  , startManager
+  , stopManager
 
-       -- * Lower level
-       , withManagerConf
-       , startManagerConf
-       , StopListening
+  -- * Configuration
+  , defaultConfig
+  , WatchConfig
+  , confWatchMode
+  , confThreadingMode
+  , confOnHandlerException
+  , WatchMode(..)
+  , ThreadingMode(..)
 
-       -- * Watching
-       , watchDir
-       , watchDirChan
-       , watchTree
-       , watchTreeChan
+  -- * Lower level
+  , withManagerConf
+  , startManagerConf
+  , StopListening
 
-       ) where
+  -- * Watching
+  , watchDir
+  , watchDirChan
+  , watchTree
+  , watchTreeChan
+  ) where
 
 import Prelude hiding (FilePath)
 
@@ -146,11 +152,11 @@
     Nothing -> return ()
     Just (_, t) -> cancel t
 
--- | Like 'withManager', but configurable
+-- | Like 'withManager', but configurable.
 withManagerConf :: WatchConfig -> (WatchManager -> IO a) -> IO a
 withManagerConf conf = bracket (startManagerConf conf) stopManager
 
--- | Like 'startManager', but configurable
+-- | Like 'startManager', but configurable.
 startManagerConf :: WatchConfig -> IO WatchManager
 startManagerConf conf = do
 # ifdef OS_Win32
diff --git a/src/System/FSNotify/Devel.hs b/src/System/FSNotify/Devel.hs
--- a/src/System/FSNotify/Devel.hs
+++ b/src/System/FSNotify/Devel.hs
@@ -17,10 +17,12 @@
 
 {-# LANGUAGE NamedFieldPuns #-}
 
-module System.FSNotify.Devel
-  ( treeExtAny, treeExtExists,
-    doAllEvents,
-    allEvents, existsEvents
+module System.FSNotify.Devel (
+  treeExtAny
+  , treeExtExists
+  , doAllEvents
+  , allEvents
+  , existsEvents
   ) where
 
 import Data.Text
diff --git a/src/System/FSNotify/Linux.hs b/src/System/FSNotify/Linux.hs
--- a/src/System/FSNotify/Linux.hs
+++ b/src/System/FSNotify/Linux.hs
@@ -14,10 +14,10 @@
 
 {-# OPTIONS_GHC -fno-warn-orphans #-}
 
-module System.FSNotify.Linux
-       ( FileListener(..)
-       , NativeManager
-       ) where
+module System.FSNotify.Linux (
+  FileListener(..)
+  , NativeManager
+  ) where
 
 import Control.Concurrent.MVar
 import Control.Exception.Safe as E
diff --git a/src/System/FSNotify/Listener.hs b/src/System/FSNotify/Listener.hs
--- a/src/System/FSNotify/Listener.hs
+++ b/src/System/FSNotify/Listener.hs
@@ -18,7 +18,7 @@
 import System.FSNotify.Types
 import System.FilePath
 
--- | An action that cancels a watching/listening job
+-- | An action that cancels a watching/listening job.
 type StopListening = IO ()
 
 type ListenFn sessionType argType = FileListener sessionType argType => WatchConfig -> sessionType -> FilePath -> ActionPredicate -> EventCallback -> IO StopListening
diff --git a/src/System/FSNotify/OSX.hs b/src/System/FSNotify/OSX.hs
--- a/src/System/FSNotify/OSX.hs
+++ b/src/System/FSNotify/OSX.hs
@@ -3,12 +3,14 @@
 -- Developed for a Google Summer of Code project - http://gsoc2012.markdittmer.org
 --
 
-{-# LANGUAGE MultiWayIf, OverloadedStrings, MultiParamTypeClasses #-}
+{-# LANGUAGE MultiWayIf #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
 
-module System.FSNotify.OSX
-       ( FileListener(..)
-       , NativeManager
-       ) where
+module System.FSNotify.OSX (
+  FileListener(..)
+  , NativeManager
+  ) where
 
 import Control.Concurrent
 import Control.Monad
diff --git a/src/System/FSNotify/Path.hs b/src/System/FSNotify/Path.hs
--- a/src/System/FSNotify/Path.hs
+++ b/src/System/FSNotify/Path.hs
@@ -7,13 +7,13 @@
 {-# LANGUAGE FlexibleInstances #-}
 {-# LANGUAGE CPP #-}
 
-module System.FSNotify.Path
-       ( findFiles
-       , findFilesAndDirs
-       , canonicalizeDirPath
-       , canonicalizePath
-       , hasThisExtension
-       ) where
+module System.FSNotify.Path (
+  findFiles
+  , findFilesAndDirs
+  , canonicalizeDirPath
+  , canonicalizePath
+  , hasThisExtension
+  ) where
 
 import Control.Monad
 import qualified Data.Text as T
diff --git a/src/System/FSNotify/Polling.hs b/src/System/FSNotify/Polling.hs
--- a/src/System/FSNotify/Polling.hs
+++ b/src/System/FSNotify/Polling.hs
@@ -7,8 +7,8 @@
 -- Developed for a Google Summer of Code project - http://gsoc2012.markdittmer.org
 --
 
-module System.FSNotify.Polling
-  ( createPollManager
+module System.FSNotify.Polling (
+  createPollManager
   , PollManager(..)
   , FileListener(..)
   ) where
@@ -30,6 +30,7 @@
 import System.PosixCompat.Files
 import System.PosixCompat.Types
 
+
 data EventType = AddedEvent
                | ModifiedEvent
                | RemovedEvent
@@ -37,8 +38,10 @@
 newtype WatchKey = WatchKey ThreadId deriving (Eq, Ord)
 data WatchData = WatchData FilePath EventCallback
 type WatchMap = Map WatchKey WatchData
-data PollManager = PollManager { pollManagerWatchMap :: MVar WatchMap
-                               , pollManagerInterval :: Int }
+data PollManager = PollManager {
+  pollManagerWatchMap :: MVar WatchMap
+  , pollManagerInterval :: Int
+  }
 
 generateEvent :: UTCTime -> EventIsDirectory -> EventType -> FilePath -> Maybe Event
 generateEvent timestamp isDir AddedEvent filePath = Just (Added filePath timestamp isDir)
diff --git a/src/System/FSNotify/Types.hs b/src/System/FSNotify/Types.hs
--- a/src/System/FSNotify/Types.hs
+++ b/src/System/FSNotify/Types.hs
@@ -4,21 +4,21 @@
 --
 {-# LANGUAGE CPP #-}
 
-module System.FSNotify.Types
-       ( act
-       , ActionPredicate
-       , Action
-       , DebounceFn
-       , WatchConfig(..)
-       , WatchMode(..)
-       , ThreadingMode(..)
-       , Event(..)
-       , EventIsDirectory(..)
-       , EventCallback
-       , EventChannel
-       , EventAndActionChannel
-       , IOEvent
-       ) where
+module System.FSNotify.Types (
+  act
+  , ActionPredicate
+  , Action
+  , DebounceFn
+  , WatchConfig(..)
+  , WatchMode(..)
+  , ThreadingMode(..)
+  , Event(..)
+  , EventIsDirectory(..)
+  , EventCallback
+  , EventChannel
+  , EventAndActionChannel
+  , IOEvent
+  ) where
 
 import Control.Concurrent.Chan
 import Control.Exception.Safe
@@ -40,11 +40,11 @@
   | ModifiedAttributes { eventPath :: FilePath, eventTime :: UTCTime, eventIsDirectory :: EventIsDirectory }
   | Removed { eventPath :: FilePath, eventTime :: UTCTime, eventIsDirectory :: EventIsDirectory }
   | WatchedDirectoryRemoved  { eventPath :: FilePath, eventTime :: UTCTime, eventIsDirectory :: EventIsDirectory }
-  -- ^ Note: currently only emitted on Linux
+  -- ^ Note: Linux-only
   | CloseWrite  { eventPath :: FilePath, eventTime :: UTCTime, eventIsDirectory :: EventIsDirectory }
-  -- ^ Note: currently only emitted on Linux
+  -- ^ Note: Linux-only
   | Unknown  { eventPath :: FilePath, eventTime :: UTCTime, eventIsDirectory :: EventIsDirectory, eventString :: String }
-  -- ^ Note: currently only emitted on Linux
+  -- ^ Note: Linux-only
   deriving (Eq, Show)
 
 type EventChannel = Chan Event
@@ -73,14 +73,14 @@
   | ThreadPerEvent
   -- ^ Launch a separate thread for every event handler.
 
--- | Watch configuration
+-- | Watch configuration.
 data WatchConfig = WatchConfig
   { confWatchMode :: WatchMode
-    -- ^ Watch mode to use
+    -- ^ Watch mode to use.
   , confThreadingMode :: ThreadingMode
-    -- ^ Threading mode to use
+    -- ^ Threading mode to use.
   , confOnHandlerException :: SomeException -> IO ()
-    -- ^ Called when a handler throws an exception
+    -- ^ Called when a handler throws an exception.
   }
 
 type IOEvent = IORef Event
diff --git a/src/System/FSNotify/Win32.hs b/src/System/FSNotify/Win32.hs
--- a/src/System/FSNotify/Win32.hs
+++ b/src/System/FSNotify/Win32.hs
@@ -5,10 +5,10 @@
 {-# LANGUAGE MultiParamTypeClasses #-}
 {-# OPTIONS_GHC -fno-warn-orphans #-}
 
-module System.FSNotify.Win32
-       ( FileListener(..)
-       , NativeManager
-       ) where
+module System.FSNotify.Win32 (
+  FileListener(..)
+  , NativeManager
+  ) where
 
 import Control.Concurrent
 import Control.Monad (when)
diff --git a/test/FSNotify/Test/EventTests.hs b/test/FSNotify/Test/EventTests.hs
--- a/test/FSNotify/Test/EventTests.hs
+++ b/test/FSNotify/Test/EventTests.hs
@@ -1,4 +1,3 @@
-{-# LANGUAGE CPP #-}
 {-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE ImplicitParams #-}
 {-# LANGUAGE MultiWayIf #-}
@@ -25,13 +24,9 @@
 import UnliftIO.Directory
 
 
-eventTests :: (MonadUnliftIO m, MonadThrow m, HasParallelSemaphore context) => ThreadingMode -> SpecFree context m ()
+eventTests :: (MonadUnliftIO m, MonadThrow m, HasParallelSemaphore' context) => ThreadingMode -> SpecFree context m ()
 eventTests threadingMode = describe "Tests" $ parallel $ do
-#ifdef OS_BSD
-  let pollOptions = [True]
-#else
-  let pollOptions = [False, True]
-#endif
+  let pollOptions = if isBSD then [True] else [False, True]
 
   forM_ pollOptions $ \poll -> describe (if poll then "Polling" else "Native") $ parallel $ do
     let ?timeInterval = if poll then 2*10^(6 :: Int) else 5*10^(5 :: Int)
@@ -41,7 +36,7 @@
 
 
 
-eventTests' :: (MonadUnliftIO m, MonadThrow m, HasParallelSemaphore context, ?timeInterval :: Int) => ThreadingMode -> Bool -> Bool -> Bool -> SpecFree context m ()
+eventTests' :: (MonadUnliftIO m, MonadThrow m, HasParallelSemaphore' context, ?timeInterval :: Int) => ThreadingMode -> Bool -> Bool -> Bool -> SpecFree context m ()
 eventTests' threadingMode poll recursive nested = do -- withParallelSemaphore $
   let itWithFolder name action = introduceTestFolder threadingMode poll recursive nested $ it name action
 
@@ -55,11 +50,16 @@
 
   itWithFolder "works with a new file" $ do
     TestFolderContext _watchedDir f getEvents _clearEvents <- getContext testFolderContext
-    h <- openFile f AppendMode
 
-    flip finally (hClose h) $
+    let wrapper action = if | isWin -> liftIO (writeFile f "foo") >> action
+                            | otherwise -> withFile f AppendMode $ \_ -> action
+
+    wrapper $
       pauseAndRetryOnExpectationFailure 3 $ liftIO getEvents >>= \events ->
         if | nested && not recursive -> events `shouldBe` []
+           | isWin && not poll -> case events of
+               [Modified {}, Added {..}] | eventPath `equalFilePath` f && eventIsDirectory == IsFile -> return ()
+               _ -> expectationFailure $ "Got wrong events: " <> show events
            | otherwise -> case events of
                [Added {..}] | eventPath `equalFilePath` f && eventIsDirectory == IsFile -> return ()
                _ -> expectationFailure $ "Got wrong events: " <> show events
@@ -109,40 +109,29 @@
     pauseAndRetryOnExpectationFailure 3 $ liftIO getEvents >>= \events ->
       if | poll -> return ()
          | nested && not recursive -> events `shouldBe` []
-         | otherwise -> case events of
-#ifdef mingw32_HOST_OS
+         | isWin -> case events of
              [Modified {..}] | eventPath `equalFilePath` f && eventIsDirectory == IsFile -> return ()
-#else
+             _ -> expectationFailure $ "Got wrong events: " <> show events
+         | otherwise -> case events of
              [ModifiedAttributes {..}] | eventPath `equalFilePath` f && eventIsDirectory == IsFile -> return ()
-#endif
              _ -> expectationFailure $ "Got wrong events: " <> show events
 
   itWithFolder "works with a modified file" $ do
     TestFolderContext _watchedDir f getEvents clearEvents <- getContext testFolderContext
     liftIO (writeFile f "" >> clearEvents)
 
-#ifdef mingw32_HOST_OS
-    writeFile f "foo"
-    do
-#else
-    withFile f WriteMode $ \h ->
-      flip finally (hClose h) $ do
-        liftIO $ hPutStr h "foo"
-#endif
-
-        pauseAndRetryOnExpectationFailure 3 $ liftIO getEvents >>= \events ->
-          if | nested && not recursive -> events `shouldBe` []
-             | otherwise -> case events of
-#ifdef darwin_HOST_OS
-                 [Modified {..}] | poll && eventPath `equalFilePath` f && eventIsDirectory == IsFile -> return ()
-                 [ModifiedAttributes {..}] | not poll && eventPath `equalFilePath` f && eventIsDirectory == IsFile -> return ()
-#else
-                 [Modified {..}] | eventPath `equalFilePath` f && eventIsDirectory == IsFile -> return ()
-#endif
-                 _ -> expectationFailure $ "Got wrong events: " <> show events <> " (wanted file path " <> show f <> ")"
+    (if isWin then withSingleWriteFile f "foo" else withOpenWritableAndWrite f "foo") $
+      pauseAndRetryOnExpectationFailure 3 $ liftIO getEvents >>= \events ->
+        if | nested && not recursive -> events `shouldBe` []
+           | isMac -> case events of
+               [Modified {..}] | poll && eventPath `equalFilePath` f && eventIsDirectory == IsFile -> return ()
+               [ModifiedAttributes {..}] | not poll && eventPath `equalFilePath` f && eventIsDirectory == IsFile -> return ()
+               _ -> expectationFailure $ "Got wrong events: " <> show events <> " (wanted file path " <> show f <> ")"
+           | otherwise -> case events of
+               [Modified {..}] | eventPath `equalFilePath` f && eventIsDirectory == IsFile -> return ()
+               _ -> expectationFailure $ "Got wrong events: " <> show events <> " (wanted file path " <> show f <> ")"
 
-#ifdef linux_HOST_OS
-  unless poll $
+  when isLinux $ unless poll $
     itWithFolder "gets a close_write" $ do
       TestFolderContext _watchedDir f getEvents clearEvents <- getContext testFolderContext
       liftIO (writeFile f "" >> clearEvents)
@@ -157,4 +146,15 @@
                  | eventPath cw `equalFilePath` f && eventIsDirectory cw == IsFile
                    && eventPath m `equalFilePath` f && eventIsDirectory m == IsFile -> return ()
                _ -> expectationFailure $ "Got wrong events: " <> show events
-#endif
+
+withSingleWriteFile :: MonadIO m => FilePath -> String -> m b -> m b
+withSingleWriteFile fp contents action = do
+  liftIO $ writeFile fp contents
+  action
+
+withOpenWritableAndWrite :: MonadUnliftIO m => FilePath -> String -> m b -> m b
+withOpenWritableAndWrite fp contents action = do
+  withFile fp WriteMode $ \h ->
+    flip finally (hClose h) $ do
+      liftIO $ hPutStr h contents
+      action
diff --git a/test/FSNotify/Test/Util.hs b/test/FSNotify/Test/Util.hs
--- a/test/FSNotify/Test/Util.hs
+++ b/test/FSNotify/Test/Util.hs
@@ -6,6 +6,7 @@
 {-# LANGUAGE TypeOperators #-}
 {-# LANGUAGE ViewPatterns #-}
 {-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE ConstraintKinds #-}
 
 module FSNotify.Test.Util where
 
@@ -54,6 +55,20 @@
 isWin = False
 #endif
 
+isLinux :: Bool
+#ifdef linux_HOST_OS
+isLinux = True
+#else
+isLinux = False
+#endif
+
+isBSD :: Bool
+#ifdef OS_BSD
+isBSD = True
+#else
+isBSD = False
+#endif
+
 pauseAndRetryOnExpectationFailure :: (MonadUnliftIO m, ?timeInterval :: Int) => Int -> m a -> m a
 pauseAndRetryOnExpectationFailure n action = threadDelay ?timeInterval >> retryOnExpectationFailure n action
 
@@ -119,12 +134,17 @@
 -- This is unfortunately necessary because of the madness of OS X FSEvents; see the comments in OSX.hs
 withRandomTempDirectory :: MonadUnliftIO m => (FilePath -> m ()) -> m ()
 withRandomTempDirectory action = do
-  randomID <- replicateM 10 $ R.randomRIO ('a', 'z')
+  randomID <- liftIO $ replicateM 10 $ R.randomRIO ('a', 'z')
   withSystemTempDirectory ("test." <> randomID) action
 
 withParallelSemaphore :: forall context m. (
-  MonadUnliftIO m, HasParallelSemaphore context
+  MonadUnliftIO m, HasLabel context "parallelSemaphore" QSem
   ) => SpecFree context m () -> SpecFree context m ()
 withParallelSemaphore = around' (defaultNodeOptions { nodeOptionsRecordTime = False, nodeOptionsVisibilityThreshold = 125 }) "claim semaphore" $ \action -> do
-  s <- getContext parallelSemaphore
+  s <- getContext parallelSemaphore'
   bracket_ (liftIO $ waitQSem s) (liftIO $ signalQSem s) (void action)
+
+parallelSemaphore' :: Label "parallelSemaphore" QSem
+parallelSemaphore' = Label
+
+type HasParallelSemaphore' context = HasLabel context "parallelSemaphore" QSem
