diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -26,9 +26,8 @@
 And once you have it, building the usual way:
 
     $ stack build
+    $ stack test --ta all
     $ stack run
-    $ stack test
-    $ stack clean
 
 If you're just looking, [browse the source](https://github.com/dino-/photoname)
 
diff --git a/changelog.md b/changelog.md
--- a/changelog.md
+++ b/changelog.md
@@ -1,3 +1,11 @@
+5.1 (2022-01-11)
+
+  * Added new number-of-links filtering feature
+  * End-to-end tests are now optional
+  * Fixed some spelling and wording issues
+  * Applied hlint suggestions
+
+
 5.0 (2020-11-26)
 
   * Updated Stackage resolver
diff --git a/package.yaml b/package.yaml
--- a/package.yaml
+++ b/package.yaml
@@ -1,5 +1,5 @@
 name: photoname
-version: '5.0'
+version: '5.1'
 license: ISC
 copyright: 2007-2021 Dino Morelli
 author: Dino Morelli
diff --git a/photoname.cabal b/photoname.cabal
--- a/photoname.cabal
+++ b/photoname.cabal
@@ -5,7 +5,7 @@
 -- see: https://github.com/sol/hpack
 
 name:           photoname
-version:        5.0
+version:        5.1
 synopsis:       Rename photo image files based on EXIF shoot date
 description:    Command-line utility for renaming/moving photo image files based on EXIF tags.
 category:       Application, Console
@@ -50,6 +50,7 @@
       Photoname.Date
       Photoname.Exif
       Photoname.Exiv2
+      Photoname.Links
       Photoname.Log
   other-modules:
       Paths_photoname
@@ -96,9 +97,9 @@
   type: exitcode-stdio-1.0
   main-is: runtests.hs
   other-modules:
-      Test.Photoname.Date
-      TestLink
-      Util
+      Photoname.Test.EndToEnd.Link
+      Photoname.Test.EndToEnd.Util
+      Photoname.Test.Unit.Date
       Paths_photoname
   hs-source-dirs:
       src/tests
diff --git a/src/app/Photoname/Opts.hs b/src/app/Photoname/Opts.hs
--- a/src/app/Photoname/Opts.hs
+++ b/src/app/Photoname/Opts.hs
@@ -21,6 +21,7 @@
   ( Artist (..)
   , ConfigPath (..)
   , CopySwitch (..)
+  , Links (Exactly, NoLimit)
   , MoveSwitch (..)
   , NoActionSwitch (..)
   , NoDirsSwitch (..)
@@ -60,6 +61,13 @@
         <> help "No subdirectory hierarchy. Just do DIR/NEWFILE"
         )
       )
