diff --git a/cabal.project b/cabal.project
index e6fdbadb4398bc0e333947b5fb8021778310d943..586737c7e0d2ca9205e6f633461b748c522f905b 100644
--- a/cabal.project
+++ b/cabal.project
@@ -1 +1,5 @@
 packages: .
+tests: True
+
+constraints:
+  tasty -unix, optparse-applicative -process
diff --git a/tests/.gitignore b/tests/.gitignore
deleted file mode 100644
index eefd6d45c7f3063d373c8ecba50951dfd5026575..0000000000000000000000000000000000000000
--- a/tests/.gitignore
+++ /dev/null
@@ -1,42 +0,0 @@
-.hpc*/
-*.o
-*.hi
-*.comp.std*
-*.run.std*
-*.eventlog
-*.genscript
-*.exe
-*.interp.stderr
-*.interp.stdout
-
-# specific files
-/T1185
-/T3816
-/T8108
-/executeFile001
-/fdReadBuf001
-/fileStatus
-/fileStatusByteString
-/fileexist01
-/forkprocess01
-/getEnvironment01
-/getEnvironment02
-/getGroupEntryForName
-/getUserEntryForName
-/libposix/po003.out
-/libposix/posix002
-/libposix/posix003
-/libposix/posix004
-/libposix/posix005
-/libposix/posix006
-/libposix/posix009
-/libposix/posix010
-/libposix/posix014
-/processGroup001
-/processGroup002
-/queryfdoption01
-/resourceLimit
-/signals001
-/signals002
-/signals004
-/user001
diff --git a/tests/fdReadBuf001.hs b/tests/FdReadBuf001.hs
similarity index 82%
rename from tests/fdReadBuf001.hs
rename to tests/FdReadBuf001.hs
index f987c94091e9b2a5e4a4c8561c5ff1ba18bfc837..3135c3e76a02ea0feb1b9707203c8db4ce750ba7 100644
--- a/tests/fdReadBuf001.hs
+++ b/tests/FdReadBuf001.hs
@@ -1,4 +1,5 @@
-{-# LANGUAGE NoMonomorphismRestriction #-}
+module Main where
+
 import System.Posix
 import Control.Monad
 import Foreign
@@ -6,19 +7,19 @@ import Control.Concurrent
 import Data.Char
 import System.Exit
 
-size  = 10000
-block = 512
-
+main :: IO ()
 main = do
+  let size  = 10000
+      block = 512
   (rd,wr) <- createPipe
   let bytes = take size (map (fromIntegral.ord) (cycle ['a'..'z']))
-  allocaBytes size $ \p -> do
+  _ <- allocaBytes size $ \p -> do
     pokeArray p bytes
     forkIO $ do r <- fdWriteBuf wr p (fromIntegral size)
                 when (fromIntegral r /= size) $ error "fdWriteBuf failed"
   allocaBytes block $ \p -> do
     let loop text = do
-           r <- fdReadBuf rd p block
+           r <- fdReadBuf rd p (fromIntegral block)
            let (chunk,rest) = splitAt (fromIntegral r) text
            chars <- peekArray (fromIntegral r) p
            when (chars /= chunk) $ error $ "mismatch: expected="++show chunk++", found="++show chars
diff --git a/tests/fileStatus.hs b/tests/FileStatus.hs
similarity index 95%
rename from tests/fileStatus.hs
rename to tests/FileStatus.hs
index 7311421700d33730186c2ed5104dc40644b04818..bdd5f0436cceb33ffa9e70c6ca2bd5700caa92fc 100644
--- a/tests/fileStatus.hs
+++ b/tests/FileStatus.hs
@@ -1,6 +1,10 @@
 
 -- GHC trac #2969
 
+{-# OPTIONS_GHC -fno-warn-missing-signatures #-}
+
+module FileStatus (main) where
+
 import System.Posix.Files
 import System.Posix.Directory
 import System.Posix.IO
@@ -20,7 +24,7 @@ link_regular = "link-regular"
 link_dir     = "link-dir"
 
 testRegular = do
-  createFile regular ownerReadMode
+  _ <- createFile regular ownerReadMode
   (fs, _) <- getStatus regular
   let expected = (False,False,False,True,False,False,False)
       actual   = snd (statusElements fs)
diff --git a/tests/fileStatusByteString.hs b/tests/FileStatusByteString.hs
similarity index 95%
rename from tests/fileStatusByteString.hs
rename to tests/FileStatusByteString.hs
index 0125363446453db034357e53d2c6a0dfc796cca6..ed8bbd8ef24eab8b87bb17a1262f49dde7d6fd62 100644
--- a/tests/fileStatusByteString.hs
+++ b/tests/FileStatusByteString.hs
@@ -1,5 +1,9 @@
 {-# LANGUAGE OverloadedStrings #-}
 
+{-# OPTIONS_GHC -fno-warn-missing-signatures #-}
+
+module FileStatusByteString (main) where
+
 -- GHC trac #2969
 
 import System.Posix.ByteString
@@ -19,7 +23,7 @@ link_regular = "link-regular2"
 link_dir     = "link-dir2"
 
 testRegular = do
-  createFile regular ownerReadMode
+  _ <- createFile regular ownerReadMode
   (fs, _) <- getStatus regular
   let expected = (False,False,False,True,False,False,False)
       actual   = snd (statusElements fs)
diff --git a/tests/ForkProcess01.hs b/tests/ForkProcess01.hs
new file mode 100644
index 0000000000000000000000000000000000000000..8ddaec26c4ee6ca05bcec9bbd4eae7cdad34edcb
--- /dev/null
+++ b/tests/ForkProcess01.hs
@@ -0,0 +1,17 @@
+-- Test that we can call exitFailure in a forked process, and have it
+-- communicated properly to the parent.
+
+module Main where
+
+import Control.Monad
+import System.Exit
+import System.Posix.Process
+
+main :: IO ()
+main = do
+  let exitCode = ExitFailure 72
+      expected = Just (Exited exitCode)
+  p <- forkProcess $ exitWith exitCode
+  actual <- getProcessStatus True False p
+  when (actual /= expected) $
+    error $ "mismatch: expected = " ++ show expected ++ ", actual = " ++ show actual
diff --git a/tests/Makefile b/tests/Makefile
deleted file mode 100644
index 6a0abcf1cf7f79f47ac3db01eec1eb9ff6ff7b45..0000000000000000000000000000000000000000
--- a/tests/Makefile
+++ /dev/null
@@ -1,7 +0,0 @@
-# This Makefile runs the tests using GHC's testsuite framework.  It
-# assumes the package is part of a GHC build tree with the testsuite
-# installed in ../../../testsuite.
-
-TOP=../../../testsuite
-include $(TOP)/mk/boilerplate.mk
-include $(TOP)/mk/test.mk
diff --git a/tests/libposix/posix004.hs b/tests/Posix004.hs
similarity index 54%
rename from tests/libposix/posix004.hs
rename to tests/Posix004.hs
index 56c16f02dc1262da10b7519783554fda2aaff2f6..da949ffe19ee0f6ded629420e73f707bd502fb33 100644
--- a/tests/libposix/posix004.hs
+++ b/tests/Posix004.hs
@@ -1,48 +1,54 @@
+module Main where
 
-import System.Exit (ExitCode(..), exitWith)
+import System.Exit
 import System.Posix.Process
 import System.Posix.Signals
 
-main = do test1
-          test2
-          test3
-          test4
-          putStrLn "I'm happy."
+main :: IO ()
+main = do
+    test1
+    test2
+    test3
+    test4
 
+test1 :: IO ()
 test1 = do
     -- Force SIGFPE exceptions to not be ignored.  Under some
     -- circumstances this test will be run with SIGFPE
     -- ignored, see #7399
-    installHandler sigFPE Default Nothing
-    forkProcess $ raiseSignal floatingPointException
-    Just (pid, tc) <- getAnyProcessStatus True False
+    _ <- installHandler sigFPE Default Nothing
+    _ <- forkProcess $ raiseSignal floatingPointException
+    Just (_, tc) <- getAnyProcessStatus True False
     case tc of
         Terminated sig _ | sig == floatingPointException -> return ()
         _ -> error "unexpected termination cause"
 
+test2 :: IO ()
 test2 = do
-    forkProcess $ exitImmediately (ExitFailure 42)
-    Just (pid, tc) <- getAnyProcessStatus True False
+    _ <- forkProcess $ exitImmediately (ExitFailure 42)
+    Just (_, tc) <- getAnyProcessStatus True False
     case tc of
         Exited (ExitFailure 42) -> return ()
         _ -> error "unexpected termination cause (2)"
 
+test3 :: IO ()
 test3 = do
-    forkProcess $ exitImmediately ExitSuccess
-    Just (pid, tc) <- getAnyProcessStatus True False
+    _ <- forkProcess $ exitImmediately ExitSuccess
+    Just (_, tc) <- getAnyProcessStatus True False
     case tc of
         Exited ExitSuccess -> return ()
         _ -> error "unexpected termination cause (3)"
 
+test4 :: IO ()
 test4 = do
-    forkProcess $ raiseSignal softwareStop
+    _ <- forkProcess $ raiseSignal softwareStop
     Just (pid, tc) <- getAnyProcessStatus True True
     case tc of
         Stopped sig | sig == softwareStop -> do
             signalProcess killProcess pid
-            Just (pid, tc) <- getAnyProcessStatus True True
-            case tc of
-                Terminated sig _ | sig == killProcess -> return ()
+            Just (_, tc') <- getAnyProcessStatus True True
+            case tc' of
+                Terminated sig' _ | sig' == killProcess -> return ()
                 _ -> error "unexpected termination cause (5)"
         _ -> error "unexpected termination cause (4)"
 
diff --git a/tests/Posix009.hs b/tests/Posix009.hs
new file mode 100644
index 0000000000000000000000000000000000000000..9fbfe142e8b148cea4e86211f47f97debc817638
--- /dev/null
+++ b/tests/Posix009.hs
@@ -0,0 +1,21 @@
+{-# OPTIONS_GHC -fno-warn-deprecations #-}
+
+module Main where
+
+import Control.Monad
+import System.Posix.Signals
+import System.Posix.Unistd
+
+main :: IO ()
+main = do
+  putStrLn "Blocking real time alarms."
+  blockSignals (addSignal realTimeAlarm reservedSignals)
+  putStrLn "Scheduling an alarm in 2 seconds..."
+  _ <- scheduleAlarm 2
+  putStrLn "Sleeping 5 seconds."
+  _ <- sleep 5
+  putStrLn "Woken up"
+  ints <- getPendingSignals
+  putStrLn "Checking pending interrupts for RealTimeAlarm"
+  unless (inSignalSet realTimeAlarm ints) $
+    error "should have a pending real time alarm"
diff --git a/tests/Posix014.hs b/tests/Posix014.hs
new file mode 100644
index 0000000000000000000000000000000000000000..81047027b3082bcf90ea2422771e9e74af34261b
--- /dev/null
+++ b/tests/Posix014.hs
@@ -0,0 +1,14 @@
+-- !! Basic pipe usage
+module Main (main) where
+
+import Control.Monad
+import System.Posix
+
+main :: IO ()
+main = do
+  let str = "Hi, there - forked child calling"
+  (rd, wd) <- createPipe
+  _ <- forkProcess $ void $ fdWrite wd str
+  (str', _) <- fdRead rd (fromIntegral (length str))
+  unless (str == str') $
+    error "should have received an identical string"
diff --git a/tests/Signals001.hs b/tests/Signals001.hs
new file mode 100644
index 0000000000000000000000000000000000000000..9661ce736149549b9398e7a75fbf46fe319007dc
--- /dev/null
+++ b/tests/Signals001.hs
@@ -0,0 +1,118 @@
+{-# LANGUAGE CPP #-}
+
+{-# OPTIONS_GHC -fno-warn-missing-signatures #-}
+
+module Signals001 (main) where
+
+import Control.Monad
+import System.Posix.Signals
+
+#include "ghcconfig.h"
+
+main :: IO ()
+main = do
+  forM_ (filter id $ testMembers emptySignalSet) $ \_ ->
+    fail "should be False"
+  forM_ (filter id $ testMembers emptyset) $ \_ ->
+    fail "should be False"
+  forM_ (filter not $ testMembers fullSignalSet) $ \_ ->
+    fail "should be True"
+  forM_ (filter not $ testMembers fullset) $ \_ ->
+    fail "should be True"
+
+fullset = internalAbort `addSignal`
+    realTimeAlarm `addSignal`
+    busError `addSignal`
+    processStatusChanged `addSignal`
+    continueProcess `addSignal`
+    floatingPointException `addSignal`
+    lostConnection `addSignal`
+    illegalInstruction `addSignal`
+    keyboardSignal `addSignal`
+    killProcess `addSignal`
+    openEndedPipe `addSignal`
+    keyboardTermination `addSignal`
+    segmentationViolation `addSignal`
+    softwareStop `addSignal`
+    softwareTermination `addSignal`
+    keyboardStop `addSignal`
+    backgroundRead `addSignal`
+    backgroundWrite `addSignal`
+    userDefinedSignal1 `addSignal`
+    userDefinedSignal2 `addSignal`
+#if HAVE_SIGPOLL
+    pollableEvent `addSignal`
+#endif
+    profilingTimerExpired `addSignal`
+    badSystemCall `addSignal`
+    breakpointTrap `addSignal`
+    urgentDataAvailable `addSignal`
+    virtualTimerExpired `addSignal`
+    cpuTimeLimitExceeded `addSignal`
+    fileSizeLimitExceeded `addSignal`
+    emptySignalSet
+
+emptyset = internalAbort `deleteSignal`
+    realTimeAlarm `deleteSignal`
+    busError `deleteSignal`
+    processStatusChanged `deleteSignal`
+    continueProcess `deleteSignal`
+    floatingPointException `deleteSignal`
+    lostConnection `deleteSignal`
+    illegalInstruction `deleteSignal`
+    keyboardSignal `deleteSignal`
+    killProcess `deleteSignal`
+    openEndedPipe `deleteSignal`
+    keyboardTermination `deleteSignal`
+    segmentationViolation `deleteSignal`
+    softwareStop `deleteSignal`
+    softwareTermination `deleteSignal`
+    keyboardStop `deleteSignal`
+    backgroundRead `deleteSignal`
+    backgroundWrite `deleteSignal`
+    userDefinedSignal1 `deleteSignal`
+    userDefinedSignal2 `deleteSignal`
+#if HAVE_SIGPOLL
+    pollableEvent `deleteSignal`
+#endif
+    profilingTimerExpired `deleteSignal`
+    badSystemCall `deleteSignal`
+    breakpointTrap `deleteSignal`
+    urgentDataAvailable `deleteSignal`
+    virtualTimerExpired `deleteSignal`
+    cpuTimeLimitExceeded `deleteSignal`
+    fileSizeLimitExceeded `deleteSignal`
+    fullSignalSet
+
+testMembers set = [
+    internalAbort `inSignalSet` set,
+    realTimeAlarm `inSignalSet` set,
+    busError `inSignalSet` set,
+    processStatusChanged `inSignalSet` set,
+    continueProcess `inSignalSet` set,
+    floatingPointException `inSignalSet` set,
+    lostConnection `inSignalSet` set,
+    illegalInstruction `inSignalSet` set,
+    keyboardSignal `inSignalSet` set,
+    killProcess `inSignalSet` set,
+    openEndedPipe `inSignalSet` set,
+    keyboardTermination `inSignalSet` set,
+    segmentationViolation `inSignalSet` set,
+    softwareStop `inSignalSet` set,
+    softwareTermination `inSignalSet` set,
+    keyboardStop `inSignalSet` set,
+    backgroundRead `inSignalSet` set,
+    backgroundWrite `inSignalSet` set,
+    userDefinedSignal1 `inSignalSet` set,
+    userDefinedSignal2 `inSignalSet` set,
+#if HAVE_SIGPOLL
+    pollableEvent `inSignalSet` set,
+#endif
+    profilingTimerExpired `inSignalSet` set,
+    badSystemCall `inSignalSet` set,
+    breakpointTrap `inSignalSet` set,
+    urgentDataAvailable `inSignalSet` set,
+    virtualTimerExpired `inSignalSet` set,
+    cpuTimeLimitExceeded `inSignalSet` set,
+    fileSizeLimitExceeded `inSignalSet` set
+    ]
diff --git a/tests/signals002.hs b/tests/Signals002.hs
similarity index 53%
rename from tests/signals002.hs
rename to tests/Signals002.hs
index b2e6e5e422da6794a5ef136e8fbb8fa5b29820f6..3a2dbf20e010d33166355188236c5945a27bb0b4 100644
--- a/tests/signals002.hs
+++ b/tests/Signals002.hs
@@ -1,15 +1,20 @@
-import System.Posix
+module Main where
+
 import Control.Concurrent
+import Control.Monad
+import System.Posix
 
 -- !!! test blockSignals, raiseSignal, unblockSignals, getPendingSignals
 
+main :: IO ()
 main = do
   blockSignals ( userDefinedSignal1 `addSignal` emptySignalSet )
   raiseSignal userDefinedSignal1
   set <- getPendingSignals
-  print (userDefinedSignal1 `inSignalSet` set)
-  m <- newEmptyMVar 
-  installHandler userDefinedSignal1 
-	(Catch (putStrLn "hello" >> putMVar m ())) Nothing
+  unless (userDefinedSignal1 `inSignalSet` set) $
+    fail "signal is missing from the set"
+  m <- newEmptyMVar
+  _ <- installHandler userDefinedSignal1
+    (Catch (putStrLn "hello" >> putMVar m ())) Nothing
   awaitSignal (Just emptySignalSet)
   takeMVar m
diff --git a/tests/signals004.hs b/tests/Signals004.hs
similarity index 85%
rename from tests/signals004.hs
rename to tests/Signals004.hs
index d822056dbebf5d36418e4528e5ef873ca07cf0b9..6dcbb422ad7967d33e79d71f3cfca4cdeb9dab0e 100644
--- a/tests/signals004.hs
+++ b/tests/Signals004.hs
@@ -1,3 +1,7 @@
+{-# OPTIONS_GHC -fno-warn-missing-signatures #-}
+
+module Main where
+
 import Control.Concurrent
 import System.Posix
 import Control.Monad
@@ -13,7 +17,7 @@ sigs = 400
 main = do
   c <- newChan
   m <- newEmptyMVar
-  installHandler sigUSR1 (handler c) Nothing
+  _ <- installHandler sigUSR1 (handler c) Nothing
   replicateM_ installers (forkIO $ do replicateM_ 1000 (install c); putMVar m ())
   replicateM_ sigs (forkIO $ raiseSignal sigUSR1)
   replicateM_ installers (takeMVar m)
diff --git a/tests/T1185.hs b/tests/T1185.hs
deleted file mode 100644
index 494841797481f019e21c326c1eea5390e1b11cd1..0000000000000000000000000000000000000000
--- a/tests/T1185.hs
+++ /dev/null
@@ -1,24 +0,0 @@
-module Main where
-
-import Control.Concurrent
-import System.Posix
-import System.IO
-import System.Exit
-
-main =
-    do putStrLn "running..."
-       (stdinr, stdinw) <- createPipe
-       (stdoutr, stdoutw) <- createPipe
-       pid <- forkProcess $ do hw <- fdToHandle stdoutw
-                               hr <- fdToHandle stdinr
-                               closeFd stdinw
-                               hGetContents hr >>= hPutStr hw
-                               hClose hr
-                               hClose hw
-                               exitImmediately ExitSuccess
-       threadDelay 100000
-       closeFd stdoutw
-       closeFd stdinw
-       hr2 <- fdToHandle stdoutr
-       hGetContents hr2 >>= putStr
-       getProcessStatus True False pid >>= print
diff --git a/tests/T1185.stdout b/tests/T1185.stdout
deleted file mode 100644
index 706231432d7803f198e13e992cc04809c5afd055..0000000000000000000000000000000000000000
--- a/tests/T1185.stdout
+++ /dev/null
@@ -1,2 +0,0 @@
-running...
-Just (Exited ExitSuccess)
diff --git a/tests/T3816.hs b/tests/T3816.hs
deleted file mode 100644
index cda272fbe856024d5e222a2d1393087ccbcce038..0000000000000000000000000000000000000000
--- a/tests/T3816.hs
+++ /dev/null
@@ -1,4 +0,0 @@
-import System.Posix
-main = do
-  getAllGroupEntries >>= print . (>0) . length
-  getAllGroupEntries >>= print . (>0) . length
diff --git a/tests/T3816.stdout b/tests/T3816.stdout
deleted file mode 100644
index dbde422651c9a2cfcc7cf081472dd40b13ed257c..0000000000000000000000000000000000000000
--- a/tests/T3816.stdout
+++ /dev/null
@@ -1,2 +0,0 @@
-True
-True
diff --git a/tests/T8108.hs b/tests/T8108.hs
deleted file mode 100644
index cf1c764486b436ced3bf001e930dd37a1d0a5ca7..0000000000000000000000000000000000000000
--- a/tests/T8108.hs
+++ /dev/null
@@ -1,8 +0,0 @@
-import Control.Monad
-import Control.Concurrent
-import System.Posix.User
-
-main = do
-    void $ forkIO $ forever $ getGroupEntryForID 0
-    void $ forkIO $ forever $ getGroupEntryForID 0
-    threadDelay (3*1000*1000)
diff --git a/tests/Test.hs b/tests/Test.hs
new file mode 100644
index 0000000000000000000000000000000000000000..d0fdb8d8c81e6a29e21a465f7d73dfb6cf9db117
--- /dev/null
+++ b/tests/Test.hs
@@ -0,0 +1,262 @@
+{-# LANGUAGE ScopedTypeVariables #-}
+
+{-# OPTIONS_GHC -fno-warn-deprecations #-}
+{-# OPTIONS_GHC -fno-warn-unused-imports #-}
+
+module Main (main) where
+
+import Control.Applicative
+import Control.Concurrent
+import qualified Control.Exception as E
+import Control.Monad
+import Data.List (sort)
+import System.Exit
+import System.IO
+import System.Posix
+import qualified System.Posix.Env.ByteString
+import Test.Tasty
+import Test.Tasty.HUnit
+
+import qualified FileStatus
+import qualified FileStatusByteString
+import qualified Signals001
+
+main :: IO ()
+main = defaultMain $ testGroup "All"
+  [ executeFile001
+  , fileExist01
+  , fileStatus
+  , fileStatusByteString
+  , getEnvironment01
+  , getEnvironment02
+  , getGroupEntry
+  , getUserEntry
+  , processGroup001
+  , processGroup002
+  , queryFdOption01
+  , resourceLimit
+  , signals001
+  , t1185
+  , t3816
+  , t8108
+  , user001
+  , posix002
+  , posix005
+  , posix006
+  , posix010
+  ]
+
+executeFile001 :: TestTree
+executeFile001 = testCase "executeFile001" $ do
+  actual <- captureStdout $
+    executeFile "echo" True ["arg1", "ar   g2"] Nothing
+  actual @?= "arg1 ar   g2\n"
+
+fileExist01 :: TestTree
+fileExist01 = testCase "fileExist01" $ do
+  fileExist "."
+    @? "file should exist"
+  not <$> fileExist "does not exist"
+    @? "file should not exist"
+
+fileStatus :: TestTree
+fileStatus = testCase "fileStatus" FileStatus.main
+
+fileStatusByteString :: TestTree
+fileStatusByteString = testCase "fileStatusByteString" FileStatusByteString.main
+
+getEnvironment01 :: TestTree
+getEnvironment01 = testCase "getEnvironment01" $ do
+  env <- getEnvironment
+  not (null env)
+    @? "environment should be non-empty"
+
+getEnvironment02 :: TestTree
+getEnvironment02 = testCase "getEnvironment02" $ do
+  env <- System.Posix.Env.ByteString.getEnvironment
+  not (null env)
+    @? "environment should be non-empty"
+
+getGroupEntry :: TestTree
+getGroupEntry = testCase "getGroupEntry" $ do
+  let act = False <$ getGroupEntryForName "thisIsNotMeantToExist"
+  act `E.catch` (\(_ :: E.SomeException) -> return True)
+    @? "group should not exist"
+
+getUserEntry :: TestTree
+getUserEntry = testCase "getUserEntry" $ do
+  let act = False <$ getUserEntryForName "thisIsNotMeantToExist"
+  act `E.catch` (\(_ :: E.SomeException) -> return True)
+    @? "user should not exist"
+
+processGroup001 :: TestTree
+processGroup001 = testCase "processGroup001" $ do
+  pgid <- getProcessGroupID
+  pgid' <- getProcessGroupIDOf =<< getProcessID
+  pgid @?= pgid'
+
+processGroup002 :: TestTree
+processGroup002 = testCase "processGroup002" $ do
+  pid <- getProcessID
+  ppid <- getParentProcessID
+  ppgid <- getProcessGroupIDOf ppid
+  -- join the parent process
+  joinProcessGroup ppgid
+  pgid1 <- getProcessGroupID
+  ppgid @?= pgid1
+  -- be a leader
+  _ <- createProcessGroupFor pid
+  pgid2 <- getProcessGroupID
+  pid @?= fromIntegral pgid2
+  -- and join the parent again
+  setProcessGroupIDOf pid ppgid
+  pgid3 <- getProcessGroupID
+  ppgid @?= pgid3
+
+queryFdOption01 :: TestTree
+queryFdOption01 = testCase "queryFdOption01" $ do
+  not <$> queryFdOption stdOutput NonBlockingRead
+    @? "should be blocking"
+  setFdOption stdOutput NonBlockingRead True
+  queryFdOption stdOutput NonBlockingRead
+    @? "should be non-blocking"
+
+resourceLimit :: TestTree
+resourceLimit = testCase "resourceLimit" $ do
+  let soft = 5
+      hard = 10
+  setResourceLimit ResourceCPUTime
+    (ResourceLimits (ResourceLimit soft) (ResourceLimit hard))
+  r <- getResourceLimit ResourceCPUTime
+  let (ResourceLimit s) = softLimit r
+  let (ResourceLimit h) = hardLimit r
+  s @?= soft
+  h @?= hard
+
+signals001 :: TestTree
+signals001 = testCase "signals001" Signals001.main
+
+t1185 :: TestTree
+t1185 = testCase "T1185" $ do
+  (stdinr, stdinw) <- createPipe
+  (stdoutr, stdoutw) <- createPipe
+  pid <- forkProcess $ do
+    hw <- fdToHandle stdoutw
+    hr <- fdToHandle stdinr
+    closeFd stdinw
+    hGetContents hr >>= hPutStr hw
+    hClose hr
+    hClose hw
+    exitImmediately ExitSuccess
+  threadDelay 100000
+  closeFd stdoutw
+  closeFd stdinw
+  hr2 <- fdToHandle stdoutr
+  hGetContents hr2 >>= putStr
+  actual <- getProcessStatus True False pid
+  actual @?= Just (Exited ExitSuccess)
+
+t3816 :: TestTree
+t3816 = testCase "T3816" $ do
+  not . null <$> getAllGroupEntries
+    @? "should be non-empty"
+  not . null <$> getAllGroupEntries
+    @? "should be non-empty"
+
+t8108 :: TestTree
+t8108 = testCase "T8108" $ do
+  void $ forkIO $ forever $ getGroupEntryForID 0
+  void $ forkIO $ forever $ getGroupEntryForID 0
+  threadDelay 3000000
+
+user001 :: TestTree
+user001 = testCase "user001" $ do
+  let force act = do
+        x <- act
+        x @?= x
+  force getRealUserID
+  force getRealUserID
+  force getRealGroupID
+  force getEffectiveUserID
+  force getEffectiveGroupID
+  force getGroups
+  force getEffectiveUserName
+  force $ getRealGroupID >>= getGroupEntryForID
+  force $ getRealGroupID >>= getGroupEntryForID >>= getGroupEntryForName . groupName
+  force getAllGroupEntries
+  force $ getRealUserID >>= getUserEntryForID
+  force getAllUserEntries
+
+posix002 :: TestTree
+posix002 = testCase "posix002" $ do
+  actual <- captureStdout $
+    executeFile "printenv" True [] (Just [("ONE","1"),("TWO","2")])
+  actual @?= "ONE=1\nTWO=2\n"
+
+posix005 :: TestTree
+posix005 = testCase "posix005" $ do
+    hSetBuffering stdout NoBuffering
+
+    setEnvironment [("one","1"),("two","2")]
+    env0 <- getEnvironment
+    sort env0 @?= [("one","1"),("two","2")]
+
+    setEnv "foo" "bar" True
+    env1 <- getEnvironment
+    sort env1 @?= [("foo","bar"),("one","1"),("two","2")]
+
+    setEnv "foo" "baz" True
+    env2 <- getEnvironment
+    sort env2 @?= [("foo","baz"),("one","1"),("two","2")]
+
+    setEnv "fu" "bar" True
+    env3 <- getEnvironment
+    sort env3 @?= [("foo","baz"),("fu","bar"),("one","1"),("two","2")]
+
+    unsetEnv "foo"
+    env4 <- getEnvironment
+    sort env4 @?= [("fu","bar"),("one","1"),("two","2")]
+
+    clearEnv
+    env5 <- getEnvironment
+    sort env5 @?= []
+
+posix006 :: TestTree
+posix006 = testCase "posix006" $ do
+  start <- epochTime
+  blockSignals reservedSignals -- see #4504
+  _ <- sleep 1
+  finish <- epochTime
+  let slept = finish - start
+  (slept >= 1 && slept <= 2)
+    @? "should have slept between 1 and 2"
+
+posix010 :: TestTree
+posix010 = testCase "posix010" $ do
+  root <- getUserEntryForName "root"
+  userName root    @?= "root"
+  userID root      @?= 0
+  userGroupID root @?= 0
+
+  root' <- getUserEntryForID (userID root)
+  userName root'    @?= "root"
+  userID root'      @?= 0
+  userGroupID root' @?= 0
+
+  homeDirectory root @?= homeDirectory root'
+
+-------------------------------------------------------------------------------
+-- Utils
+
+captureStdout :: IO () -> IO String
+captureStdout = captureFd stdOutput
+
+captureFd :: Fd -> IO () -> IO String
+captureFd fd act = do
+  (dRead, dWrite) <- createPipe
+  _ <- forkProcess $ do
+    _ <- dupTo dWrite fd
+    act
+  closeFd dWrite
+  handle <- fdToHandle dRead
+  hGetContents handle
diff --git a/tests/all.T b/tests/all.T
deleted file mode 100644
index e4d2f5b7c8a3d1d668ce3d4dc13187f581784ce7..0000000000000000000000000000000000000000
--- a/tests/all.T
+++ /dev/null
@@ -1,74 +0,0 @@
-
-test('signals001',  normal, compile_and_run, ['-package unix -cpp'])
-test('signals002', [], compile_and_run, ['-package unix'])
-test('fileexist01', normal, compile_and_run, ['-package unix'])
-
-# test #4512
-test('forkprocess01', extra_ways(['threaded1_ls']), compile_and_run,
-     ['-package unix'])
-
-#
-# user001 may fail due to this bug in glibc:
-#   http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=466647
-#
-# Ticket #1487. The glibc implementation of getlogin, which is called by
-# getLoginName, requires that a terminal is connected to filedescriptor 0.
-# See: https://sourceware.org/git/?p=glibc.git;a=blob;f=sysdeps/unix/getlogin.c
-# Therefore we have to omit the 'ghci' way, because it relies on redirecting
-# stdin from file.
-#
-# But getLoginName also fails on GNU/Linux when using a terminal emulator
-# that doesn't write login records to /var/run/utmp. Running:
-#   $ logname
-# should print your login name. If it doesn't, the getLoginName test in user001
-# would fail, so we disabled that test.
-#
-test('user001', omit_ways(['ghci']), compile_and_run, ['-package unix'])
-test('resourceLimit', normal, compile_and_run, ['-package unix'])
-
-x86FreeBsdFail = when(platform('i386-unknown-freebsd'), expect_fail)
-
-test('queryfdoption01', [omit_ways(['ghci']), x86FreeBsdFail], compile_and_run,
-     ['-package unix'])
-test('getEnvironment01', x86FreeBsdFail, compile_and_run, ['-package unix'])
-test('getEnvironment02', x86FreeBsdFail, compile_and_run, ['-package unix'])
-test('getGroupEntryForName', [x86FreeBsdFail, exit_code(1)], compile_and_run,
-     ['-package unix'])
-test('getUserEntryForName', [x86FreeBsdFail, exit_code(1)], compile_and_run,
-     ['-package unix'])
-
-
-test('signals004', normal, compile_and_run, ['-package unix'])
-
-if ('threaded1' in config.run_ways):
-   only_threaded_ways = only_ways(['ghci','threaded1','threaded2'])
-else:
-   only_threaded_ways = skip
-
-test('fdReadBuf001', only_threaded_ways, compile_and_run, ['-package unix'])
-
-test('fileStatus',
-     extra_clean(['dir', 'regular', 'link-dir', 'link-regular']),
-     compile_and_run,
-     ['-package unix'])
-
-test('fileStatusByteString',
-     extra_clean(['dir', 'regular', 'link-dir', 'link-regular']),
-     compile_and_run,
-     ['-package unix'])
-
-
-test('T1185', normal, compile_and_run, ['-package unix'])
-
-# This test fails for me on x86/Linux with a "does not exist" error.
-# Running with strace shows it is trying to talk to winbindd (part of
-# Samba), so I think the failure has nothing to do with GHC.  Also it
-# works on a different machine that doesn't have Samba installed.
-#  --SDM 18/05/2010
-test('T3816', normal, compile_and_run, ['-package unix'])
-
-test('processGroup001', normal, compile_and_run, ['-package unix'])
-test('processGroup002', normal, compile_and_run, ['-package unix'])
-test('executeFile001', omit_ways(prof_ways + ['threaded2']), compile_and_run, ['-package unix'])
-
-test('T8108', normal, compile_and_run, ['-package unix'])
diff --git a/tests/executeFile001.hs b/tests/executeFile001.hs
deleted file mode 100644
index 7a70695b07cc56aeb57ffe90e7944424960b3c70..0000000000000000000000000000000000000000
--- a/tests/executeFile001.hs
+++ /dev/null
@@ -1,6 +0,0 @@
-
-import System.Posix.Process
-
-main :: IO ()
-main = executeFile "echo" True ["arg1", "ar   g2"] Nothing
-
diff --git a/tests/executeFile001.stdout b/tests/executeFile001.stdout
deleted file mode 100644
index 9f4111c2dd30303958b07eb19fa3f31ba8f0aeda..0000000000000000000000000000000000000000
--- a/tests/executeFile001.stdout
+++ /dev/null
@@ -1 +0,0 @@
-arg1 ar   g2
diff --git a/tests/fileexist01.hs b/tests/fileexist01.hs
deleted file mode 100644
index 7bddda9af0b1cef3b8cb392f76bc944f7e6b4183..0000000000000000000000000000000000000000
--- a/tests/fileexist01.hs
+++ /dev/null
@@ -1,5 +0,0 @@
--- test System.Posix.fileExist
-import System.Posix
-main = do
-  fileExist "fileexist01.hs" >>= print
-  fileExist "does not exist" >>= print
diff --git a/tests/fileexist01.stdout b/tests/fileexist01.stdout
deleted file mode 100644
index 1cc8b5e10d332579303311a121b2c33c1d9c9f9a..0000000000000000000000000000000000000000
--- a/tests/fileexist01.stdout
+++ /dev/null
@@ -1,2 +0,0 @@
-True
-False
diff --git a/tests/forkprocess01.hs b/tests/forkprocess01.hs
deleted file mode 100644
index bc182c53afeb311a88e840c2067e2fa7247b7225..0000000000000000000000000000000000000000
--- a/tests/forkprocess01.hs
+++ /dev/null
@@ -1,9 +0,0 @@
--- Test that we can call exitFailure in a forked process, and have it
--- communicated properly to the parent.
-import System.Exit
-import System.Posix.Process
-main = do
-  p <- forkProcess $ exitWith (ExitFailure 72)
-  r <- getProcessStatus True False p
-  print r
-
diff --git a/tests/forkprocess01.stdout b/tests/forkprocess01.stdout
deleted file mode 100644
index 3c10134ade4343582e784e1e998636fd08b3e7ce..0000000000000000000000000000000000000000
--- a/tests/forkprocess01.stdout
+++ /dev/null
@@ -1 +0,0 @@
-Just (Exited (ExitFailure 72))
diff --git a/tests/getEnvironment01.hs b/tests/getEnvironment01.hs
deleted file mode 100644
index fb50fab5be1487e40dd7ed7e7bfb12cbc7b3854a..0000000000000000000000000000000000000000
--- a/tests/getEnvironment01.hs
+++ /dev/null
@@ -1,8 +0,0 @@
-
--- test for trac #781 (GHCi on x86_64, cannot link to static data in
--- shared libs)
-
-import System.Posix.Env
-
-main = getEnvironment >>= (print . (0 <=) . length)
-
diff --git a/tests/getEnvironment01.stdout b/tests/getEnvironment01.stdout
deleted file mode 100644
index 0ca95142bb715442d0c2c82a7c573a08c4593845..0000000000000000000000000000000000000000
--- a/tests/getEnvironment01.stdout
+++ /dev/null
@@ -1 +0,0 @@
-True
diff --git a/tests/getEnvironment02.hs b/tests/getEnvironment02.hs
deleted file mode 100644
index be920df3985ad974f3ed6eaa5648baf110c8f22d..0000000000000000000000000000000000000000
--- a/tests/getEnvironment02.hs
+++ /dev/null
@@ -1,8 +0,0 @@
-
--- test for trac #781 (GHCi on x86_64, cannot link to static data in
--- shared libs)
-
-import System.Posix.Env.ByteString
-
-main = getEnvironment >>= (print . (0 <=) . length)
-
diff --git a/tests/getEnvironment02.stdout b/tests/getEnvironment02.stdout
deleted file mode 100644
index 0ca95142bb715442d0c2c82a7c573a08c4593845..0000000000000000000000000000000000000000
--- a/tests/getEnvironment02.stdout
+++ /dev/null
@@ -1 +0,0 @@
-True
diff --git a/tests/getGroupEntryForName.hs b/tests/getGroupEntryForName.hs
deleted file mode 100644
index bdb42722685f86650dadd6d6be8df30c99994f1b..0000000000000000000000000000000000000000
--- a/tests/getGroupEntryForName.hs
+++ /dev/null
@@ -1,5 +0,0 @@
-
-import System.Posix.User
-
-main :: IO ()
-main = getGroupEntryForName "thisIsNotMeantToExist" >> return ()
diff --git a/tests/getGroupEntryForName.stderr b/tests/getGroupEntryForName.stderr
deleted file mode 100644
index 9a2679fc96de330298f0e2e87817bf160f0a4b6a..0000000000000000000000000000000000000000
--- a/tests/getGroupEntryForName.stderr
+++ /dev/null
@@ -1 +0,0 @@
-getGroupEntryForName: getGroupEntryForName: does not exist (no such group)
diff --git a/tests/getUserEntryForName.hs b/tests/getUserEntryForName.hs
deleted file mode 100644
index a31566e9ce62122b7861f9245915d1bf71c70522..0000000000000000000000000000000000000000
--- a/tests/getUserEntryForName.hs
+++ /dev/null
@@ -1,5 +0,0 @@
-
-import System.Posix.User
-
-main :: IO ()
-main = getUserEntryForName "thisIsNotMeantToExist" >> return ()
diff --git a/tests/getUserEntryForName.stderr b/tests/getUserEntryForName.stderr
deleted file mode 100644
index 0a941d94f25fd32e8fe416e955f921a868a0c7d3..0000000000000000000000000000000000000000
--- a/tests/getUserEntryForName.stderr
+++ /dev/null
@@ -1 +0,0 @@
-getUserEntryForName: getUserEntryForName: does not exist (no such user)
diff --git a/tests/libposix/Makefile b/tests/libposix/Makefile
deleted file mode 100644
index 4ca77510701c80326915a18bd6729824bbc1ca65..0000000000000000000000000000000000000000
--- a/tests/libposix/Makefile
+++ /dev/null
@@ -1,7 +0,0 @@
-# This Makefile runs the tests using GHC's testsuite framework.  It
-# assumes the package is part of a GHC build tree with the testsuite
-# installed in ../../../testsuite.
-
-TOP=../../../../testsuite
-include $(TOP)/mk/boilerplate.mk
-include $(TOP)/mk/test.mk
diff --git a/tests/libposix/all.T b/tests/libposix/all.T
deleted file mode 100644
index a3455abd9d57ad27928eab652e37f7f10fa32de2..0000000000000000000000000000000000000000
--- a/tests/libposix/all.T
+++ /dev/null
@@ -1,16 +0,0 @@
-test('posix002', [ reqlib('unix'), omit_ways(prof_ways), fragile_for(16550, ['threaded2']) ],
-                 compile_and_run, [''])
-
-# Skip on mingw32: assumes existence of 'pwd' and /tmp
-test('posix003', [when(opsys('mingw32'), skip), extra_clean(['po003.out'])],
-                 compile_and_run, [''])
-
-test('posix004', [ reqlib('unix') ], compile_and_run, [''])
-
-test('posix005', [reqlib('unix') ], compile_and_run, [''])
-
-test('posix006', reqlib('unix'), compile_and_run, [''])
-test('posix009', [ omit_ways(threaded_ways), reqlib('unix') ], compile_and_run, [''])
-test('posix010', reqlib('unix'), compile_and_run, [''])
-
-test('posix014', [ reqlib('unix') ], compile_and_run, [''])
diff --git a/tests/libposix/posix002.hs b/tests/libposix/posix002.hs
deleted file mode 100644
index c5909abd6f6097446799b38a748aa819384d218e..0000000000000000000000000000000000000000
--- a/tests/libposix/posix002.hs
+++ /dev/null
@@ -1,4 +0,0 @@
-import System.Posix.Process
-
-main =
-    executeFile "printenv" True [] (Just [("ONE","1"),("TWO","2")])
diff --git a/tests/libposix/posix002.stdout b/tests/libposix/posix002.stdout
deleted file mode 100644
index 5e17a60f42fc2af04dba36993ba1c203f0d29e59..0000000000000000000000000000000000000000
--- a/tests/libposix/posix002.stdout
+++ /dev/null
@@ -1,2 +0,0 @@
-ONE=1
-TWO=2
diff --git a/tests/libposix/posix003.hs b/tests/libposix/posix003.hs
deleted file mode 100644
index b28f9f7dbf3f45ed82b977d9266f974220330494..0000000000000000000000000000000000000000
--- a/tests/libposix/posix003.hs
+++ /dev/null
@@ -1,17 +0,0 @@
-
-import Control.Monad
-import Data.Char
-import System.Exit
-import System.IO
-import System.Process
-
-main = do hw <- openFile "po003.out" WriteMode
-          ph <- runProcess "pwd" [] (Just "/dev") Nothing Nothing (Just hw) Nothing
-          ec <- waitForProcess ph
-          hClose hw
-          unless (ec == ExitSuccess) $ error "pwd failed"
-          hr <- openFile "po003.out" ReadMode
-          output <- hGetContents hr
-          putStrLn ("Got: " ++ show (filter (not . isSpace) output))
-          hClose hr
-
diff --git a/tests/libposix/posix003.stdout b/tests/libposix/posix003.stdout
deleted file mode 100644
index 5206ef3c222fe5c1791bb4da87c22df5c004df11..0000000000000000000000000000000000000000
--- a/tests/libposix/posix003.stdout
+++ /dev/null
@@ -1 +0,0 @@
-Got: "/dev"
diff --git a/tests/libposix/posix004.stdout b/tests/libposix/posix004.stdout
deleted file mode 100644
index 8ed7ee54d5bac70c81324f7abaea69567f731bc4..0000000000000000000000000000000000000000
--- a/tests/libposix/posix004.stdout
+++ /dev/null
@@ -1 +0,0 @@
-I'm happy.
diff --git a/tests/libposix/posix005.hs b/tests/libposix/posix005.hs
deleted file mode 100644
index 91331ff570399a175acdcc00bdc86b5c28a145e2..0000000000000000000000000000000000000000
--- a/tests/libposix/posix005.hs
+++ /dev/null
@@ -1,24 +0,0 @@
-import Data.List (sort)
-import System.IO
-import System.Posix.Env
-
-printEnv :: IO ()
-printEnv = getEnvironment >>= print . sort
-
-main = do
-    hSetBuffering stdout NoBuffering
-    term <- getEnv "TERM"
-    maybe (return ()) putStrLn term
-    setEnvironment [("one","1"),("two","2")]
-    printEnv
-    setEnv "foo" "bar" True
-    printEnv
-    setEnv "foo" "baz" True
-    printEnv
-    setEnv "fu" "bar" True
-    printEnv
-    unsetEnv "foo"
-    printEnv
-    clearEnv
-    printEnv
-
diff --git a/tests/libposix/posix005.stdout b/tests/libposix/posix005.stdout
deleted file mode 100644
index 4f6005430bc90a4b32e3f0ab609501f2b0cd871a..0000000000000000000000000000000000000000
--- a/tests/libposix/posix005.stdout
+++ /dev/null
@@ -1,7 +0,0 @@
-vt100
-[("one","1"),("two","2")]
-[("foo","bar"),("one","1"),("two","2")]
-[("foo","baz"),("one","1"),("two","2")]
-[("foo","baz"),("fu","bar"),("one","1"),("two","2")]
-[("fu","bar"),("one","1"),("two","2")]
-[]
diff --git a/tests/libposix/posix006.hs b/tests/libposix/posix006.hs
deleted file mode 100644
index 697e4e6e209358b30a955ac0dea13780d929db0a..0000000000000000000000000000000000000000
--- a/tests/libposix/posix006.hs
+++ /dev/null
@@ -1,18 +0,0 @@
-
-import System.Posix.Time
-import System.Posix.Unistd
-import System.Posix.Signals
-
-main = do start <- epochTime
-          blockSignals reservedSignals -- see #4504
-          sleep 1
-          finish <- epochTime
-          let slept = finish - start
-          if slept >= 1 && slept <= 2
-              then putStrLn "OK"
-              else do putStr "Started: "
-                      print start
-                      putStr "Finished: "
-                      print finish
-                      putStr "Slept: "
-                      print slept
diff --git a/tests/libposix/posix006.stdout b/tests/libposix/posix006.stdout
deleted file mode 100644
index d86bac9de59abcc26bc7956c1e842237c7581859..0000000000000000000000000000000000000000
--- a/tests/libposix/posix006.stdout
+++ /dev/null
@@ -1 +0,0 @@
-OK
diff --git a/tests/libposix/posix009.hs b/tests/libposix/posix009.hs
deleted file mode 100644
index 067d3a9f298a1989f275e54e9afcfbf36b93317c..0000000000000000000000000000000000000000
--- a/tests/libposix/posix009.hs
+++ /dev/null
@@ -1,15 +0,0 @@
-import System.Posix.Signals
-import System.Posix.Unistd
-
-main = do
-    putStrLn "Blocking real time alarms."
-    blockSignals (addSignal realTimeAlarm reservedSignals)
-    putStrLn "Scheduling an alarm in 2 seconds..."
-    scheduleAlarm 2
-    putStrLn "Sleeping 5 seconds."
-    sleep 5
-    putStrLn "Woken up"
-    ints <- getPendingSignals
-    putStrLn "Checking pending interrupts for RealTimeAlarm"
-    print (inSignalSet realTimeAlarm ints)
-
diff --git a/tests/libposix/posix009.stdout b/tests/libposix/posix009.stdout
deleted file mode 100644
index d2946753e219bcc4098387e4ed635fb89369f985..0000000000000000000000000000000000000000
--- a/tests/libposix/posix009.stdout
+++ /dev/null
@@ -1,6 +0,0 @@
-Blocking real time alarms.
-Scheduling an alarm in 2 seconds...
-Sleeping 5 seconds.
-Woken up
-Checking pending interrupts for RealTimeAlarm
-True
diff --git a/tests/libposix/posix010.hs b/tests/libposix/posix010.hs
deleted file mode 100644
index 420d2107fa17f7411ff780c0da712065e7d30c12..0000000000000000000000000000000000000000
--- a/tests/libposix/posix010.hs
+++ /dev/null
@@ -1,16 +0,0 @@
-import System.Posix
-
-main = do
-    root <- getUserEntryForName "root"
-    putStrLn (ue2String root)
-    root' <- getUserEntryForID (userID root)
-    putStrLn (ue2String root')
-    if homeDirectory root == homeDirectory root' &&
-       userShell     root == userShell     root'
-        then putStrLn "OK"
-        else putStrLn "Mismatch"
-
-ue2String ue = concat [name, ":", show uid, ":", show gid]
-    where name = userName ue
-          uid = userID ue
-          gid = userGroupID ue
diff --git a/tests/libposix/posix010.stdout b/tests/libposix/posix010.stdout
deleted file mode 100644
index 77a50244985e191bafc1e77f627e420b16b648aa..0000000000000000000000000000000000000000
--- a/tests/libposix/posix010.stdout
+++ /dev/null
@@ -1,3 +0,0 @@
-root:0:0
-root:0:0
-OK
diff --git a/tests/libposix/posix014.hs b/tests/libposix/posix014.hs
deleted file mode 100644
index 9d844b20ce297a60ac8b21667af272896341041d..0000000000000000000000000000000000000000
--- a/tests/libposix/posix014.hs
+++ /dev/null
@@ -1,13 +0,0 @@
--- !! Basic pipe usage
-module Main (main) where
-
-import System.Posix
-
-main = do
-  (rd, wd) <- createPipe
-  pid <- forkProcess $ do (str, _) <- fdRead rd 32
-                          putStrLn str
-  fdWrite wd "Hi, there - forked child calling"
-  getProcessStatus True False pid
-  return ()
-
diff --git a/tests/libposix/posix014.stdout b/tests/libposix/posix014.stdout
deleted file mode 100644
index cab0a57734f792096e2dcf110454ea58fbd7ed46..0000000000000000000000000000000000000000
--- a/tests/libposix/posix014.stdout
+++ /dev/null
@@ -1 +0,0 @@
-Hi, there - forked child calling
diff --git a/tests/processGroup001.hs b/tests/processGroup001.hs
deleted file mode 100644
index cd9f70b739a698c128de7ffa1e85aacab6beb3bd..0000000000000000000000000000000000000000
--- a/tests/processGroup001.hs
+++ /dev/null
@@ -1,7 +0,0 @@
-import System.Posix.Process
-
-main = do
-	pgid <- getProcessGroupID
-	pgid' <- getProcessGroupIDOf =<< getProcessID
-	putStr "Testing getProcessGroupID == getProcessGroupIDOf =<< getProcessID: "
-	print $ pgid == pgid'
diff --git a/tests/processGroup001.stdout b/tests/processGroup001.stdout
deleted file mode 100644
index b9be50f310a45ad7233b7b4993a4396cd0e5e034..0000000000000000000000000000000000000000
--- a/tests/processGroup001.stdout
+++ /dev/null
@@ -1 +0,0 @@
-Testing getProcessGroupID == getProcessGroupIDOf =<< getProcessID: True
diff --git a/tests/processGroup002.hs b/tests/processGroup002.hs
deleted file mode 100644
index c93a4168b2df7b9ece36d0fa20fec73ca118a2b8..0000000000000000000000000000000000000000
--- a/tests/processGroup002.hs
+++ /dev/null
@@ -1,21 +0,0 @@
-import System.Posix.Process
-
-main = do
-	pid <- getProcessID
-	ppid <- getParentProcessID
-	ppgid <- getProcessGroupIDOf ppid
-	-- join the parent process
-	putStr "Testing joinProcessGroup: "
-	joinProcessGroup ppgid
-	pgid1 <- getProcessGroupID
-	print $ ppgid == pgid1
-	-- be a leader
-	putStr "Testing createProcessGroupFor: "
-	createProcessGroupFor pid
-	pgid2 <- getProcessGroupID
-	print $ pid == fromIntegral pgid2
-	-- and join the parent again
-	putStr "Testing setProcessGroupIDOf: "
-	setProcessGroupIDOf pid ppgid
-	pgid3 <- getProcessGroupID
-	print $ ppgid == pgid3
diff --git a/tests/processGroup002.stdout b/tests/processGroup002.stdout
deleted file mode 100644
index b9d2409ab0a581a8fcf755d896efb6df27f10a92..0000000000000000000000000000000000000000
--- a/tests/processGroup002.stdout
+++ /dev/null
@@ -1,3 +0,0 @@
-Testing joinProcessGroup: True
-Testing createProcessGroupFor: True
-Testing setProcessGroupIDOf: True
diff --git a/tests/queryfdoption01.hs b/tests/queryfdoption01.hs
deleted file mode 100644
index 46833c105fd97d850371d487da6c7d4f1222429d..0000000000000000000000000000000000000000
--- a/tests/queryfdoption01.hs
+++ /dev/null
@@ -1,11 +0,0 @@
-import System.Posix.IO
-import System.IO
-
-showNBR = do
-	v <- System.Posix.IO.queryFdOption 0 System.Posix.IO.NonBlockingRead
-	putStr $ "NonBlockingRead = " ++ (show v) ++ "\n"
-
-main = do
-	showNBR
-	System.Posix.IO.setFdOption 0 System.Posix.IO.NonBlockingRead True
-	showNBR
diff --git a/tests/queryfdoption01.stdin b/tests/queryfdoption01.stdin
deleted file mode 100644
index 0e9d79c36c8cda75230525e28afce2ac12f9f95c..0000000000000000000000000000000000000000
--- a/tests/queryfdoption01.stdin
+++ /dev/null
@@ -1,3 +0,0 @@
-You can't fcntl(fd, F_SETFL, O_NONBLOCK) /dev/null on (Open)BSD,
-so just supply this dummy file instead of running this test on
-/dev/null.
diff --git a/tests/queryfdoption01.stdout b/tests/queryfdoption01.stdout
deleted file mode 100644
index 1ed43b583e23320cc5b865204991357e79056327..0000000000000000000000000000000000000000
--- a/tests/queryfdoption01.stdout
+++ /dev/null
@@ -1,2 +0,0 @@
-NonBlockingRead = False
-NonBlockingRead = True
diff --git a/tests/resourceLimit.hs b/tests/resourceLimit.hs
deleted file mode 100644
index 05e35afcc07e4fb6afe14a3d9c84ae93c78ce0df..0000000000000000000000000000000000000000
--- a/tests/resourceLimit.hs
+++ /dev/null
@@ -1,16 +0,0 @@
-
--- #2038
-
-import System.Posix.Resource
-
-main :: IO ()
-main = do
-    let soft = ResourceLimit 5
-        hard = ResourceLimit 10
-    setResourceLimit ResourceCPUTime (ResourceLimits soft hard)
-    r <- getResourceLimit ResourceCPUTime
-    let (ResourceLimit s) = softLimit r
-    let (ResourceLimit h) = hardLimit r
-    putStrLn $ show s
-    putStrLn $ show h
-
diff --git a/tests/resourceLimit.stdout b/tests/resourceLimit.stdout
deleted file mode 100644
index c3ec80144cb1009fbead397836eade7cf19df5e5..0000000000000000000000000000000000000000
--- a/tests/resourceLimit.stdout
+++ /dev/null
@@ -1,2 +0,0 @@
-5
-10
diff --git a/tests/signals001.hs b/tests/signals001.hs
deleted file mode 100644
index 20c1d89631a6dd771335ee06722039e02157e5fe..0000000000000000000000000000000000000000
--- a/tests/signals001.hs
+++ /dev/null
@@ -1,108 +0,0 @@
-{-# LANGUAGE CPP #-}
-
-import System.Posix.Signals
-
-#include "ghcconfig.h"
-
-main = do
-  print (testMembers emptySignalSet)
-  print (testMembers emptyset)
-  print (testMembers fullSignalSet)
-  print (testMembers fullset)
-
-fullset = internalAbort `addSignal`
-	  realTimeAlarm `addSignal`
-	  busError `addSignal`
-	  processStatusChanged `addSignal`
-	  continueProcess `addSignal`
-	  floatingPointException `addSignal`
-	  lostConnection `addSignal`
-	  illegalInstruction `addSignal`
-	  keyboardSignal `addSignal`
-	  killProcess `addSignal`
-	  openEndedPipe `addSignal`
-	  keyboardTermination `addSignal`
-	  segmentationViolation `addSignal`
-	  softwareStop `addSignal`
-	  softwareTermination `addSignal`
-	  keyboardStop `addSignal`
-	  backgroundRead `addSignal`
-	  backgroundWrite `addSignal`
-	  userDefinedSignal1 `addSignal`
-	  userDefinedSignal2 `addSignal`
-#if HAVE_SIGPOLL
-	  pollableEvent `addSignal`
-#endif
-	  profilingTimerExpired `addSignal`
-	  badSystemCall `addSignal`
-	  breakpointTrap `addSignal`
-	  urgentDataAvailable `addSignal`
-	  virtualTimerExpired `addSignal`
-	  cpuTimeLimitExceeded `addSignal`
-	  fileSizeLimitExceeded `addSignal`
-	  emptySignalSet
-
-emptyset = internalAbort `deleteSignal`
-	  realTimeAlarm `deleteSignal`
-	  busError `deleteSignal`
-	  processStatusChanged `deleteSignal`
-	  continueProcess `deleteSignal`
-	  floatingPointException `deleteSignal`
-	  lostConnection `deleteSignal`
-	  illegalInstruction `deleteSignal`
-	  keyboardSignal `deleteSignal`
-	  killProcess `deleteSignal`
-	  openEndedPipe `deleteSignal`
-	  keyboardTermination `deleteSignal`
-	  segmentationViolation `deleteSignal`
-	  softwareStop `deleteSignal`
-	  softwareTermination `deleteSignal`
-	  keyboardStop `deleteSignal`
-	  backgroundRead `deleteSignal`
-	  backgroundWrite `deleteSignal`
-	  userDefinedSignal1 `deleteSignal`
-	  userDefinedSignal2 `deleteSignal`
-#if HAVE_SIGPOLL
-	  pollableEvent `deleteSignal`
-#endif
-	  profilingTimerExpired `deleteSignal`
-	  badSystemCall `deleteSignal`
-	  breakpointTrap `deleteSignal`
-	  urgentDataAvailable `deleteSignal`
-	  virtualTimerExpired `deleteSignal`
-	  cpuTimeLimitExceeded `deleteSignal`
-	  fileSizeLimitExceeded `deleteSignal`
-	  fullSignalSet
-  
-testMembers set = [
-	  internalAbort `inSignalSet` set,
-	  realTimeAlarm `inSignalSet` set,
-	  busError `inSignalSet` set,
-	  processStatusChanged `inSignalSet` set,
-	  continueProcess `inSignalSet` set,
-	  floatingPointException `inSignalSet` set,
-	  lostConnection `inSignalSet` set,
-	  illegalInstruction `inSignalSet` set,
-	  keyboardSignal `inSignalSet` set,
-	  killProcess `inSignalSet` set,
-	  openEndedPipe `inSignalSet` set,
-	  keyboardTermination `inSignalSet` set,
-	  segmentationViolation `inSignalSet` set,
-	  softwareStop `inSignalSet` set,
-	  softwareTermination `inSignalSet` set,
-	  keyboardStop `inSignalSet` set,
-	  backgroundRead `inSignalSet` set,
-	  backgroundWrite `inSignalSet` set,
-	  userDefinedSignal1 `inSignalSet` set,
-	  userDefinedSignal2 `inSignalSet` set,
-#if HAVE_SIGPOLL
-	  pollableEvent `inSignalSet` set,
-#endif
-	  profilingTimerExpired `inSignalSet` set,
-	  badSystemCall `inSignalSet` set,
-	  breakpointTrap `inSignalSet` set,
-	  urgentDataAvailable `inSignalSet` set,
-	  virtualTimerExpired `inSignalSet` set,
-	  cpuTimeLimitExceeded `inSignalSet` set,
-	  fileSizeLimitExceeded `inSignalSet` set
-    ]
diff --git a/tests/signals001.stdout b/tests/signals001.stdout
deleted file mode 100644
index b90d1f35c6c3c999558c7dab17174c2858417934..0000000000000000000000000000000000000000
--- a/tests/signals001.stdout
+++ /dev/null
@@ -1,4 +0,0 @@
-[False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False]
-[False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False]
-[True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True]
-[True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True]
diff --git a/tests/signals001.stdout-i386-unknown-freebsd b/tests/signals001.stdout-i386-unknown-freebsd
deleted file mode 100644
index b90d1f35c6c3c999558c7dab17174c2858417934..0000000000000000000000000000000000000000
--- a/tests/signals001.stdout-i386-unknown-freebsd
+++ /dev/null
@@ -1,4 +0,0 @@
-[False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False]
-[False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False]
-[True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True]
-[True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True]
diff --git a/tests/signals001.stdout-i386-unknown-openbsd b/tests/signals001.stdout-i386-unknown-openbsd
deleted file mode 100644
index b90d1f35c6c3c999558c7dab17174c2858417934..0000000000000000000000000000000000000000
--- a/tests/signals001.stdout-i386-unknown-openbsd
+++ /dev/null
@@ -1,4 +0,0 @@
-[False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False]
-[False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False]
-[True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True]
-[True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True]
diff --git a/tests/signals001.stdout-sparc-unknown-openbsd b/tests/signals001.stdout-sparc-unknown-openbsd
deleted file mode 100644
index b90d1f35c6c3c999558c7dab17174c2858417934..0000000000000000000000000000000000000000
--- a/tests/signals001.stdout-sparc-unknown-openbsd
+++ /dev/null
@@ -1,4 +0,0 @@
-[False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False]
-[False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False]
-[True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True]
-[True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True]
diff --git a/tests/signals001.stdout-x86_64-unknown-openbsd b/tests/signals001.stdout-x86_64-unknown-openbsd
deleted file mode 100644
index b90d1f35c6c3c999558c7dab17174c2858417934..0000000000000000000000000000000000000000
--- a/tests/signals001.stdout-x86_64-unknown-openbsd
+++ /dev/null
@@ -1,4 +0,0 @@
-[False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False]
-[False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False,False]
-[True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True]
-[True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True,True]
diff --git a/tests/signals002.stdout b/tests/signals002.stdout
deleted file mode 100644
index 8e3dc9e5539e4994091aea69af24ddbf557241b1..0000000000000000000000000000000000000000
--- a/tests/signals002.stdout
+++ /dev/null
@@ -1,2 +0,0 @@
-True
-hello
diff --git a/tests/user001.hs b/tests/user001.hs
deleted file mode 100644
index 4b4dd8bde09c8dd657653d13fbd13e7c7fdb6e8d..0000000000000000000000000000000000000000
--- a/tests/user001.hs
+++ /dev/null
@@ -1,27 +0,0 @@
--- test that none of System.Posix.User.get* fail
-import Control.Exception as Exception
-import System.Posix.User
-
-check :: Show a => a -> Bool
-check a = show a == show a
-
-p :: Show a => String -> IO a -> IO ()
-p s m = (do putStr (s ++ ": ")
-            c <- fmap check m
-            putStrLn $ if c then "OK" else "I am the pope!")
-        `Exception.catch` (\e -> putStrLn ("ERROR: " ++ show (e::SomeException)))
-
-main :: IO ()
-main = do p "getRealUserID"        $ getRealUserID
-          p "getRealGroupID"       $ getRealGroupID
-          p "getEffectiveUserID"   $ getEffectiveUserID
-          p "getEffectiveGroupID"  $ getEffectiveGroupID
-          p "getGroups"            $ getGroups
-          --p "getLoginName"         $ getLoginName
-          p "getEffectiveUserName" $ getEffectiveUserName
-          p "getGroupEntryForID"   $ getRealGroupID >>= getGroupEntryForID
-          p "getGroupEntryForName" $ getRealGroupID >>= getGroupEntryForID >>= getGroupEntryForName . groupName
-          p "getAllGroupEntries"   $ getAllGroupEntries
-          p "getUserEntryForID"    $ getRealUserID >>= getUserEntryForID
-          --p "getUserEntryForName"  $ getLoginName >>= getUserEntryForName
-          p "getAllUserEntries"    $ getAllUserEntries
diff --git a/tests/user001.stdout b/tests/user001.stdout
deleted file mode 100644
index e2e03df1356f52db1899b25deba7909875828d8f..0000000000000000000000000000000000000000
--- a/tests/user001.stdout
+++ /dev/null
@@ -1,11 +0,0 @@
-getRealUserID: OK
-getRealGroupID: OK
-getEffectiveUserID: OK
-getEffectiveGroupID: OK
-getGroups: OK
-getEffectiveUserName: OK
-getGroupEntryForID: OK
-getGroupEntryForName: OK
-getAllGroupEntries: OK
-getUserEntryForID: OK
-getAllUserEntries: OK
diff --git a/unix.cabal b/unix.cabal
index 26955af92bb0d97afeedad506a6fa344f3de7adf..d7aa7be8d3968cb4ddb6eef00a7656552dcc0fb2 100644
--- a/unix.cabal
+++ b/unix.cabal
@@ -142,3 +142,71 @@ library
     c-sources:
         cbits/HsUnix.c
         cbits/execvpe.c
+
+test-suite unix-tests
+    hs-source-dirs: tests
+    main-is: Test.hs
+    other-modules:
+        FileStatus
+        FileStatusByteString
+        Signals001
+    type: exitcode-stdio-1.0
+    default-language: Haskell2010
+    build-depends: base, tasty, tasty-hunit, unix
+    ghc-options: -Wall -with-rtsopts=-V0
+
+test-suite FdReadBuf001
+    hs-source-dirs: tests
+    main-is: FdReadBuf001.hs
+    type: exitcode-stdio-1.0
+    default-language: Haskell2010
+    build-depends: base, unix
+    ghc-options: -Wall -threaded
+
+test-suite ForkProcess01
+    hs-source-dirs: tests
+    main-is: ForkProcess01.hs
+    type: exitcode-stdio-1.0
+    default-language: Haskell2010
+    build-depends: base, unix
+    ghc-options: -Wall
+
+test-suite Signals002
+    hs-source-dirs: tests
+    main-is: Signals002.hs
+    type: exitcode-stdio-1.0
+    default-language: Haskell2010
+    build-depends: base, unix
+    ghc-options: -Wall
+
+test-suite Signals004
+    hs-source-dirs: tests
+    main-is: Signals004.hs
+    type: exitcode-stdio-1.0
+    default-language: Haskell2010
+    build-depends: base, unix
+    ghc-options: -Wall
+
+test-suite Posix004
+    hs-source-dirs: tests
+    main-is: Posix004.hs
+    type: exitcode-stdio-1.0
+    default-language: Haskell2010
+    build-depends: base, unix
+    ghc-options: -Wall
+
+test-suite Posix009
+    hs-source-dirs: tests
+    main-is: Posix009.hs
+    type: exitcode-stdio-1.0
+    default-language: Haskell2010
+    build-depends: base, unix
+    ghc-options: -Wall -with-rtsopts=-V0
+
+test-suite Posix014
+    hs-source-dirs: tests
+    main-is: Posix014.hs
+    type: exitcode-stdio-1.0
+    default-language: Haskell2010
+    build-depends: base, unix
+    ghc-options: -Wall