+  <*> option (Exactly <$> auto)
+        (  long "links"
+        <> short 'l'
+        <> metavar "NUM"
+        <> help "Reject files unless they have this many hard links. See LINKS. Default: accept all files"
+        <> value NoLimit
+        )
   <*> ( MoveSwitch <$> switch
         (  long "move"
         <> help "Move the files, don't just hard-link to the new locations. In other words, remove the source path."
@@ -104,7 +112,7 @@
         <> showDefault
         <> value (Verbose INFO)
         )
-  <*> ( some $ strArgument
+  <*> some ( strArgument
         $ metavar "FILES..."
       )
 
@@ -117,7 +125,7 @@
    confExists <- doesFileExist path
 
    if confExists
-      then (map ("--" ++) . lines) <$> readFile path
+      then map ("--" ++) . lines <$> readFile path
       else do
         hPutStrLn stderr $ "Config file " <> path <> " does not exist!"
         exitFailure
@@ -140,7 +148,7 @@
   case optConfig cliOpts of
     Just configPath -> do
       confArgs <- loadConfig configPath
-      parseOpts' $ confArgs <> (optPaths cliOpts)
+      parseOpts' $ confArgs <> optPaths cliOpts
     Nothing -> pure cliOpts
 
 
@@ -201,6 +209,10 @@
 Be careful with what you put in here, we've seen problems with email addresses rendering the entire field not visible in some applications. Keep it simple as above!
 
 Pass a quoted empty string to -a|--artist to delete an existing Artist tag, like this: -a '' or --artist=''
+
+LINKS
+
+A common use of photoname is to process a directory of images, leaving you with 2 hard links to the original files. If at a later time you want to process any new images in the same directory, they could be identified by their different number of links. The -l|--links switch can perform this filtering in lieu of using another tool like `find`.
 
 PREFIX
 
diff --git a/src/app/photoname.hs b/src/app/photoname.hs
--- a/src/app/photoname.hs
+++ b/src/app/photoname.hs
@@ -1,14 +1,18 @@
 import Control.Monad ( filterM, forM_, when )
 import Control.Newtype.Generics ( op )
-import System.Posix ( getFileStatus, isRegularFile )
+import Data.Functor ( (<&>) )
+import System.Posix ( FileStatus, getFileStatus, isRegularFile )
 import Text.Printf ( printf )
 
-import Photoname.Common ( CopySwitch (..), MoveSwitch (..),
-  NoActionSwitch (..), Options (..), Ph, SrcPath (..), runRename )
+import Photoname.Common
+  ( CopySwitch (..), Links, MoveSwitch (..), NoActionSwitch (..)
+  , Options (..), Ph, SrcPath (..), runRename
+  )
 import Photoname.CopyLink ( createNewLink )
 import Photoname.Date ( PhDate, parseExifDate, parseFilenameDate )
 import Photoname.Exif ( getExifDate )
 import Photoname.Exiv2 ( setArtist, setExifDate )
+import Photoname.Links ( describeHardLinkPolicy, linksTest )
 import Photoname.Log ( errorM, initLogging, infoM, lname )
 import Photoname.Opts ( parseOpts )
 
@@ -30,6 +34,17 @@
   setArtist destPath
 
 
+-- Get rid of anything not a regular file or with a number of links that
+-- doesn't match our links amount which is either passed in by the user with
+-- the -l|--links switch or any number of links is fine.
+filterWantedFiles :: Links -> [FilePath] -> IO [SrcPath]
+filterWantedFiles links inputFiles = map SrcPath <$> filterM (\p ->
+    getFileStatus p <&> testFile) inputFiles
+  where
+    testFile :: FileStatus -> Bool
+    testFile fileStatus = isRegularFile fileStatus && linksTest links fileStatus
+
+
 -- Figure out and execute what the user wants based on the supplied args.
 main :: IO ()
 main = do
@@ -37,10 +52,6 @@
 
    initLogging $ optVerbosity opts
 
-   -- Get rid of anything not a regular file from the list of paths
-   actualPaths <- map SrcPath <$> filterM
-      (\p -> getFileStatus p >>= pure . isRegularFile) (optPaths opts)
-
    -- Notify user of the switches that will be in effect.
    when (op NoActionSwitch . optNoAction $ opts) $
       infoM lname "No-action mode, nothing will be changed."
@@ -51,10 +62,17 @@
    when (op MoveSwitch . optMove $ opts) $
       infoM lname "Removing original links after new links are in place."
 
+   describeHardLinkPolicy $ optLinks opts
+
+   actualPaths <- filterWantedFiles (optLinks opts) (optPaths opts)
+
    -- Do the link manipulations, and report any errors.
    forM_ actualPaths $ \srcPath -> do
       result <- runRename opts $ processFile srcPath
-      either (\em -> errorM lname $ printf "** Processing %s: %s\n" (op SrcPath srcPath) em)
-         (const pure ()) result
+      either
+        {- HLINT ignore "Avoid lambda" -}
+        -- Because the compiler can't figure out printf is expecting an argument at compile time
+        (\em -> errorM lname $ printf "** Processing %s: %s\n" (op SrcPath srcPath) em)
+        pure result
 
    -- Perhaps we should get an ExitCode back from all this above?
diff --git a/src/lib/Photoname/Common.hs b/src/lib/Photoname/Common.hs
--- a/src/lib/Photoname/Common.hs
+++ b/src/lib/Photoname/Common.hs
@@ -5,6 +5,7 @@
   , ConfigPath (..)
   , CopySwitch (..)
   , DestPath (..)
+  , Links(..)
   , MoveSwitch (..)
   , NoActionSwitch (..)
   , NoDirsSwitch (..)
@@ -32,6 +33,7 @@
 import Control.Newtype.Generics
 import GHC.Generics
 import System.Log.Logger ( Priority (..) )
+import System.Posix ( CNlink )
 
 
 data Verbosity
@@ -67,6 +69,8 @@
 
 instance Newtype NoDirsSwitch
 
+data Links = Exactly CNlink | NoLimit
+
 newtype MoveSwitch = MoveSwitch Bool
   deriving Generic
 
@@ -97,6 +101,7 @@
   , optConfig     :: Maybe ConfigPath
   , optCopy       :: CopySwitch
   , optNoDirs     :: NoDirsSwitch
+  , optLinks      :: Links
   , optMove       :: MoveSwitch
   , optNoAction   :: NoActionSwitch
   , optParentDir  :: ParentDir
diff --git a/src/lib/Photoname/CopyLink.hs b/src/lib/Photoname/CopyLink.hs
--- a/src/lib/Photoname/CopyLink.hs
+++ b/src/lib/Photoname/CopyLink.hs
@@ -34,7 +34,7 @@
     FilenameDate lt -> buildDatePath lt
     NoDateFound -> throwError "Could not extract any date information"
 
-  -- Check for existance of the target file
+  -- Check for existence of the target file
   exists <- liftIO $ fileExist destFp
   when exists $ throwError $ "Destination " ++ destFp ++ " exists!"
 
@@ -46,7 +46,7 @@
     liftIO $ createDirectoryIfMissing True $ takeDirectory destFp
 
     -- Make the new file
-    if (op CopySwitch . optCopy $ opts)
+    if op CopySwitch . optCopy $ opts
       then liftIO $ copyFile srcFp destFp
       else tryHardLink srcPath destPath
 
@@ -80,7 +80,7 @@
 
    parentFp <- asks (op ParentDir . optParentDir)
    noDirs <- asks (op NoDirsSwitch . optNoDirs)
-   pure . DestPath $ if (noDirs)
+   pure . DestPath $ if noDirs
       then parentFp </> fileName
-      else parentFp </> (formatYear date) </>
-         (formatDateHyphens date) </> fileName
+      else parentFp </> formatYear date </>
+         formatDateHyphens date </> fileName
diff --git a/src/lib/Photoname/Date.hs b/src/lib/Photoname/Date.hs
--- a/src/lib/Photoname/Date.hs
+++ b/src/lib/Photoname/Date.hs
@@ -56,7 +56,7 @@
 parseExifDate :: Maybe String -> PhDate
 parseExifDate Nothing = NoDateFound
 parseExifDate (Just s) =
-   case (parse dateParser "" s) of
+   case parse dateParser "" s of
       Left _  -> NoDateFound
       Right x -> ExifDate x
    where
@@ -69,7 +69,7 @@
             LocalTime
                (fromGregorian (read year) (read month) (read day))
                (TimeOfDay (read hour) (read minute)
-                  (fromIntegral ((read second) :: Integer)))
+                  (fromIntegral (read second :: Integer)))
 
 
 {- Parse a string in one of the the forms below into a CalendarTime datatype.
@@ -85,7 +85,7 @@
 -}
 parseFilenameDate :: SrcPath -> PhDate
 parseFilenameDate srcPath =
-  case (parse dateParser "" (takeFileName . op SrcPath $ srcPath)) of
+  case parse dateParser "" (takeFileName . op SrcPath $ srcPath) of
     Left _  -> NoDateFound
     Right x -> FilenameDate x
   where
@@ -106,7 +106,7 @@
          LocalTime
             (fromGregorian (read year) (read month) (read day))
             (TimeOfDay (read hour) (read minute)
-               (fromIntegral ((read second) :: Integer)))
+               (fromIntegral (read second :: Integer)))
 
 
 {- Format a Maybe CalendarTime into a "yyyy" string
diff --git a/src/lib/Photoname/Exif.hs b/src/lib/Photoname/Exif.hs
--- a/src/lib/Photoname/Exif.hs
+++ b/src/lib/Photoname/Exif.hs
@@ -32,5 +32,5 @@
 extractDate (Left _) = Nothing
 extractDate (Right exifMap) =
   -- Find the first date available in the Map
-  show <$> ( ala First foldMap $ map (flip M.lookup exifMap)
-    [dateTimeOriginal, dateTimeDigitized, dateTime] )
+  show <$> ala First foldMap (map (flip M.lookup exifMap)
+    [dateTimeOriginal, dateTimeDigitized, dateTime])
diff --git a/src/lib/Photoname/Exiv2.hs b/src/lib/Photoname/Exiv2.hs
--- a/src/lib/Photoname/Exiv2.hs
+++ b/src/lib/Photoname/Exiv2.hs
@@ -29,8 +29,6 @@
   unless (op NoActionSwitch . optNoAction $ opts) $
     liftIO $ mapM_ (callCommand . unCommand) commands
 
-  pure ()
-
 
 setArtist :: DestPath -> Ph ()
 setArtist (DestPath destFp) = do
diff --git a/src/lib/Photoname/Links.hs b/src/lib/Photoname/Links.hs
new file mode 100644
--- /dev/null
+++ b/src/lib/Photoname/Links.hs
@@ -0,0 +1,24 @@
+module Photoname.Links
+  ( describeHardLinkPolicy
+  , linksTest
+  )
+  where
+
+import System.Posix ( FileStatus, linkCount )
+import Text.Printf ( printf )
+
+import Photoname.Common ( Links (Exactly, NoLimit) )
+import Photoname.Log ( infoM, lname )
+
+
+linksTest :: Links -> FileStatus -> Bool
+linksTest (Exactly linkCountWanted) fileStatus = linkCountWanted == linkCount fileStatus
+linksTest NoLimit                   _          = True
+
+
+describeHardLinkPolicy :: Links -> IO ()
+describeHardLinkPolicy l = case l of
+  Exactly 1 -> infoM lname          "Only processing files with 1 hard link"
+  Exactly n -> infoM lname $ printf "Only processing files with %d hard links" (toInteger n)
+  NoLimit   -> pure ()
+
diff --git a/src/tests/Photoname/Test/EndToEnd/Link.hs b/src/tests/Photoname/Test/EndToEnd/Link.hs
new file mode 100644
--- /dev/null
+++ b/src/tests/Photoname/Test/EndToEnd/Link.hs
@@ -0,0 +1,340 @@
+{-# OPTIONS_GHC -fno-warn-unused-do-bind #-}
+
+module Photoname.Test.EndToEnd.Link
+  ( tests
+  )
+  where
+
+import System.Directory ( copyFile, removeDirectoryRecursive, removeFile )
+import System.FilePath.Posix ( (</>), (<.>) )
+import System.Posix.Files ( fileExist )
+import System.Process ( waitForProcess )
+import Text.Regex.Posix ( (=~) )
+import Test.Tasty
+import Test.Tasty.HUnit
+import qualified Photoname.Test.EndToEnd.Util as Util
+
+
+parentDir, oldPath, newLinkPathDate :: FilePath
+
+parentDir = Util.resourcesPath </> "testParentDir"
+oldPath = Util.resourcesPath </> "dateTimeDigitized.jpg"
+newLinkPathDate = parentDir </> "2003/2003-09-02/20030902-114303.jpg"
+
+
+tests :: TestTree
+tests = testGroup "test the normal behavior of hard-linking original files to new paths"
+  [ testLinkDigitized
+  , testLinkOriginal
+  , testLinkDate
+  , testNoDate
+  , testMove
+  , testLinkNoAction
+  , testLinkNoActionLong
+  , testLinkQuiet
+  , testLinkQuietLong
+  , testLinkSuffix
+  , testLinkPrefix
+  , testNoExif
+  , testNotAnImage
+  , testDirForFile
+  , testLinkFilenameDate
+  ]
+
+
+testLinkDigitized :: TestTree
+testLinkDigitized = testCase "tests for DateTimeDigitized" $ do
+   -- Run the program with known input data
+   (output, procH) <- Util.getBinaryOutput
+      [ "--parent-dir=" ++ parentDir, oldPath ]
+   waitForProcess procH
+
+   -- Check that the correct output path exists
+   existsNew <- fileExist newLinkPathDate
+   assertBool "make link, date digitized: existence of new link" existsNew
+
+   -- Check that old path still exists
+   existsOld <- fileExist oldPath
+   assertBool "make link, date digitized: existence of old link" existsOld
+
+   -- Remove files and dirs that were created
+   removeDirectoryRecursive parentDir
+
+   -- Test output to stdout
+   assertBool "make link, date digitized: correct output"
+      (output =~ newLinkPathDate :: Bool)
+
+
+testLinkOriginal :: TestTree
+testLinkOriginal = testCase "tests for DateTimeOriginal" $ do
+   let digitizedOldPath = Util.resourcesPath </> "dateTimeOriginal.jpg"
+
+   -- Run the program with known input data
+   (output, procH) <- Util.getBinaryOutput
+      [ "--parent-dir=" ++ parentDir, digitizedOldPath ]
+   waitForProcess procH
+
+   -- Check that the correct output path exists
+   existsNew <- fileExist newLinkPathDate
+   assertBool "make link, date original: existence of new link" existsNew
+
+   -- Check that old path still exists
+   existsOld <- fileExist digitizedOldPath
+   assertBool "make link, date original: existence of old link" existsOld
+
+   -- Remove files and dirs that were created
+   removeDirectoryRecursive parentDir
+
+   -- Test output to stdout
+   assertBool "make link, date original: correct output"
+      (output =~ newLinkPathDate :: Bool)
+
+
+testLinkDate :: TestTree
+testLinkDate = testCase "tests for DateTime" $ do
+   let dateOldPath = Util.resourcesPath </> "dateTime.jpg"
+   let customLinkPathDate = parentDir </> "2019/2019-03-26/20190326-075309.jpg"
+
+   -- Run the program with known input data
+   (output, procH) <- Util.getBinaryOutput
+      [ "--parent-dir=" ++ parentDir, dateOldPath ]
+   waitForProcess procH
+
+   -- Check that the correct output path exists
+   existsNew <- fileExist customLinkPathDate
+   assertBool "make link, date: existence of new link" existsNew
+
+   -- Check that old path still exists
+   existsOld <- fileExist dateOldPath
+   assertBool "make link, date: existence of old link" existsOld
+
+   -- Remove files and dirs that were created
+   removeDirectoryRecursive parentDir
+
+   -- Test output to stdout
+   assertBool "make link, date: correct output"
+      (output =~ customLinkPathDate :: Bool)
+
+
+testNoDate :: TestTree
+testNoDate = testCase "test for no date in the file" $ do
+   -- Run the program with known input data
+   (output, procH) <- Util.getBinaryOutput
+      [ "--parent-dir=" ++ parentDir, Util.resourcesPath </> "noDate.jpg" ]
+   waitForProcess procH
+
+   -- Test output to stdout
+   assertBool "no EXIF: correct output"
+      (output =~ "\\*\\* Processing util/resources/test/noDate.jpg: Could not extract any date information" :: Bool)
+
+
+testMove :: TestTree
+testMove = testCase "tests to ensure the file is moved (original link removed)" $ do
+   -- Make a dummy copy of the source file. This test will be getting rid
+   -- of it, if successful.
+   let newOldPath = Util.resourcesPath </> "moveTest.jpg"
+   copyFile oldPath newOldPath
+
+   -- Run the program with known input data
+   (output, procH) <- Util.getBinaryOutput
+      [ "--move", "--parent-dir=" ++ parentDir, newOldPath ]
+   waitForProcess procH
+
+   -- Check that the correct output path exists
+   existsNew <- fileExist newLinkPathDate
+   assertBool "move file: existence of new link" existsNew
+
+   -- Check that old path still exists
+   existsOld <- fileExist newOldPath
+   Util.assertFalse "move file: existence of old link" existsOld
+
+   -- Remove files and dirs that were created
+   removeDirectoryRecursive parentDir
+
+   -- Test output to stdout
+   assertBool "move file: correct output"
+      (output =~ newLinkPathDate :: Bool)
+
+
+testLinkNoAction :: TestTree
+testLinkNoAction = testLinkNoAction' "no action" "-n"
+
+
+testLinkNoActionLong :: TestTree
+testLinkNoActionLong = testLinkNoAction' "no action long" "--no-action"
+
+
+testLinkNoAction' :: String -> String -> TestTree
+testLinkNoAction' label switch = testCase label $ do
+   -- Run the program with known input data
+   (output, procH) <- Util.getBinaryOutput
+      [ switch, "--parent-dir=" ++ parentDir, oldPath ]
+   waitForProcess procH
+
+   -- Check that the correct output path exists
+   existsNew <- fileExist parentDir
+   Util.assertFalse (label ++ ": existence of new link") existsNew
+
+   -- Check that old path still exists
+   existsOld <- fileExist oldPath
+   assertBool (label ++ ": existence of old link") existsOld
+
+   -- Test output to stdout
+   assertBool (label ++ ": correct output")
+      (output =~ newLinkPathDate :: Bool)
+
+
+testLinkQuiet :: TestTree
+testLinkQuiet = testLinkQuiet' "make link quiet" "-v0"
+
+
+testLinkQuietLong :: TestTree
+testLinkQuietLong = testLinkQuiet' "make link quiet long" "--verbose 0"
+
+
+-- Reusable test code for above short/long versions of the quiet switch
+testLinkQuiet' :: String -> String -> TestTree
+testLinkQuiet' label switch = testCase label $ do
+   -- Run the program with known input data
+   (output, procH) <- Util.getBinaryOutput
+      [ switch, "--parent-dir=" ++ parentDir, oldPath ]
+   waitForProcess procH
+
+   -- Check that the correct output path exists
+   existsNew <- fileExist newLinkPathDate
+   assertBool (label ++ ": existence of new link") existsNew
+
+   -- Check that old path still exists
+   existsOld <- fileExist oldPath
+   assertBool (label ++ ": existence of old link") existsOld
+
+   -- Remove files and dirs that were created
+   removeDirectoryRecursive parentDir
+
+   -- Test output to stdout
+   Util.assertFalse (label ++ ": no output")
+      (output =~ newLinkPathDate :: Bool)
+
+
+testLinkSuffix :: TestTree
+testLinkSuffix = testCase "test link with a suffix" $ do
+   let suffix = "_dwm"
+
+   -- Run the program with known input data
+   (output, procH) <- Util.getBinaryOutput
+      [ "--parent-dir=" ++ parentDir, "--suffix=" ++ suffix, oldPath ]
+   waitForProcess procH
+
+   let newLinkPath = parentDir </>
+         ("2003/2003-09-02/20030902-114303" ++ suffix) <.> "jpg"
+
+   -- Check that the correct output path exists
+   existsNew <- fileExist newLinkPath
+   assertBool "make link: existence of new link" existsNew
+
+   -- Check that old path still exists
+   existsOld <- fileExist oldPath
+   assertBool "make link: existence of old link" existsOld
+
+   -- Remove files and dirs that were created
+   removeDirectoryRecursive parentDir
+
+   -- Test output to stdout
+   assertBool "make link: correct output"
+      (output =~ newLinkPath :: Bool)
+
+
+testLinkPrefix :: TestTree
+testLinkPrefix = testCase "test link with a prefix" $ do
+  let prefix = "SomeSubject_"
+
+  -- Run the program with known input data
+  (output, procH) <- Util.getBinaryOutput
+    [ "--parent-dir=" ++ parentDir, "--prefix=" ++ prefix, oldPath ]
+  waitForProcess procH
+
+  let newLinkPath = parentDir </>
+        ("2003/2003-09-02/" <> prefix <> "20030902-114303") <.> "jpg"
+
+  -- Check that the correct output path exists
+  existsNew <- fileExist newLinkPath
+  assertBool "make link: existence of new link" existsNew
+
+  -- Check that old path still exists
+  existsOld <- fileExist oldPath
+  assertBool "make link: existence of old link" existsOld
+
+  -- Remove files and dirs that were created
+  removeDirectoryRecursive parentDir
+
+  -- Test output to stdout
+  assertBool "make link: correct output"
+    (output =~ newLinkPath :: Bool)
+
+
+testNoExif :: TestTree
+testNoExif = testCase "test for a file without any EXIF data" $ do
+   -- Run the program with known input data
+   (output, procH) <- Util.getBinaryOutput
+      [ "--parent-dir=" ++ parentDir, Util.resourcesPath </> "noExif.jpg" ]
+   waitForProcess procH
+
+   -- Test output to stdout
+   assertBool "no EXIF: correct output"
+      (output =~ "\\*\\* Processing util/resources/test/noExif.jpg: Could not extract any date information" :: Bool)
+
+
+testNotAnImage :: TestTree
+testNotAnImage = testCase "test for a file that isn't an image" $ do
+   -- Run the program with known input data
+   (output, procH) <- Util.getBinaryOutput
+      [ "--parent-dir=" ++ parentDir, Util.resourcesPath </> "notAnImage.txt" ]
+   waitForProcess procH
+
+   -- Test output to stdout
+   assertBool "no EXIF: correct output"
+      (output =~ "\\*\\* Processing util/resources/test/notAnImage.txt: Could not extract any date information" :: Bool)
+
+
+testDirForFile :: TestTree
+testDirForFile = testCase "test when a directory is passed instead of a file" $ do
+   -- Run the program with known input data
+   (output, procH) <- Util.getBinaryOutput
+      [ "--parent-dir=" ++ parentDir, Util.resourcesPath ]
+   waitForProcess procH
+
+   -- Test output to stdout
+   assertBool "dir as file to change: correct output"
+      (output =~ "" :: Bool)
+
+
+testLinkFilenameDate :: TestTree
+testLinkFilenameDate = testCase "test to ensure the date can be acquired from the file name" $ do
+   -- Make a dummy copy of the source file. This test will be modifying
+   -- it, if successful.
+   let newOldPath = Util.resourcesPath </> "copy-of-foobar-2021-10-04-17-29-49-942.jpg"
+   copyFile (Util.resourcesPath </> "foobar-2021-10-04-17-29-49-942.jpg") newOldPath
+   let newLinkPathDate' = parentDir </> "2021/2021-10-04/20211004-172949.jpg"
+
+   -- Run the program with known input data
+   (output, procH) <- Util.getBinaryOutput
+      [ "--parent-dir=" ++ parentDir, newOldPath ]
+   waitForProcess procH
+
+   -- Check that the correct output path exists
+   existsNew <- fileExist newLinkPathDate'
+   assertBool "filename date: existence of new link" existsNew
+
+   -- Check that old path still exists
+   existsOld <- fileExist newOldPath
+   assertBool "filename date: existence of old link" existsOld
+
+   -- Remove files and dirs that were created
+   removeFile newOldPath
+   removeDirectoryRecursive parentDir
+
+   -- WARNING We are NOT checking for the new EXIF tags in the files!!
+
+   -- Test output to stdout
+   assertBool "filename date: correct output"
+      (output =~ newLinkPathDate' :: Bool)
diff --git a/src/tests/Photoname/Test/EndToEnd/Util.hs b/src/tests/Photoname/Test/EndToEnd/Util.hs
new file mode 100644
--- /dev/null
+++ b/src/tests/Photoname/Test/EndToEnd/Util.hs
@@ -0,0 +1,38 @@
+module Photoname.Test.EndToEnd.Util
+  ( resourcesPath
+  , getProcessOutput, getBinaryOutput
+  , assertFalse
+  )
+  where
+
+import System.IO ( hGetContents )
+import System.Process ( ProcessHandle, runInteractiveCommand )
+import Test.Tasty.HUnit ( Assertion, assertBool )
+
+
+command :: String
+command = "stack exec photoname"
+
+
+resourcesPath :: FilePath
+resourcesPath = "util/resources/test"
+
+
+{- Quick and dirty function to run a process and grab its output.
+   This evil thing doesn't watch STDERR at all or otherwise do anything
+   even remotely safe.
+-}
+getProcessOutput :: String -> [String] -> IO (String, ProcessHandle)
+getProcessOutput path' args = do
+   (_, outH, _, procH) <- runInteractiveCommand
+      $ path' <> " -- " <> unwords args
+   output <- hGetContents outH
+   pure (output, procH)
+
+
+getBinaryOutput :: [String] -> IO (String, ProcessHandle)
+getBinaryOutput = getProcessOutput command
+
+
+assertFalse :: String -> Bool -> Assertion
+assertFalse l b = assertBool l $ not b
diff --git a/src/tests/Photoname/Test/Unit/Date.hs b/src/tests/Photoname/Test/Unit/Date.hs
new file mode 100644
--- /dev/null
+++ b/src/tests/Photoname/Test/Unit/Date.hs
@@ -0,0 +1,84 @@
+{-# OPTIONS_GHC -fno-warn-orphans #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+
+module Photoname.Test.Unit.Date
+  ( tests
+  )
+  where
+
+import Data.Time.Calendar
+import Data.Time.LocalTime
+import Photoname.Common ( SrcPath (..) )
+import Photoname.Date ( PhDate (..), parseExifDate, parseFilenameDate )
+import Test.Tasty
+import Test.Tasty.HUnit
+import Test.Tasty.QuickCheck
+
+
+expectedLocalTime :: LocalTime
+expectedLocalTime = LocalTime (fromGregorian 2021 10 04)
+  (TimeOfDay 17 29 (fromIntegral (49 :: Integer)))
+
+
+tests :: TestTree
+tests = testGroup "Photoname.Date"
+  [ parsingTests
+  , propsPhDate
+  ]
+
+
+parsingTests :: TestTree
+parsingTests = testGroup "parsing tests"
+  [ testCase "parse a datetime in EXIF format" $
+      ExifDate expectedLocalTime @=?
+        parseExifDate (Just "2021:10:04 17:29:49")
+  , testCase "parse a date from a file path with directories" $
+      FilenameDate expectedLocalTime @=?
+        parseFilenameDate (SrcPath "some/directory/signal-2021-10-04-172949.jpg")
+  , testCase "parse a date from a file path with directories and more hyphens" $
+      FilenameDate expectedLocalTime @=?
+        parseFilenameDate (SrcPath "some/directory/signal-2021-10-04-17-29-49-942.jpg")
+  , testCase "parse a date from a file path with no directories" $
+      FilenameDate expectedLocalTime @=?
+        parseFilenameDate (SrcPath "signal-2021-10-04-172949.jpg")
+  , testCase "parse a date from a file path with more hyphens but no directories" $
+      FilenameDate expectedLocalTime @=?
+        parseFilenameDate (SrcPath "signal-2021-10-04-17-29-49-942.jpg")
+  , testCase "parse a date from a previously-named file path" $
+      FilenameDate expectedLocalTime @=?
+        parseFilenameDate (SrcPath "20211004-172949.jpg")
+  , testCase "parse a date from a previously-named file path with suffix" $
+      FilenameDate expectedLocalTime @=?
+        parseFilenameDate (SrcPath "20211004-172949_xyz.jpg")
+  , testCase "parse a date from a Pixel phone path file path" $
+      FilenameDate expectedLocalTime @=?
+        parseFilenameDate (SrcPath "PXL_20211004_172949000.jpg")
+  , testCase "parse a date from a Pixel phone path file path with extra suffix" $
+      FilenameDate expectedLocalTime @=?
+        parseFilenameDate (SrcPath "PXL_20211004_172949000_foo.jpg")
+  ]
+
+
+instance Arbitrary PhDate where
+  arbitrary = do
+    oneof
+      [ pure $ ExifDate expectedLocalTime
+      , pure $ FilenameDate expectedLocalTime
+      , pure NoDateFound
+      ]
+
+{- HLINT ignore "Monoid law, right identity" -}
+{- HLINT ignore "Monoid law, left identity" -}
+{- HLINT ignore "Use mconcat" -}
+propsPhDate :: TestTree
+propsPhDate = testGroup "testing the Semigroup and Monoid properties of PhDate"
+  [ testProperty "Semigroup associativity  x <> (y <> z) == (x <> y) <> z" $
+    \(x :: PhDate) (y :: PhDate) (z :: PhDate) ->
+      x <> (y <> z) == (x <> y) <> z
+  , testProperty "Monoid right identity  x <> mempty == x" $
+    \(x :: PhDate) -> x <> mempty == x
+  , testProperty "Monoid left identity  mempty <> x == x" $
+    \(x :: PhDate) -> mempty <> x == x
+  , testProperty "Monoid concatenation  mconcat xs == foldr (<>) mempty xs" $
+    \(xs :: [PhDate]) -> mconcat xs == foldr (<>) mempty xs
+  ]
diff --git a/src/tests/Test/Photoname/Date.hs b/src/tests/Test/Photoname/Date.hs
deleted file mode 100644
--- a/src/tests/Test/Photoname/Date.hs
+++ /dev/null
@@ -1,81 +0,0 @@
-{-# OPTIONS_GHC -fno-warn-orphans #-}
-{-# LANGUAGE ScopedTypeVariables #-}
-
-module Test.Photoname.Date
-  ( tests
-  )
-  where
-
-import Data.Time.Calendar
-import Data.Time.LocalTime
-import Photoname.Common ( SrcPath (..) )
-import Photoname.Date ( PhDate (..), parseExifDate, parseFilenameDate )
-import Test.Tasty
-import Test.Tasty.HUnit
-import Test.Tasty.QuickCheck
-
-
-expectedLocalTime :: LocalTime
-expectedLocalTime = LocalTime (fromGregorian 2021 10 04)
-  (TimeOfDay 17 29 (fromIntegral (49 :: Integer)))
-
-
-tests :: TestTree
-tests = testGroup "Photoname.Date"
-  [ parsingTests
-  , propsPhDate
-  ]
-
-
-parsingTests :: TestTree
-parsingTests = testGroup "parsing tests"
-  [ testCase "parse a datetime in EXIF format" $
-      ExifDate expectedLocalTime @=?
-        (parseExifDate $ Just "2021:10:04 17:29:49")
-  , testCase "parse a date from a file path with directories" $
-      FilenameDate expectedLocalTime @=?
-        parseFilenameDate (SrcPath "some/directory/signal-2021-10-04-172949.jpg")
-  , testCase "parse a date from a file path with directories and more hyphens" $
-      FilenameDate expectedLocalTime @=?
-        parseFilenameDate (SrcPath "some/directory/signal-2021-10-04-17-29-49-942.jpg")
-  , testCase "parse a date from a file path with no directories" $
-      FilenameDate expectedLocalTime @=?
-        parseFilenameDate (SrcPath "signal-2021-10-04-172949.jpg")
-  , testCase "parse a date from a file path with more hyphens but no directories" $
-      FilenameDate expectedLocalTime @=?
-        parseFilenameDate (SrcPath "signal-2021-10-04-17-29-49-942.jpg")
-  , testCase "parse a date from a previously-named file path" $
-      FilenameDate expectedLocalTime @=?
-        parseFilenameDate (SrcPath "20211004-172949.jpg")
-  , testCase "parse a date from a previously-named file path with suffix" $
-      FilenameDate expectedLocalTime @=?
-        parseFilenameDate (SrcPath "20211004-172949_xyz.jpg")
-  , testCase "parse a date from a Pixel phone path file path" $
-      FilenameDate expectedLocalTime @=?
-        parseFilenameDate (SrcPath "PXL_20211004_172949000.jpg")
-  , testCase "parse a date from a Pixel phone path file path with extra suffix" $
-      FilenameDate expectedLocalTime @=?
-        parseFilenameDate (SrcPath "PXL_20211004_172949000_foo.jpg")
-  ]
-
-
-instance Arbitrary PhDate where
-  arbitrary = do
-    oneof
-      [ pure $ ExifDate expectedLocalTime
-      , pure $ FilenameDate expectedLocalTime
-      , pure NoDateFound
-      ]
-
-propsPhDate :: TestTree
-propsPhDate = testGroup "testing the Semigroup and Monoid properties of PhDate"
-  [ testProperty "Semigroup associativity  x <> (y <> z) == (x <> y) <> z" $
-    \(x :: PhDate) (y :: PhDate) (z :: PhDate) ->
-      x <> (y <> z) == (x <> y) <> z
-  , testProperty "Monoid right identity  x <> mempty == x" $
-    \(x :: PhDate) -> x <> mempty == x
-  , testProperty "Monoid left identity  mempty <> x == x" $
-    \(x :: PhDate) -> mempty <> x == x
-  , testProperty "Monoid concatenation  mconcat xs == foldr (<>) mempty xs" $
-    \(xs :: [PhDate]) -> mconcat xs == foldr (<>) mempty xs
-  ]
diff --git a/src/tests/TestLink.hs b/src/tests/TestLink.hs
deleted file mode 100644
--- a/src/tests/TestLink.hs
+++ /dev/null
@@ -1,340 +0,0 @@
-{-# OPTIONS_GHC -fno-warn-unused-do-bind #-}
-
-module TestLink
-  ( tests
-  )
-  where
-
-import System.Directory ( copyFile, removeDirectoryRecursive, removeFile )
-import System.FilePath.Posix ( (</>), (<.>) )
-import System.Posix.Files ( fileExist )
-import System.Process ( waitForProcess )
-import Text.Regex.Posix ( (=~) )
-import Test.Tasty
-import Test.Tasty.HUnit
-import qualified Util
-
-
-parentDir, oldPath, newLinkPathDate :: FilePath
-
-parentDir = Util.resourcesPath </> "testParentDir"
-oldPath = Util.resourcesPath </> "dateTimeDigitized.jpg"
-newLinkPathDate = parentDir </> "2003/2003-09-02/20030902-114303.jpg"
-
-
-tests :: TestTree
-tests = testGroup "test the normal behavior of hard-linking original files to new paths"
-  [ testLinkDigitized
-  , testLinkOriginal
-  , testLinkDate
-  , testNoDate
-  , testMove
-  , testLinkNoAction
-  , testLinkNoActionLong
-  , testLinkQuiet
-  , testLinkQuietLong
-  , testLinkSuffix
-  , testLinkPrefix
-  , testNoExif
-  , testNotAnImage
-  , testDirForFile
-  , testLinkFilenameDate
-  ]
-
-
-testLinkDigitized :: TestTree
-testLinkDigitized = testCase "tests for DateTimeDigitized" $ do
-   -- Run the program with known input data
-   (output, procH) <- Util.getBinaryOutput
-      [ "--parent-dir=" ++ parentDir, oldPath ]
-   waitForProcess procH
-
-   -- Check that the correct output path exists
-   existsNew <- fileExist newLinkPathDate
-   assertBool "make link, date digitized: existance of new link" existsNew
-
-   -- Check that old path still exists
-   existsOld <- fileExist oldPath
-   assertBool "make link, date digitized: existance of old link" existsOld
-
-   -- Remove files and dirs that were created
-   removeDirectoryRecursive parentDir
-
-   -- Test output to stdout
-   assertBool "make link, date digitized: correct output"
-      (output =~ newLinkPathDate :: Bool)
-
-
-testLinkOriginal :: TestTree
-testLinkOriginal = testCase "tests for DateTimeOriginal" $ do
-   let digitizedOldPath = Util.resourcesPath </> "dateTimeOriginal.jpg"
-
-   -- Run the program with known input data
-   (output, procH) <- Util.getBinaryOutput
-      [ "--parent-dir=" ++ parentDir, digitizedOldPath ]
-   waitForProcess procH
-
-   -- Check that the correct output path exists
-   existsNew <- fileExist newLinkPathDate
-   assertBool "make link, date original: existance of new link" existsNew
-
-   -- Check that old path still exists
-   existsOld <- fileExist digitizedOldPath
-   assertBool "make link, date original: existance of old link" existsOld
-
-   -- Remove files and dirs that were created
-   removeDirectoryRecursive parentDir
-
-   -- Test output to stdout
-   assertBool "make link, date original: correct output"
-      (output =~ newLinkPathDate :: Bool)
-
-
-testLinkDate :: TestTree
-testLinkDate = testCase "tests for DateTime" $ do
-   let dateOldPath = Util.resourcesPath </> "dateTime.jpg"
-   let customLinkPathDate = parentDir </> "2019/2019-03-26/20190326-075309.jpg"
-
-   -- Run the program with known input data
-   (output, procH) <- Util.getBinaryOutput
-      [ "--parent-dir=" ++ parentDir, dateOldPath ]
-   waitForProcess procH
-
-   -- Check that the correct output path exists
-   existsNew <- fileExist customLinkPathDate
-   assertBool "make link, date: existance of new link" existsNew
-
-   -- Check that old path still exists
-   existsOld <- fileExist dateOldPath
-   assertBool "make link, date: existance of old link" existsOld
-
-   -- Remove files and dirs that were created
-   removeDirectoryRecursive parentDir
-
-   -- Test output to stdout
-   assertBool "make link, date: correct output"
-      (output =~ customLinkPathDate :: Bool)
-
-
-testNoDate :: TestTree
-testNoDate = testCase "test for no date in the file" $ do
-   -- Run the program with known input data
-   (output, procH) <- Util.getBinaryOutput
-      [ "--parent-dir=" ++ parentDir, Util.resourcesPath </> "noDate.jpg" ]
-   waitForProcess procH
-
-   -- Test output to stdout
-   assertBool "no EXIF: correct output"
-      (output =~ "\\*\\* Processing util/resources/test/noDate.jpg: Could not extract any date information" :: Bool)
-
-
-testMove :: TestTree
-testMove = testCase "tests to ensure the file is moved (original link removed)" $ do
-   -- Make a dummy copy of the source file. This test will be getting rid
-   -- of it, if successful.
-   let newOldPath = Util.resourcesPath </> "moveTest.jpg"
-   copyFile oldPath newOldPath
-
-   -- Run the program with known input data
-   (output, procH) <- Util.getBinaryOutput
-      [ "--move", "--parent-dir=" ++ parentDir, newOldPath ]
-   waitForProcess procH
-
-   -- Check that the correct output path exists
-   existsNew <- fileExist newLinkPathDate
-   assertBool "move file: existance of new link" existsNew
-
-   -- Check that old path still exists
-   existsOld <- fileExist newOldPath
-   Util.assertFalse "move file: existance of old link" existsOld
-
-   -- Remove files and dirs that were created
-   removeDirectoryRecursive parentDir
-
-   -- Test output to stdout
-   assertBool "move file: correct output"
-      (output =~ newLinkPathDate :: Bool)
-
-
-testLinkNoAction :: TestTree
-testLinkNoAction = testLinkNoAction' "no action" "-n"
-
-
-testLinkNoActionLong :: TestTree
-testLinkNoActionLong = testLinkNoAction' "no action long" "--no-action"
-
-
-testLinkNoAction' :: String -> String -> TestTree
-testLinkNoAction' label switch = testCase label $ do
-   -- Run the program with known input data
-   (output, procH) <- Util.getBinaryOutput
-      [ switch, "--parent-dir=" ++ parentDir, oldPath ]
-   waitForProcess procH
-
-   -- Check that the correct output path exists
-   existsNew <- fileExist parentDir
-   Util.assertFalse (label ++ ": existance of new link") existsNew
-
-   -- Check that old path still exists
-   existsOld <- fileExist oldPath
-   assertBool (label ++ ": existance of old link") existsOld
-
-   -- Test output to stdout
-   assertBool (label ++ ": correct output")
-      (output =~ newLinkPathDate :: Bool)
-
-
-testLinkQuiet :: TestTree
-testLinkQuiet = testLinkQuiet' "make link quiet" "-v0"
-
-
-testLinkQuietLong :: TestTree
-testLinkQuietLong = testLinkQuiet' "make link quiet long" "--verbose 0"
-
-
--- Reusable test code for above short/long versions of the quiet switch
-testLinkQuiet' :: String -> String -> TestTree
-testLinkQuiet' label switch = testCase label $ do
-   -- Run the program with known input data
-   (output, procH) <- Util.getBinaryOutput
-      [ switch, "--parent-dir=" ++ parentDir, oldPath ]
-   waitForProcess procH
-
-   -- Check that the correct output path exists
-   existsNew <- fileExist newLinkPathDate
-   assertBool (label ++ ": existance of new link") existsNew
-
-   -- Check that old path still exists
-   existsOld <- fileExist oldPath
-   assertBool (label ++ ": existance of old link") existsOld
-
-   -- Remove files and dirs that were created
-   removeDirectoryRecursive parentDir
-
-   -- Test output to stdout
-   Util.assertFalse (label ++ ": no output")
-      (output =~ newLinkPathDate :: Bool)
-
-
-testLinkSuffix :: TestTree
-testLinkSuffix = testCase "test link with a suffix" $ do
-   let suffix = "_dwm"
-
-   -- Run the program with known input data
-   (output, procH) <- Util.getBinaryOutput
-      [ "--parent-dir=" ++ parentDir, "--suffix=" ++ suffix, oldPath ]
-   waitForProcess procH
-
-   let newLinkPath = parentDir </>
-         ("2003/2003-09-02/20030902-114303" ++ suffix) <.> "jpg"
-
-   -- Check that the correct output path exists
-   existsNew <- fileExist newLinkPath
-   assertBool "make link: existance of new link" existsNew
-
-   -- Check that old path still exists
-   existsOld <- fileExist oldPath
-   assertBool "make link: existance of old link" existsOld
-
-   -- Remove files and dirs that were created
-   removeDirectoryRecursive parentDir
-
-   -- Test output to stdout
-   assertBool "make link: correct output"
-      (output =~ newLinkPath :: Bool)
-
-
-testLinkPrefix :: TestTree
-testLinkPrefix = testCase "test link with a prefix" $ do
-  let prefix = "SomeSubject_"
-
-  -- Run the program with known input data
-  (output, procH) <- Util.getBinaryOutput
-    [ "--parent-dir=" ++ parentDir, "--prefix=" ++ prefix, oldPath ]
-  waitForProcess procH
-
-  let newLinkPath = parentDir </>
-        ("2003/2003-09-02/" <> prefix <> "20030902-114303") <.> "jpg"
-
-  -- Check that the correct output path exists
-  existsNew <- fileExist newLinkPath
-  assertBool "make link: existance of new link" existsNew
-
-  -- Check that old path still exists
-  existsOld <- fileExist oldPath
-  assertBool "make link: existance of old link" existsOld
-
-  -- Remove files and dirs that were created
-  removeDirectoryRecursive parentDir
-
-  -- Test output to stdout
-  assertBool "make link: correct output"
-    (output =~ newLinkPath :: Bool)
-
-
-testNoExif :: TestTree
-testNoExif = testCase "test for a file without any EXIF data" $ do
-   -- Run the program with known input data
-   (output, procH) <- Util.getBinaryOutput
-      [ "--parent-dir=" ++ parentDir, Util.resourcesPath </> "noExif.jpg" ]
-   waitForProcess procH
-
-   -- Test output to stdout
-   assertBool "no EXIF: correct output"
-      (output =~ "\\*\\* Processing util/resources/test/noExif.jpg: Could not extract any date information" :: Bool)
-
-
-testNotAnImage :: TestTree
-testNotAnImage = testCase "test for a file that isn't an image" $ do
-   -- Run the program with known input data
-   (output, procH) <- Util.getBinaryOutput
-      [ "--parent-dir=" ++ parentDir, Util.resourcesPath </> "notAnImage.txt" ]
-   waitForProcess procH
-
-   -- Test output to stdout
-   assertBool "no EXIF: correct output"
-      (output =~ "\\*\\* Processing util/resources/test/notAnImage.txt: Could not extract any date information" :: Bool)
-
-
-testDirForFile :: TestTree
-testDirForFile = testCase "test when a directory is passed instead of a file" $ do
-   -- Run the program with known input data
-   (output, procH) <- Util.getBinaryOutput
-      [ "--parent-dir=" ++ parentDir, Util.resourcesPath ]
-   waitForProcess procH
-
-   -- Test output to stdout
-   assertBool "dir as file to change: correct output"
-      (output =~ "" :: Bool)
-
-
-testLinkFilenameDate :: TestTree
-testLinkFilenameDate = testCase "test to ensure the date can be acquired from the file name" $ do
-   -- Make a dummy copy of the source file. This test will be modifying
-   -- it, if successful.
-   let newOldPath = Util.resourcesPath </> "copy-of-foobar-2021-10-04-17-29-49-942.jpg"
-   copyFile (Util.resourcesPath </> "foobar-2021-10-04-17-29-49-942.jpg") newOldPath
-   let newLinkPathDate' = parentDir </> "2021/2021-10-04/20211004-172949.jpg"
-
-   -- Run the program with known input data
-   (output, procH) <- Util.getBinaryOutput
-      [ "--parent-dir=" ++ parentDir, newOldPath ]
-   waitForProcess procH
-
-   -- Check that the correct output path exists
-   existsNew <- fileExist newLinkPathDate'
-   assertBool "filename date: existance of new link" existsNew
-
-   -- Check that old path still exists
-   existsOld <- fileExist newOldPath
-   assertBool "filename date: existance of old link" existsOld
-
-   -- Remove files and dirs that were created
-   removeFile newOldPath
-   removeDirectoryRecursive parentDir
-
-   -- WARNING We are NOT checking for the new EXIF tags in the files!!
-
-   -- Test output to stdout
-   assertBool "filename date: correct output"
-      (output =~ newLinkPathDate' :: Bool)
diff --git a/src/tests/Util.hs b/src/tests/Util.hs
deleted file mode 100644
--- a/src/tests/Util.hs
+++ /dev/null
@@ -1,39 +0,0 @@
-module Util
-  ( resourcesPath
-  , getProcessOutput, getBinaryOutput
-  , assertFalse
-  )
-  where
-
-import Data.List ( intercalate )
-import System.IO ( hGetContents )
-import System.Process ( ProcessHandle, runInteractiveCommand )
-import Test.Tasty.HUnit ( Assertion, assertBool )
-
-
-command :: String
-command = "stack exec photoname"
-
-
-resourcesPath :: FilePath
-resourcesPath = "util/resources/test"
-
-
-{- Quick and dirty function to run a process and grab its output.
-   This evil thing doesn't watch STDERR at all or otherwise do anything
-   even remotely safe.
--}
-getProcessOutput :: String -> [String] -> IO (String, ProcessHandle)
-getProcessOutput path' args = do
-   (_, outH, _, procH) <- runInteractiveCommand
-      $ path' ++ " -- " ++ (intercalate " " args)
-   output <- hGetContents outH
-   pure (output, procH)
-
-
-getBinaryOutput :: [String] -> IO (String, ProcessHandle)
-getBinaryOutput = getProcessOutput command
-
-
-assertFalse :: String -> Bool -> Assertion
-assertFalse l b = assertBool l $ not b
diff --git a/src/tests/runtests.hs b/src/tests/runtests.hs
--- a/src/tests/runtests.hs
+++ b/src/tests/runtests.hs
@@ -1,14 +1,24 @@
 module Main
    where
 
+import System.Environment ( getArgs, withArgs )
 import Test.Tasty
 
-import qualified TestLink ( tests )
-import qualified Test.Photoname.Date ( tests )
+import qualified Photoname.Test.EndToEnd.Link as Link
+import qualified Photoname.Test.Unit.Date as Date
 
 
+{- Defaults to only running the unit tests. To run everything:
+
+    $ stack test --ta all
+    $ stack test --test-arguments all
+-}
+
 main :: IO ()
-main = defaultMain $ testGroup " tests"
-  [ Test.Photoname.Date.tests
-  , TestLink.tests
-  ]
+main = do
+  args <- getArgs
+  let baseTests = [ Date.tests ]
+  let tests = if "all" `elem` args
+        then Link.tests : baseTests
+        else baseTests
+  withArgs [] $ defaultMain $ testGroup " tests" tests
