phatsort (empty) → 0.5.0.1
raw patch · 23 files changed
+5828/−0 lines, 23 filesdep +HMockdep +MonadRandomdep +ansi-wl-pprintsetup-changed
Dependencies added: HMock, MonadRandom, ansi-wl-pprint, base, directory, filepath, optparse-applicative, phatsort, random-shuffle, tasty, tasty-hunit, transformers, unix-compat
Files
- CHANGELOG.md +115/−0
- LICENSE +21/−0
- README.md +142/−0
- Setup.hs +2/−0
- app/LibOA.hs +141/−0
- app/Options.hs +133/−0
- app/phatsort.hs +79/−0
- app/seqcp.hs +123/−0
- phatsort.cabal +127/−0
- src/PhatSort.hs +27/−0
- src/PhatSort/Cmd/PhatSort.hs +231/−0
- src/PhatSort/Cmd/SeqCp.hs +205/−0
- src/PhatSort/Monad/FileSystem.hs +150/−0
- src/PhatSort/Monad/Stdio.hs +38/−0
- src/PhatSort/Monad/Sync.hs +47/−0
- src/PhatSort/Monad/Sync/Unistd.hs +22/−0
- src/PhatSort/Monad/Trans/Error.hs +135/−0
- src/PhatSort/Script.hs +45/−0
- src/PhatSort/SortOptions.hs +46/−0
- test/PhatSort/Cmd/PhatSort/Mock.hs +1659/−0
- test/PhatSort/Cmd/SeqCp/Mock.hs +2237/−0
- test/Spec.hs +23/−0
- test/TestLib.hs +80/−0
+ CHANGELOG.md view
@@ -0,0 +1,115 @@+# `phatsort-haskell` Changelog++This project follows the [Haskell package versioning policy][PVP], with+versions in `A.B.C.D` format. `A` may be incremented arbitrarily for+non-technical reasons, but [semantic versioning][SemVer] is otherwise+followed, where `A.B` is the major version, `C` is the minor version, and `D`+is the patch version. Initial development uses versions `0.0.0.D`, for which+every version is considered breaking.++[PVP]: <https://pvp.haskell.org/>+[SemVer]: <https://semver.org/>++The format of this changelog is based on [Keep a Changelog][KaC], with the+following conventions:++* Level-two heading `Unreleased` is used to track changes that have not been+ released.+* Other level-two headings specify the release in `A.B.C.D (YYYY-MM-DD)`+ format, with newer versions above older versions.+* Level-three headings are used to categorize changes as follows:+ 1. Breaking+ 2. Non-Breaking+* Changes are listed in arbitrary order and present tense.++[KaC]: <https://keepachangelog.com/en/1.0.0/>++## 0.5.0.1 (2022-03-02)++### Non-Breaking++* Bump `optparse-applicative` dependency version upper bound++## 0.5.0.0 (2021-12-10)++### Breaking++* Add `seqcp`+* Check that each target directory is not a mount point+* Call `sync` system call via FFI instead of running the `sync` command++### Non-Breaking++* Add tests using mocking++## 0.4.0.0 (2021-06-25)++### Breaking++* Fix `--help` when using `optparse-applicative` `0.16`++### Non-Breaking++* Refactor Nix configuration++## 0.3.0.0 (2021-05-27)++### Breaking++* Add support for `optparse-applicative` `0.16`++### Non-Breaking++* Add `.deb` and `.rpm` packaging+* Add Cabal support to `Makefile`+* Add Cabal tests to GitHub Actions+* Add [stan](https://hackage.haskell.org/package/stan) static analysis++## 0.2.0.2 (2020-11-23)++### Non-Breaking++* Use GitHub Actions instead of Travis CI++## 0.2.0.1 (2020-11-08)++### Non-Breaking++* Rename Git default branch to `main`++## 0.2.0.0 (2020-07-26)++### Breaking++* Add syncing, `--no-sync` option++### Non-Breaking++* Refactor `Makefile`, add `STACK_NIX_PATH` support+* Add `test-all` command to `Makefile`+* Add Nix configuration++## 0.1.0.3 (2019-12-22)++### Non-Breaking++* Switch back to using `LibOA` instead of `optparse-applicative-supplement`++## 0.1.0.2 (2019-12-22)++### Non-Breaking++* Use `optparse-applicative-supplement`++## 0.1.0.1 (2019-12-22)++### Non-Breaking++* Add Travis CI+* Turn off threading++## 0.1.0.0 (2019-12-21)++### Breaking++* Initial public release
+ LICENSE view
@@ -0,0 +1,21 @@+The MIT License++Copyright (c) 2019-2022 Travis Cardwell++Permission is hereby granted, free of charge, to any person obtaining a copy+of this software and associated documentation files (the "Software"), to deal+in the Software without restriction, including without limitation the rights+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+copies of the Software, and to permit persons to whom the Software is+furnished to do so, subject to the following conditions:++The above copyright notice and this permission notice shall be included in+all copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+SOFTWARE.
+ README.md view
@@ -0,0 +1,142 @@+# PhatSort++[](https://www.repostatus.org/#active)+[](https://github.com/ExtremaIS/phatsort-haskell/actions)+[](https://hackage.haskell.org/package/phatsort)+[](https://stackage.org/package/phatsort)+[](https://stackage.org/nightly/package/phatsort)++* [Overview](#overview)+* [CLI](#cli)+ * [Requirements](#requirements)+ * [Installation](#installation)+ * [`.deb` Package Installation](#deb-package-installation)+ * [`.rpm` Package Installation](#rpm-package-installation)+ * [Installation From Hackage](#installation-from-hackage)+ * [Installation From Stackage](#installation-from-stackage)+ * [Usage](#usage)+* [Related Software](#related-software)+* [Project](#project)+ * [Links](#links)+ * [Tags](#tags)+ * [Contribution](#contribution)+ * [License](#license)++## Overview++The PhatSort project provides two command-line utilities for sorting files+and directories on FAT filesystems. The `phatsort` utility sorts files and+directories that are already on the filesystem. The `seqcp` utility copies+files and directories to the filesystem in sorted order.++There are many MP3 players that allow you to mount the device as external+storage and manage the media yourself. The storage generally uses a FAT+filesystem. When copying multiple files onto the storage, using the command+line (`cp`/`mv`) or a GUI, they are generally stored in an arbitrary order.+This is not a problem if the firmware of the MP3 player sorts by filename, but+many MP3 players use the order in the FAT filesystem without sorting, which+results in podcasts and album tracks being played out of order.++There are some utilities that sort the FAT tables of an unmounted filesystem.+(See [Related Software](#related-software) for information and links.)+Unfortunately, there are many devices for which this does not work. PhatSort+takes a different approach to solving the problem. It works by creating new+directories and moving ("renaming") the files in the desired order, while the+filesystem is mounted. This method works on all devices that have been tried+so far.++PhatSort also (optionally) forces the filesystem buffers to be written to the+storage media after each change. This helps avoid write failures when using+devices that have problems with writing large amounts of data. Note that the+`seqcp` utility helps with this issue even on non-FAT filesystems.++## CLI++### Requirements++PhatSort has only been tested on Linux. It *might* work on other operating+systems. Scripts that are output use POSIX shell commands and therefore+require a POSIX shell to execute.++### Installation++#### `.deb` Package Installation++Check the [Releases][] page for `.deb` packages.++#### `.rpm` Package Installation++[Releases]: <https://github.com/ExtremaIS/phatsort-haskell/releases>++Check the [Releases][] page for `.rpm` packages.++#### Installation From Hackage++Install PhatSort from [Hackage][] using [Cabal][] as follows:++```+$ cabal v2-install phatsort+```++[Hackage]: <https://hackage.haskell.org/package/phatsort>+[Cabal]: <https://www.haskell.org/cabal/>++#### Installation From Stackage++Install PhatSort from [Stackage][] using [Stack][] as follows:++```+$ stack install phatsort+```++[Stackage]: <https://www.stackage.org/package/phatsort>+[Stack]: <https://haskellstack.org/>++### Usage++See the [`phatsort`](doc/phatsort.1.md) and [`seqcp`](doc/seqcp.1.md) man+pages for usage information.++## Related Software++[`FATSort`](https://fatsort.sourceforge.io/) is a command-line utility that+sorts unmounted FAT filesystems by direct manipulation of the FAT tables.+Unfortunately, there are many devices for which this does not work.++[`YAFS`](http://www.luisrios.eti.br/public/en_us/projects/yafs/) is a+command-line utility that sorts unmounted FAT filesystems by direct+manipulation of the FAT tables.+[`Visual YAFS`](http://www.luisrios.eti.br/public/en_us/projects/visual_yafs/)+provides a GUI. I have not tried either of these.++[`DriveSort`](http://www.anerty.net/software/file/DriveSort/) is Windows GUI+software that sorts unmounted FAT filesystems by direct manipulation of the+FAT tables. I have not tried it.++## Project++### Links++* Hackage: <https://hackage.haskell.org/package/phatsort>+* Stackage: <https://www.stackage.org/package/phatsort>+* GitHub: <https://github.com/ExtremaIS/phatsort-haskell>+* GitHub Actions CI: <https://github.com/ExtremaIS/phatsort-haskell/actions>++### Tags++All releases are tagged in the `main` branch. Release tags are signed using+the+[`security@extrema.is` GPG key](http://keys.gnupg.net/pks/lookup?op=vindex&fingerprint=on&search=0x1D484E4B4705FADF).++### Contribution++Issues and feature requests are tracked on GitHub:+<https://github.com/ExtremaIS/phatsort-haskell/issues>++Issues may also be submitted via email to <bugs@extrema.is>.++### License++This project is released under the+[MIT License](https://opensource.org/licenses/MIT) as specified in the+[`LICENSE`](LICENSE) file.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ app/LibOA.hs view
@@ -0,0 +1,141 @@+------------------------------------------------------------------------------+-- |+-- Module : LibOA+-- Description : supplementary functions for optparse-applicative+-- Copyright : Copyright (c) 2019-2022 Travis Cardwell+-- License : MIT+--+-- This is a collection of functions that I often use with+-- @optparse-applicative@. I do not feel that it is worth maintaining yet+-- another helper package on Hackage, so I just copy the code to different+-- projects as required. If the library grows to a substantial size or others+-- with to use it, I will reconsider.+--+-- Revision: 2022-01-02+------------------------------------------------------------------------------++{-# LANGUAGE CPP #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE TupleSections #-}++module LibOA+ ( -- * Options+ -- $Options+ helper+ , versioner+ -- * Utilities+ , commands+ -- * Help+ , (<||>)+ , section+ , table+ , table_+ , vspace+ ) where++-- https://hackage.haskell.org/package/ansi-wl-pprint+import qualified Text.PrettyPrint.ANSI.Leijen as Doc+import Text.PrettyPrint.ANSI.Leijen (Doc)++-- https://hackage.haskell.org/package/base+import Data.List (intersperse, transpose)+import Data.Maybe (fromMaybe)+#if !MIN_VERSION_base (4,11,0)+import Data.Monoid ((<>))+#endif++-- https://hackage.haskell.org/package/optparse-applicative+import qualified Options.Applicative as OA+#if MIN_VERSION_optparse_applicative (0,16,0)+import qualified Options.Applicative.Builder.Internal as OABI+#endif+import qualified Options.Applicative.Common as OAC+import qualified Options.Applicative.Types as OAT++------------------------------------------------------------------------------+-- $Options+--+-- Option descriptions are not capitalized.++-- | A hidden @-h@ / @--help@ option that always fails, showing the help+--+-- This is the same as 'OA.helper' except that it has a different help+-- message.+helper :: OA.Parser (a -> a)+#if MIN_VERSION_optparse_applicative (0,16,0)+helper = OA.option helpReader $ mconcat+ [ OA.short 'h'+ , OA.long "help"+ , OA.value id+ , OA.metavar ""+ , OABI.noGlobal+ , OA.noArgError (OA.ShowHelpText Nothing)+ , OA.help "show this help text"+ , OA.hidden+ ]+ where+ helpReader = do+ potentialCommand <- OAT.readerAsk+ OA.readerAbort $ OA.ShowHelpText (Just potentialCommand)+#else+helper = OA.abortOption OA.ShowHelpText $ mconcat+ [ OA.short 'h'+ , OA.long "help"+ , OA.help "show help and exit"+ , OA.hidden+ ]+#endif++-- | A hidden @--version@ option that always fails, showing the version+versioner+ :: String -- ^ version string+ -> OA.Parser (a -> a)+versioner verStr = OA.infoOption verStr $ mconcat+ [ OA.long "version"+ , OA.help "show version and exit"+ , OA.hidden+ ]++------------------------------------------------------------------------------+-- $Utilities++-- | Get a list of commands for a parser+commands :: OA.Parser a -> [String]+commands =+ let go _ opt = case OAT.optMain opt of+ OAT.CmdReader _ cmds _ -> reverse cmds+ _otherReader -> []+ in concat . OAC.mapParser go++------------------------------------------------------------------------------+-- $Help++-- | Insert a blank line between two documents+(<||>) :: Doc -> Doc -> Doc+d1 <||> d2 = d1 <> Doc.line <> Doc.line <> d2+infixr 5 <||>++-- | Create a section with a title and indented body+section :: String -> Doc -> Doc+section title = (Doc.text title Doc.<$$>) . Doc.indent 2++-- | Create a table, with formatting+table :: Int -> [[(String, Doc -> Doc)]] -> Doc+table sep rows = Doc.vcat $+ map (fromMaybe Doc.empty . foldr go Nothing . zip lengths) rows+ where+ lengths :: [Int]+ lengths = map ((+) sep . maximum . map (length . fst)) $ transpose rows++ go :: (Int, (String, Doc -> Doc)) -> Maybe Doc -> Maybe Doc+ go (len, (s, f)) = Just . \case+ Just doc -> Doc.fill len (f $ Doc.string s) <> doc+ Nothing -> f $ Doc.string s++-- | Create a table, without formatting+table_ :: Int -> [[String]] -> Doc+table_ sep = table sep . (map . map) (, id)++-- | Vertically space documents with blank lines between them+vspace :: [Doc] -> Doc+vspace = mconcat . intersperse (Doc.line <> Doc.line)
+ app/Options.hs view
@@ -0,0 +1,133 @@+------------------------------------------------------------------------------+-- |+-- Module : Options+-- Description : CLI options and help documentation+-- Copyright : Copyright (c) 2019-2022 Travis Cardwell+-- License : MIT+------------------------------------------------------------------------------++module Options where++-- https://hackage.haskell.org/package/ansi-wl-pprint+import Text.PrettyPrint.ANSI.Leijen (Doc)++-- https://hackage.haskell.org/package/base+import Data.Bool (bool)++-- https://hackage.haskell.org/package/optparse-applicative+import qualified Options.Applicative as OA++-- (phatsort)+import PhatSort.SortOptions+ ( SortCase(CaseInsensitive, CaseSensitive)+ , SortFirst(FirstDirs, FirstFiles, FirstNone)+ , SortOrder(OrderName, OrderRandom, OrderTime)+ )++-- (phatsort:executable)+import qualified LibOA++------------------------------------------------------------------------------++caseOption :: OA.Parser SortCase+caseOption = fmap (bool CaseSensitive CaseInsensitive) . OA.switch $+ mconcat+ [ OA.long "case"+ , OA.short 'c'+ , OA.help "case-insensitive sort"+ ]++------------------------------------------------------------------------------++exitCodeHelp :: Doc+exitCodeHelp = LibOA.section "Exit codes:" $ LibOA.table_ 2+ [ ["0", "no error"]+ , ["1", "execution error"]+ , ["2", "command line error"]+ ]++------------------------------------------------------------------------------++firstOption :: OA.Parser SortFirst+firstOption = OA.option (OA.eitherReader parseFirst) $ mconcat+ [ OA.long "first"+ , OA.short 'f'+ , OA.metavar "TYPE"+ , OA.value FirstNone+ , OA.help "sort certain directory entries first"+ ]+ where+ parseFirst :: String -> Either String SortFirst+ parseFirst "dirs" = Right FirstDirs+ parseFirst "files" = Right FirstFiles+ parseFirst _ = Left "unknown first option; \"dirs\" or \"files\" expected"++------------------------------------------------------------------------------++firstTypeHelp :: Doc+firstTypeHelp = LibOA.section "TYPE choices:" $ LibOA.table_ 2+ [ ["dirs", "sort directories before files"]+ , ["files", "sort files before directories"]+ ]++------------------------------------------------------------------------------++noSyncOption :: OA.Parser Bool+noSyncOption = fmap not . OA.switch $ mconcat+ [ OA.long "no-sync"+ , OA.short 'n'+ , OA.help "do not sync after each command"+ ]++------------------------------------------------------------------------------++orderOption :: OA.Parser SortOrder+orderOption = OA.option (OA.eitherReader parseOrder) $ mconcat+ [ OA.long "order"+ , OA.short 'o'+ , OA.metavar "ORDER"+ , OA.value OrderName+ , OA.help "desired order (default: name)"+ ]+ where+ parseOrder :: String -> Either String SortOrder+ parseOrder "name" = Right OrderName+ parseOrder "time" = Right OrderTime+ parseOrder "random" = Right OrderRandom+ parseOrder _ = Left "unknown ORDER"++------------------------------------------------------------------------------++orderHelp :: Doc+orderHelp = LibOA.section "ORDER choices:" $ LibOA.table_ 2+ [ ["name", "sort by filename"]+ , ["time", "sort by modification time"]+ , ["random", "random order"]+ ]++------------------------------------------------------------------------------++reverseOption :: OA.Parser Bool+reverseOption = OA.switch $ mconcat+ [ OA.long "reverse"+ , OA.short 'r'+ , OA.help "reverse sort"+ ]++------------------------------------------------------------------------------++scriptOption :: OA.Parser Bool+scriptOption = OA.switch $ mconcat+ [ OA.long "script"+ , OA.short 's'+ , OA.help "output script instead of executing"+ ]++------------------------------------------------------------------------------++verboseOption :: OA.Parser Bool+verboseOption = OA.switch $ mconcat+ [ OA.long "verbose"+ , OA.short 'v'+ , OA.help "display progress"+ ]
+ app/phatsort.hs view
@@ -0,0 +1,79 @@+------------------------------------------------------------------------------+-- |+-- Module : Main+-- Description : phatsort CLI+-- Copyright : Copyright (c) 2019-2022 Travis Cardwell+-- License : MIT+------------------------------------------------------------------------------++module Main (main) where++-- https://hackage.haskell.org/package/base+import Control.Applicative (some)+import qualified Data.List.NonEmpty as NonEmpty+import Data.List.NonEmpty (NonEmpty)+import System.Exit (ExitCode(ExitFailure), exitWith)+import System.IO (hPutStrLn, stderr)++-- https://hackage.haskell.org/package/optparse-applicative+import qualified Options.Applicative as OA++-- (phatsort)+import PhatSort (version)+import PhatSort.Cmd.PhatSort (Options(Options), runIO)++-- (phatsort:executable)+import qualified LibOA+import Options+ ( caseOption, exitCodeHelp, firstOption, firstTypeHelp, noSyncOption+ , orderHelp, orderOption, reverseOption, scriptOption, verboseOption+ )++------------------------------------------------------------------------------++options :: OA.Parser Options+options = Options+ <$> caseOption+ <*> firstOption+ <*> noSyncOption+ <*> orderOption+ <*> reverseOption+ <*> scriptOption+ <*> verboseOption+ <*> targetArguments++------------------------------------------------------------------------------++targetArguments :: OA.Parser (NonEmpty FilePath)+targetArguments = fmap NonEmpty.fromList . some . OA.strArgument $ mconcat+ [ OA.metavar "TARGET ..."+ , OA.help "target directories"+ ]++------------------------------------------------------------------------------++parseOptions :: IO Options+parseOptions = OA.execParser+ . OA.info (LibOA.helper <*> LibOA.versioner version <*> options)+ $ mconcat+ [ OA.fullDesc+ , OA.progDesc "FAT filesystem sort utility"+ , OA.failureCode 2+ , OA.footerDoc . Just $ LibOA.vspace+ [ firstTypeHelp+ , orderHelp+ , exitCodeHelp+ ]+ ]++------------------------------------------------------------------------------++errorExit :: String -> IO a+errorExit message = do+ hPutStrLn stderr $ "error: " ++ message+ exitWith $ ExitFailure 1++------------------------------------------------------------------------------++main :: IO ()+main = either errorExit pure =<< runIO =<< parseOptions
+ app/seqcp.hs view
@@ -0,0 +1,123 @@+------------------------------------------------------------------------------+-- |+-- Module : Main+-- Description : seqcp CLI+-- Copyright : Copyright (c) 2019-2022 Travis Cardwell+-- License : MIT+------------------------------------------------------------------------------++{-# LANGUAGE RecordWildCards #-}++module Main (main) where++-- https://hackage.haskell.org/package/base+import Control.Applicative (some)+import Data.Bifunctor (first)+import Data.List.NonEmpty (nonEmpty)+import System.Exit (ExitCode(ExitFailure), exitWith)+import System.IO (hPutStrLn, stderr)++-- https://hackage.haskell.org/package/optparse-applicative+import qualified Options.Applicative as OA++-- (phatsort)+import PhatSort (version)+import PhatSort.Cmd.SeqCp+ ( Options+ ( Options, optCase, optFirst, optSync, optOrder, optReverse, optScript+ , optVerbose, optSources, optDestination+ )+ , runIO+ )+import PhatSort.SortOptions (SortCase, SortFirst, SortOrder)++-- (phatsort:executable)+import qualified LibOA+import Options+ ( caseOption, exitCodeHelp, firstOption, firstTypeHelp, noSyncOption+ , orderHelp, orderOption, reverseOption, scriptOption, verboseOption+ )++------------------------------------------------------------------------------++data Options'+ = Options'+ { optCase :: !SortCase+ , optFirst :: !SortFirst+ , optSync :: !Bool+ , optOrder :: !SortOrder+ , optReverse :: !Bool+ , optScript :: !Bool+ , optVerbose :: !Bool+ , optArgs :: ![FilePath]+ }+ deriving Show++------------------------------------------------------------------------------++options :: OA.Parser Options'+options = Options'+ <$> caseOption+ <*> firstOption+ <*> noSyncOption+ <*> orderOption+ <*> reverseOption+ <*> scriptOption+ <*> verboseOption+ <*> arguments++------------------------------------------------------------------------------++arguments :: OA.Parser [FilePath]+arguments = some . OA.strArgument $ mconcat+ [ OA.metavar "SOURCE ... DESTINATION"+ , OA.help "source directories/files and destination directory"+ ]++------------------------------------------------------------------------------++parseOptions :: IO Options+parseOptions = processOptions =<< OA.execParser info+ where+ info :: OA.ParserInfo Options'+ info+ = OA.info (LibOA.helper <*> LibOA.versioner version <*> options)+ $ mconcat+ [ OA.fullDesc+ , OA.progDesc "sequentially copy files and directories"+ , OA.failureCode 2+ , OA.footerDoc . Just $ LibOA.vspace+ [ firstTypeHelp+ , orderHelp+ , exitCodeHelp+ ]+ ]++ processOptions :: Options' -> IO Options+ processOptions Options'{..} = case first nonEmpty <$> unsnoc optArgs of+ Just (Just optSources, optDestination) -> pure Options{..}+ Just (Nothing, _destination) -> usageExit "Missing: DESTINATION"+ Nothing -> usageExit "Missing: SOURCE ... DESTINATION"++ unsnoc :: [FilePath] -> Maybe ([FilePath], FilePath)+ unsnoc (x:xs)+ | null xs = Just ([], x)+ | otherwise = first (x :) <$> unsnoc xs+ unsnoc [] = Nothing++ usageExit :: String -> IO a+ usageExit msg = OA.handleParseResult . OA.Failure $+ OA.parserFailure OA.defaultPrefs info (OA.ErrorMsg msg) []++------------------------------------------------------------------------------++errorExit :: String -> IO a+errorExit message = do+ hPutStrLn stderr $ "error: " ++ message+ exitWith $ ExitFailure 1++------------------------------------------------------------------------------++main :: IO ()+main = either errorExit pure =<< runIO =<< parseOptions+
+ phatsort.cabal view
@@ -0,0 +1,127 @@+name: phatsort+version: 0.5.0.1+category: Utils+synopsis: FAT filesystem sort utility+description:+ This package provides a utility for sorting files and directories on a FAT+ filesystem. Please see the README on GitHub at+ <https://github.com/ExtremaIS/phatsort-haskell#readme>.++homepage: https://github.com/ExtremaIS/phatsort-haskell#readme+bug-reports: https://github.com/ExtremaIS/phatsort-haskell/issues+author: Travis Cardwell <travis.cardwell@extrema.is>+maintainer: Travis Cardwell <travis.cardwell@extrema.is>+copyright: Copyright (c) 2019-2022 Travis Cardwell+license: MIT+license-file: LICENSE++cabal-version: 1.24+build-type: Simple+tested-with:+ GHC ==8.2.2+ || ==8.4.4+ || ==8.6.5+ || ==8.8.4+ || ==8.10.7+ || ==9.0.2+ || ==9.2.1++extra-source-files:+ CHANGELOG.md+ README.md++source-repository head+ type: git+ location: https://github.com/ExtremaIS/phatsort-haskell.git++flag write-hie+ description: write .hie files+ default: False++library+ hs-source-dirs: src+ exposed-modules:+ PhatSort+ , PhatSort.Cmd.PhatSort+ , PhatSort.Cmd.SeqCp+ , PhatSort.Monad.FileSystem+ , PhatSort.Monad.Stdio+ , PhatSort.Monad.Sync+ , PhatSort.Monad.Trans.Error+ , PhatSort.Script+ , PhatSort.SortOptions+ other-modules:+ Paths_phatsort+ if !os(windows)+ other-modules:+ PhatSort.Monad.Sync.Unistd+ build-depends:+ base >=4.7 && <5+ , directory >=1.3 && <1.4+ , filepath >=1.4 && <1.5+ , MonadRandom >=0.5 && <0.6+ , random-shuffle >=0.0.4 && <0.1+ , transformers >=0.5 && <0.6+ , unix-compat >=0.5 && <0.6+ default-language: Haskell2010+ default-extensions:+ OverloadedStrings+ if flag(write-hie)+ ghc-options: -Wall -fwrite-ide-info -hiedir=.hie+ else+ ghc-options: -Wall++executable phatsort+ hs-source-dirs: app+ main-is: phatsort.hs+ other-modules:+ LibOA+ , Options+ build-depends:+ ansi-wl-pprint >=0.6 && <0.7+ , base+ , optparse-applicative >=0.14 && <0.18+ , phatsort+ default-language: Haskell2010+ if flag(write-hie)+ ghc-options: -Wall -fwrite-ide-info -hiedir=.hie+ else+ ghc-options: -Wall++executable seqcp+ hs-source-dirs: app+ main-is: seqcp.hs+ other-modules:+ LibOA+ , Options+ build-depends:+ ansi-wl-pprint >=0.6 && <0.7+ , base+ , optparse-applicative >=0.14 && <0.18+ , phatsort+ default-language: Haskell2010+ if flag(write-hie)+ ghc-options: -Wall -fwrite-ide-info -hiedir=.hie+ else+ ghc-options: -Wall++test-suite phatsort-test+ type: exitcode-stdio-1.0+ hs-source-dirs: test+ main-is: Spec.hs+ build-depends:+ base+ , MonadRandom+ , phatsort+ , tasty >=1.0 && <1.5+ , tasty-hunit >=0.10 && <0.11+ , transformers >=0.5 && <0.7+ if impl(ghc >= 8.6.1)+ other-modules:+ PhatSort.Cmd.PhatSort.Mock+ , PhatSort.Cmd.SeqCp.Mock+ , TestLib+ build-depends:+ HMock >=0.5.1 && <0.6+ default-language: Haskell2010+ ghc-options: -Wall -threaded -rtsopts -with-rtsopts=-N
+ src/PhatSort.hs view
@@ -0,0 +1,27 @@+------------------------------------------------------------------------------+-- |+-- Module : PhatSort+-- Description : metadata+-- Copyright : Copyright (c) 2019-2022 Travis Cardwell+-- License : MIT+------------------------------------------------------------------------------++module PhatSort+ ( -- * Constants+ version+ ) where++-- https://hackage.haskell.org/package/base+import Data.Version (showVersion)++-- (phatsort:cabal)+import qualified Paths_phatsort as Project++------------------------------------------------------------------------------+-- $Constants++-- | PhatSort version string (\"@phatsort-haskell X.X.X.X@\")+--+-- @since 0.5.0.0+version :: String+version = "phatsort-haskell " ++ showVersion Project.version
+ src/PhatSort/Cmd/PhatSort.hs view
@@ -0,0 +1,231 @@+------------------------------------------------------------------------------+-- |+-- Module : PhatSort.Cmd.PhatSort+-- Description : phatsort command implementation+-- Copyright : Copyright (c) 2019-2022 Travis Cardwell+-- License : MIT+------------------------------------------------------------------------------++{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE ScopedTypeVariables #-}++module PhatSort.Cmd.PhatSort+ ( -- * Options+ Options(..)+ -- * API+ , runIO+ , run+ ) where++-- https://hackage.haskell.org/package/base+import Control.Monad (forM, forM_, unless, when)+import Data.Char (toLower)+import Data.List (dropWhileEnd, isSuffixOf, partition, sortBy)+import qualified Data.List.NonEmpty as NonEmpty+import Data.List.NonEmpty (NonEmpty)+import Data.Ord (comparing)++-- https://hackage.haskell.org/package/filepath+import System.FilePath ((</>), takeDirectory)++-- https://hackage.haskell.org/package/MonadRandom+import Control.Monad.Random.Class (MonadRandom)++-- https://hackage.haskell.org/package/random-shuffle+import System.Random.Shuffle (shuffleM)++-- (phatsort)+import qualified PhatSort.Monad.FileSystem as FS+import PhatSort.Monad.FileSystem (MonadFileSystem)+import qualified PhatSort.Monad.Stdio as Stdio+import PhatSort.Monad.Stdio (MonadStdio)+import qualified PhatSort.Monad.Sync as Sync+import PhatSort.Monad.Sync (MonadSync)+import qualified PhatSort.Monad.Trans.Error as Error+import PhatSort.Monad.Trans.Error (ErrorT)+import qualified PhatSort.Script as Script+import PhatSort.SortOptions+ ( SortCase(CaseInsensitive, CaseSensitive)+ , SortFirst(FirstDirs, FirstFiles, FirstNone)+ , SortOrder(OrderName, OrderRandom, OrderTime)+ )++------------------------------------------------------------------------------+-- $Options++-- | Command options+data Options+ = Options+ { optCase :: !SortCase+ , optFirst :: !SortFirst+ , optSync :: !Bool+ , optOrder :: !SortOrder+ , optReverse :: !Bool+ , optScript :: !Bool+ , optVerbose :: !Bool+ , optTargets :: !(NonEmpty FilePath)+ }+ deriving Show++------------------------------------------------------------------------------+-- $API++-- | Run the command in the 'IO' monad+runIO :: Options -> IO (Either String ())+runIO = Error.run . run++------------------------------------------------------------------------------++-- | Run the command+run+ :: forall m+ . (MonadFileSystem m, MonadRandom m, MonadStdio m, MonadSync m)+ => Options+ -> ErrorT m ()+run Options{..} = do+ targets <- mapM getTarget $ NonEmpty.toList optTargets+ forM_ targets $ \Target{..} -> do+ putProgress targetArgPath+ mvDir targetDstPath targetSrcPath >> sync+ mkDir targetDstPath >> sync+ processDir targetArgPath targetSrcPath targetDstPath+ rmDir targetSrcPath >> sync+ where+ mkDir :: FilePath -> ErrorT m ()+ mkDir path+ | optScript = Stdio.putStrLn $ Script.formatCommand ["mkdir", path]+ | otherwise = Error.errorTE $ FS.createDirectory path++ mvDir :: FilePath -> FilePath -> ErrorT m ()+ mvDir srcPath dstPath+ | optScript =+ Stdio.putStrLn $ Script.formatCommand ["mv", srcPath, dstPath]+ | otherwise = Error.errorTE $ FS.renameDirectory srcPath dstPath++ mvFile :: FilePath -> FilePath -> ErrorT m ()+ mvFile srcPath dstPath+ | optScript =+ Stdio.putStrLn $ Script.formatCommand ["mv", srcPath, dstPath]+ | otherwise = Error.errorTE $ FS.renameFile srcPath dstPath++ processDir :: FilePath -> FilePath -> FilePath -> ErrorT m ()+ processDir argPath srcPath dstPath = do+ let goDir Entry{..} = do+ putProgress entryArgPath+ mkDir entryDstPath >> sync+ processDir entryArgPath entrySrcPath entryDstPath+ rmDir entrySrcPath >> sync+ goFile Entry{..} = do+ putProgress entryArgPath+ mvFile entrySrcPath entryDstPath >> sync+ go entry@Entry{..}+ | FS.isDirectory entryStatus = goDir entry+ | otherwise = goFile entry+ allEntries <- getEntries optScript argPath srcPath dstPath+ let (dirEntries, fileEntries) =+ partition (FS.isDirectory . entryStatus) allEntries+ case optFirst of+ FirstNone -> mapM_ go =<< sortEntries allEntries+ FirstDirs -> do+ mapM_ goDir =<< sortEntries dirEntries+ mapM_ goFile =<< sortEntries fileEntries+ FirstFiles -> do+ mapM_ goFile =<< sortEntries fileEntries+ mapM_ goDir =<< sortEntries dirEntries++ putProgress :: FilePath -> ErrorT m ()+ putProgress path+ | not optVerbose = pure ()+ | optScript = Stdio.putStrLn $ Script.formatCommand ["echo", path]+ | otherwise = Stdio.putStrLn path++ rmDir :: FilePath -> ErrorT m ()+ rmDir path+ | optScript = Stdio.putStrLn $ Script.formatCommand ["rmdir", path]+ | otherwise = Error.errorTE $ FS.removeDirectory path++ sortEntries :: [Entry] -> ErrorT m [Entry]+ sortEntries entries = Error.lift $ do+ let compareNames = case optCase of+ CaseSensitive -> comparing entryName+ CaseInsensitive -> comparing (map toLower . entryName)+ compareTimes = comparing (FS.modificationTime . entryStatus)+ reverse' = if optReverse then reverse else id+ case optOrder of+ OrderName -> pure . reverse' $ sortBy compareNames entries+ OrderTime -> pure . reverse' $ sortBy compareTimes entries+ OrderRandom -> shuffleM entries++ sync :: ErrorT m ()+ sync+ | not optSync = pure ()+ | optScript = Stdio.putStrLn $ Script.formatCommand ["sync"]+ | otherwise = Sync.sync++------------------------------------------------------------------------------+-- $Internal++-- | Target directory+data Target+ = Target+ { targetArgPath :: !FilePath -- ^ path for verbose and errors+ , targetSrcPath :: !FilePath -- ^ absolute source path (@-phat@)+ , targetDstPath :: !FilePath -- ^ absolute destination path+ }++------------------------------------------------------------------------------++-- | Create a 'Target' for a target argument, performing checks+getTarget+ :: MonadFileSystem m+ => FilePath -- ^ target argument+ -> ErrorT m Target+getTarget targetArgPath = do+ when ("-phat" `isSuffixOf` targetArgPath) .+ Error.throw $ "-phat directory: " ++ targetArgPath+ targetDstPath <- Error.errorTE .+ FS.makeAbsolute $ dropWhileEnd (== '/') targetArgPath+ tgtStatus <- Error.errorTE $ FS.getFileStatus targetDstPath+ unless (FS.isDirectory tgtStatus) .+ Error.throw $ "not a directory: " ++ targetArgPath+ let parentDir = takeDirectory targetDstPath+ parentStatus <- Error.errorTE $ FS.getFileStatus parentDir+ unless (FS.deviceID tgtStatus == FS.deviceID parentStatus) .+ Error.throw $ "mount point: " ++ targetArgPath+ let targetSrcPath = targetDstPath ++ "-phat"+ exists <- Error.errorTE $ FS.doesPathExist targetSrcPath+ when exists . Error.throw $ "already exists: " ++ targetArgPath ++ "-phat"+ pure Target{..}++------------------------------------------------------------------------------++-- | Directory entry+data Entry+ = Entry+ { entryName :: !FilePath -- ^ filename+ , entryArgPath :: !FilePath -- ^ path for verbose and errors+ , entrySrcPath :: !FilePath -- ^ absolute source path+ , entryDstPath :: !FilePath -- ^ absolute desination path+ , entryStatus :: !FS.FileStatus+ }++------------------------------------------------------------------------------++-- | Get entries in the specified directory+getEntries+ :: MonadFileSystem m+ => Bool -- ^ script?+ -> FilePath -- ^ path for verbose and errors+ -> FilePath -- ^ absolute source path+ -> FilePath -- ^ absolute destination path+ -> ErrorT m [Entry]+getEntries isScript argDir srcDir dstDir = do+ names <- Error.errorTE $+ FS.listDirectory (if isScript then dstDir else srcDir)+ forM names $ \entryName -> do+ let entryArgPath = argDir </> entryName+ entrySrcPath = srcDir </> entryName+ entryDstPath = dstDir </> entryName+ entryStatus <- Error.errorTE $+ FS.getFileStatus (if isScript then entryDstPath else entrySrcPath)+ pure Entry{..}
+ src/PhatSort/Cmd/SeqCp.hs view
@@ -0,0 +1,205 @@+------------------------------------------------------------------------------+-- |+-- Module : PhatSort.Cmd.SeqCp+-- Description : seqcp command implementation+-- Copyright : Copyright (c) 2019-2022 Travis Cardwell+-- License : MIT+------------------------------------------------------------------------------++{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE ScopedTypeVariables #-}++module PhatSort.Cmd.SeqCp+ ( -- * Options+ Options(..)+ -- * API+ , runIO+ , run+ ) where++-- https://hackage.haskell.org/package/base+import Control.Monad (forM, forM_, unless, when)+import Data.Char (toLower)+import Data.List (dropWhileEnd, isPrefixOf, partition, sortBy)+import qualified Data.List.NonEmpty as NonEmpty+import Data.List.NonEmpty (NonEmpty)+import Data.Ord (comparing)++-- https://hackage.haskell.org/package/filepath+import System.FilePath ((</>), splitDirectories, takeFileName)++-- https://hackage.haskell.org/package/MonadRandom+import Control.Monad.Random.Class (MonadRandom)++-- https://hackage.haskell.org/package/random-shuffle+import System.Random.Shuffle (shuffleM)++-- (phatsort)+import qualified PhatSort.Monad.FileSystem as FS+import PhatSort.Monad.FileSystem (MonadFileSystem)+import qualified PhatSort.Monad.Stdio as Stdio+import PhatSort.Monad.Stdio (MonadStdio)+import qualified PhatSort.Monad.Sync as Sync+import PhatSort.Monad.Sync (MonadSync)+import qualified PhatSort.Monad.Trans.Error as Error+import PhatSort.Monad.Trans.Error (ErrorT)+import qualified PhatSort.Script as Script+import PhatSort.SortOptions+ ( SortCase(CaseInsensitive, CaseSensitive)+ , SortFirst(FirstDirs, FirstFiles, FirstNone)+ , SortOrder(OrderName, OrderRandom, OrderTime)+ )++------------------------------------------------------------------------------+-- $Options++data Options+ = Options+ { optCase :: !SortCase+ , optFirst :: !SortFirst+ , optSync :: !Bool+ , optOrder :: !SortOrder+ , optReverse :: !Bool+ , optScript :: !Bool+ , optVerbose :: !Bool+ , optSources :: !(NonEmpty FilePath)+ , optDestination :: !FilePath+ }+ deriving Show++------------------------------------------------------------------------------+-- $API++-- | Run the command in the 'IO' monad+runIO :: Options -> IO (Either String ())+runIO = Error.run . run++------------------------------------------------------------------------------++-- | Run the command+run+ :: forall m+ . (MonadFileSystem m, MonadRandom m, MonadStdio m, MonadSync m)+ => Options+ -> ErrorT m ()+run Options{..} = copyTargets =<< getArgTargets optSources optDestination+ where+ copyDir :: FilePath -> FilePath -> FilePath -> ErrorT m ()+ copyDir argPath srcPath dstPath = do+ putProgress argPath+ mkDir dstPath >> sync+ copyTargets =<< getDirTargets argPath srcPath dstPath++ copyFile :: FilePath -> FilePath -> FilePath -> ErrorT m ()+ copyFile argPath srcPath dstPath = do+ putProgress argPath+ cp srcPath dstPath >> sync++ copyTargets :: [Target] -> ErrorT m ()+ copyTargets targets = do+ sortedTargets <- sortTargets targets+ forM_ sortedTargets $ \Target{..} ->+ if FS.isDirectory targetStatus+ then copyDir targetArgPath targetSrcPath targetDstPath+ else copyFile targetArgPath targetSrcPath targetDstPath++ cp :: FilePath -> FilePath -> ErrorT m ()+ cp srcPath dstPath+ | optScript =+ Stdio.putStrLn $ Script.formatCommand ["cp", srcPath, dstPath]+ | otherwise = Error.errorTE $ FS.copyFile srcPath dstPath++ mkDir :: FilePath -> ErrorT m ()+ mkDir path+ | optScript = Stdio.putStrLn $ Script.formatCommand ["mkdir", path]+ | otherwise = Error.errorTE $ FS.createDirectory path++ putProgress :: FilePath -> ErrorT m ()+ putProgress path+ | not optVerbose = pure ()+ | optScript = Stdio.putStrLn $ Script.formatCommand ["echo", path]+ | otherwise = Stdio.putStrLn path++ sortTargets :: [Target] -> ErrorT m [Target]+ sortTargets targets =+ let compareNames = case optCase of+ CaseSensitive -> comparing targetName+ CaseInsensitive -> comparing (map toLower . targetName)+ compareTimes = comparing (FS.modificationTime . targetStatus)+ reverse' = if optReverse then reverse else id+ go = case optOrder of+ OrderName -> pure . reverse' . sortBy compareNames+ OrderTime -> pure . reverse' . sortBy compareTimes+ OrderRandom -> shuffleM+ (dirTargets, fileTargets) =+ partition (FS.isDirectory . targetStatus) targets+ in Error.lift $ case optFirst of+ FirstNone -> go targets+ FirstDirs -> (++) <$> go dirTargets <*> go fileTargets+ FirstFiles -> (++) <$> go fileTargets <*> go dirTargets++ sync :: ErrorT m ()+ sync+ | not optSync = pure ()+ | optScript = Stdio.putStrLn $ Script.formatCommand ["sync"]+ | otherwise = Sync.sync++------------------------------------------------------------------------------+-- $Internal++-- | Target file or directory+data Target+ = Target+ { targetArgPath :: !FilePath -- ^ path for verbose and errors+ , targetSrcPath :: !FilePath -- ^ absolute source path+ , targetDstPath :: !FilePath -- ^ absolute destination path+ , targetName :: !FilePath -- ^ file or directory name+ , targetStatus :: !FS.FileStatus+ }++------------------------------------------------------------------------------++-- | Create 'Target's for arguments, performing checks+getArgTargets+ :: MonadFileSystem m+ => NonEmpty FilePath -- ^ sources+ -> FilePath -- ^ destination directory+ -> ErrorT m [Target]+getArgTargets sources destDir = do+ destPath <- Error.errorTE .+ FS.makeAbsolute $ dropWhileEnd (== '/') destDir+ destStatus <- Error.errorTE $ FS.getFileStatus destPath+ unless (FS.isDirectory destStatus) .+ Error.throw $ "not a directory: " ++ destDir+ let destDirs = splitDirectories destPath+ forM (NonEmpty.toList sources) $ \targetArgPath -> do+ targetSrcPath <- Error.errorTE .+ FS.makeAbsolute $ dropWhileEnd (== '/') targetArgPath+ let targetName = takeFileName targetSrcPath+ targetDstPath = destPath </> targetName+ targetStatus <- Error.errorTE $ FS.getFileStatus targetSrcPath+ when (FS.isDirectory targetStatus) .+ when (splitDirectories targetSrcPath `isPrefixOf` destDirs) $+ Error.throw $+ "source directory above target directory: " ++ targetArgPath+ exists <- Error.errorTE $ FS.doesPathExist targetDstPath+ when exists . Error.throw $ "already exists: " ++ targetDstPath+ pure Target{..}++------------------------------------------------------------------------------++-- | Get 'Target's for entries in a directory+getDirTargets+ :: MonadFileSystem m+ => FilePath -- ^ path for verbose and errors+ -> FilePath -- ^ absolute source path+ -> FilePath -- ^ absolute destination path+ -> ErrorT m [Target]+getDirTargets argDir srcDir dstDir = do+ names <- Error.errorTE $ FS.listDirectory srcDir+ forM names $ \targetName -> do+ let targetArgPath = argDir </> targetName+ targetSrcPath = srcDir </> targetName+ targetDstPath = dstDir </> targetName+ targetStatus <- Error.errorTE $ FS.getFileStatus targetSrcPath+ pure Target{..}
+ src/PhatSort/Monad/FileSystem.hs view
@@ -0,0 +1,150 @@+------------------------------------------------------------------------------+-- |+-- Module : PhatSort.Monad.FileSystem+-- Description : filesystem I/O+-- Copyright : Copyright (c) 2019-2022 Travis Cardwell+-- License : MIT+------------------------------------------------------------------------------++module PhatSort.Monad.FileSystem+ ( -- * MonadFileSystem+ MonadFileSystem(..)+ -- * FileStatus+ , FileStatus(..)+ ) where++-- https://hackage.haskell.org/package/base+import System.IO.Error (tryIOError)+import System.Posix.Types (DeviceID, EpochTime)++-- https://hackage.haskell.org/package/directory+import qualified System.Directory as Dir++-- https://hackage.haskell.org/package/transformers+import Control.Monad.Trans.Class (MonadTrans(lift))+import Control.Monad.Trans.Except (ExceptT)++-- https://hackage.haskell.org/package/unix-compat+import qualified System.PosixCompat.Files as Files++------------------------------------------------------------------------------+-- $MonadFileSystem++-- | Filesystem I/O+--+-- @since 0.5.0.0+class Monad m => MonadFileSystem m where+ -- | Copy a file+ copyFile+ :: FilePath -- ^ source+ -> FilePath -- ^ destination+ -> m (Either IOError ())++ -- | Create a directory+ createDirectory :: FilePath -> m (Either IOError ())++ -- | Check if a path exists+ doesPathExist :: FilePath -> m (Either IOError Bool)++ -- | Get file status information+ getFileStatus :: FilePath -> m (Either IOError FileStatus)++ -- | Get a list of directory entries+ listDirectory :: FilePath -> m (Either IOError [FilePath])++ -- | Convert a path into an absolute path+ makeAbsolute :: FilePath -> m (Either IOError FilePath)++ -- | Remove a directory+ removeDirectory :: FilePath -> m (Either IOError ())++ -- | Rename a directory+ renameDirectory+ :: FilePath -- ^ target directory+ -> FilePath -- ^ new directory name+ -> m (Either IOError ())++ -- | Rename a file+ renameFile+ :: FilePath -- ^ target file+ -> FilePath -- ^ new file name+ -> m (Either IOError ())++instance MonadFileSystem IO where+ copyFile = (tryIOError .) . Dir.copyFile+ {-# INLINE copyFile #-}++ createDirectory = tryIOError . Dir.createDirectory+ {-# INLINE createDirectory #-}++ doesPathExist = tryIOError . Dir.doesPathExist+ {-# INLINE doesPathExist #-}++ getFileStatus = tryIOError . fmap toFileStatus . Files.getFileStatus+ {-# INLINE getFileStatus #-}++ listDirectory = tryIOError . Dir.listDirectory+ {-# INLINE listDirectory #-}++ makeAbsolute = tryIOError . Dir.makeAbsolute+ {-# INLINE makeAbsolute #-}++ removeDirectory = tryIOError . Dir.removeDirectory+ {-# INLINE removeDirectory #-}++ renameDirectory = (tryIOError .) . Dir.renameDirectory+ {-# INLINE renameDirectory #-}++ renameFile = (tryIOError .) . Dir.renameFile+ {-# INLINE renameFile #-}++instance MonadFileSystem m => MonadFileSystem (ExceptT e m) where+ copyFile = (lift .) . copyFile+ {-# INLINE copyFile #-}++ createDirectory = lift . createDirectory+ {-# INLINE createDirectory #-}++ doesPathExist = lift . doesPathExist+ {-# INLINE doesPathExist #-}++ getFileStatus = lift . getFileStatus+ {-# INLINE getFileStatus #-}++ listDirectory = lift . listDirectory+ {-# INLINE listDirectory #-}++ makeAbsolute = lift . makeAbsolute+ {-# INLINE makeAbsolute #-}++ removeDirectory = lift . removeDirectory+ {-# INLINE removeDirectory #-}++ renameDirectory = (lift .) . renameDirectory+ {-# INLINE renameDirectory #-}++ renameFile = (lift .) . renameFile+ {-# INLINE renameFile #-}++------------------------------------------------------------------------------+-- $FileStatus++-- | Mockable subset of 'Files.FileStatus'+--+-- @since 0.5.0.0+data FileStatus+ = FileStatus+ { deviceID :: !DeviceID+ , isDirectory :: !Bool+ , modificationTime :: !EpochTime+ }++-- | Convert from 'Files.FileStatus' to 'FileStatus'+--+-- @since 0.5.0.0+toFileStatus :: Files.FileStatus -> FileStatus+toFileStatus status = FileStatus+ { deviceID = Files.deviceID status+ , isDirectory = Files.isDirectory status+ , modificationTime = Files.modificationTime status+ }
+ src/PhatSort/Monad/Stdio.hs view
@@ -0,0 +1,38 @@+------------------------------------------------------------------------------+-- |+-- Module : PhatSort.Monad.Stdio+-- Description : standard I/O+-- Copyright : Copyright (c) 2019-2022 Travis Cardwell+-- License : MIT+------------------------------------------------------------------------------++module PhatSort.Monad.Stdio+ ( -- * MonadStdio+ MonadStdio(..)+ ) where++-- https://hackage.haskell.org/package/base+import qualified Prelude+import Prelude hiding (putStrLn)++-- https://hackage.haskell.org/package/transformers+import Control.Monad.Trans.Class (MonadTrans(lift))+import Control.Monad.Trans.Except (ExceptT)++------------------------------------------------------------------------------+-- $MonadStdio++-- | Standard I/O+--+-- @since 0.5.0.0+class Monad m => MonadStdio m where+ -- | Write a 'String' to @STDOUT@, appending a newline+ putStrLn :: String -> m ()++instance MonadStdio IO where+ putStrLn = Prelude.putStrLn+ {-# INLINE putStrLn #-}++instance MonadStdio m => MonadStdio (ExceptT e m) where+ putStrLn = lift . putStrLn+ {-# INLINE putStrLn #-}
+ src/PhatSort/Monad/Sync.hs view
@@ -0,0 +1,47 @@+------------------------------------------------------------------------------+-- |+-- Module : PhatSort.Monad.Sync+-- Description : commit all data in filesystem buffers+-- Copyright : Copyright (c) 2019-2022 Travis Cardwell+-- License : MIT+------------------------------------------------------------------------------++{-# LANGUAGE CPP #-}++module PhatSort.Monad.Sync+ ( -- * MonadSync+ MonadSync(..)+ ) where++-- https://hackage.haskell.org/package/transformers+import Control.Monad.Trans.Class (MonadTrans(lift))+import Control.Monad.Trans.Except (ExceptT)++-- (phatsort)+#ifndef mingw32_HOST_OS+import PhatSort.Monad.Sync.Unistd (c_sync)+#endif++------------------------------------------------------------------------------+-- $MonadSync++-- | Commit all data in filesystem buffers+--+-- @since 0.5.0.0+class Monad m => MonadSync m where+ -- | Commit all data in filesystem buffers+ --+ -- This method calls the @sync@ system call on POSIX systems. It does+ -- nothing on Windows.+ sync :: m ()++instance MonadSync IO where+#ifdef mingw32_HOST_OS+ sync = pure ()+#else+ sync = c_sync+#endif++instance MonadSync m => MonadSync (ExceptT e m) where+ sync = lift sync+ {-# INLINE sync #-}
+ src/PhatSort/Monad/Sync/Unistd.hs view
@@ -0,0 +1,22 @@+------------------------------------------------------------------------------+-- |+-- Module : PhatSort.Monad.Sync.Unistd+-- Description : sync implementation for POSIX systems+-- Copyright : Copyright (c) 2019-2022 Travis Cardwell+-- License : MIT+------------------------------------------------------------------------------++{-# LANGUAGE ForeignFunctionInterface #-}++module PhatSort.Monad.Sync.Unistd+ ( -- * API+ c_sync+ ) where++------------------------------------------------------------------------------++-- | Call the @sync@ system call+--+-- @since 0.5.0.0+foreign import ccall "unistd.h sync"+ c_sync :: IO ()
+ src/PhatSort/Monad/Trans/Error.hs view
@@ -0,0 +1,135 @@+------------------------------------------------------------------------------+-- |+-- Module : PhatSort.Monad.Trans.Error+-- Description : error monad transformer+-- Copyright : Copyright (c) 2019-2022 Travis Cardwell+-- License : MIT+------------------------------------------------------------------------------++module PhatSort.Monad.Trans.Error+ ( -- * ErrorT+ ErrorT+ -- * API+ , errorT+ , errorTE+ , lift+ , liftEither+ , liftEitherE+ , run+ , throw+ , throwE+ ) where++-- https://hackage.haskell.org/package/base+import Control.Exception (Exception(displayException))+import Data.Bifunctor (first)++-- https://hackage.haskell.org/package/transformers+import qualified Control.Monad.Trans.Except as Except+import Control.Monad.Trans.Except (ExceptT(ExceptT))+import qualified Control.Monad.Trans.Class as Trans++------------------------------------------------------------------------------+-- $ErrorT++-- | Transformer used to manage errors: 'ExceptT' 'String'+--+-- @since 0.5.0.0+type ErrorT = ExceptT String++------------------------------------------------------------------------------+-- $API++-- | Manage 'String' errors of an action+--+-- @since 0.5.0.0+errorT+ :: Monad m+ => m (Either String a)+ -> ErrorT m a+errorT = ExceptT+{-# INLINE errorT #-}++------------------------------------------------------------------------------++-- | Manage 'Exception' errors of an action+--+-- @since 0.5.0.0+errorTE+ :: (Exception e, Monad m)+ => m (Either e a)+ -> ErrorT m a+errorTE = ExceptT . fmap (first displayException)+{-# INLINE errorTE #-}++------------------------------------------------------------------------------++-- | Lift an action without errors+--+-- @since 0.5.0.0+lift+ :: Monad m+ => m a+ -> ErrorT m a+lift = Trans.lift+{-# INLINE lift #-}++------------------------------------------------------------------------------++-- | Manage 'String' errors+--+-- @since 0.5.0.0+liftEither+ :: Monad m+ => Either String a+ -> ErrorT m a+liftEither = ExceptT . pure+{-# INLINE liftEither #-}++------------------------------------------------------------------------------++-- | Manage 'Exception' errors+--+-- @since 0.5.0.0+liftEitherE+ :: (Exception e, Monad m)+ => Either e a+ -> ErrorT m a+liftEitherE = ExceptT . pure . first displayException+{-# INLINE liftEitherE #-}++------------------------------------------------------------------------------++-- | Run the transformer+--+-- @since 0.5.0.0+run+ :: Monad m+ => ErrorT m a+ -> m (Either String a)+run = Except.runExceptT+{-# INLINE run #-}++------------------------------------------------------------------------------++-- | Throw a 'String' error+--+-- @since 0.5.0.0+throw+ :: Monad m+ => String+ -> ErrorT m a+throw = Except.throwE+{-# INLINE throw #-}++------------------------------------------------------------------------------++-- | Throw an 'Exception' error+--+-- @since 0.5.0.0+throwE+ :: (Exception e, Monad m)+ => e+ -> ErrorT m a+throwE = Except.throwE . displayException+{-# INLINE throwE #-}
+ src/PhatSort/Script.hs view
@@ -0,0 +1,45 @@+------------------------------------------------------------------------------+-- |+-- Module : PhatSort.Script+-- Description : script API+-- Copyright : Copyright (c) 2019-2022 Travis Cardwell+-- License : MIT+------------------------------------------------------------------------------++module PhatSort.Script+ ( -- * API+ formatCommand+ ) where++-- https://hackage.haskell.org/package/base+import Data.Char (ord)++------------------------------------------------------------------------------++-- | Format a shell command, escaping when necessary+--+-- @since 0.5.0.0+formatCommand+ :: [String] -- ^ command and arguments+ -> String+formatCommand = unwords . map escape+ where+ escape :: String -> String+ escape s+ | all isOkChar s = s+ | otherwise = "'" ++ concatMap tr s ++ "'"++ isOkChar :: Char -> Bool+ isOkChar c =+ let c' = ord c+ in or+ [ (c' - ord 'a') <= 25+ , (c' - ord 'A') <= 25+ , (c' - ord '0') <= 9+ , c `elem` (".-+_/" :: String)+ ]++ tr :: Char -> String+ tr '\'' = "'\"'\"'"+ tr '\0' = ""+ tr c = [c]
+ src/PhatSort/SortOptions.hs view
@@ -0,0 +1,46 @@+------------------------------------------------------------------------------+-- |+-- Module : PhatSort.SortOptions+-- Description : sort options+-- Copyright : Copyright (c) 2019-2022 Travis Cardwell+-- License : MIT+------------------------------------------------------------------------------++module PhatSort.SortOptions+ ( -- * Sort Options+ SortCase(..)+ , SortFirst(..)+ , SortOrder(..)+ ) where++------------------------------------------------------------------------------++-- | Case sensitivity+--+-- @since 0.5.0.0+data SortCase+ = CaseSensitive+ | CaseInsensitive+ deriving (Eq, Show)++------------------------------------------------------------------------------++-- | Sorting certain directory entries first+--+-- @since 0.5.0.0+data SortFirst+ = FirstNone+ | FirstDirs+ | FirstFiles+ deriving (Eq, Show)++------------------------------------------------------------------------------++-- | Sort order+--+-- @since 0.5.0.0+data SortOrder+ = OrderName+ | OrderTime+ | OrderRandom+ deriving (Eq, Show)
+ test/PhatSort/Cmd/PhatSort/Mock.hs view
@@ -0,0 +1,1659 @@+module PhatSort.Cmd.PhatSort.Mock (tests) where++-- https://hackage.haskell.org/package/base+import Control.Monad ((<=<))+import qualified Data.List.NonEmpty as NonEmpty++-- https://hackage.haskell.org/package/HMock+import Test.HMock ((|->), expect, inAnyOrder, inSequence, runMockT)++-- https://hackage.haskell.org/package/tasty+import Test.Tasty (TestTree, testGroup)++-- https://hackage.haskell.org/package/tasty-hunit+import Test.Tasty.HUnit (testCase)++-- (phatsort)+import PhatSort.Cmd.PhatSort+ ( Options+ ( Options, optCase, optFirst, optOrder, optReverse, optScript, optSync+ , optTargets, optVerbose+ )+ , run+ )+import PhatSort.Monad.FileSystem (FileStatus(FileStatus))+import qualified PhatSort.Monad.Trans.Error as Error+import PhatSort.SortOptions+ ( SortCase(CaseInsensitive, CaseSensitive)+ , SortFirst(FirstDirs, FirstFiles, FirstNone)+ , SortOrder(OrderName, OrderRandom, OrderTime)+ )++-- (phatsort:test)+import TestLib++------------------------------------------------------------------------------++defaultOptions :: Options+defaultOptions = Options+ { optCase = CaseSensitive+ , optFirst = FirstNone+ , optSync = True+ , optOrder = OrderName+ , optReverse = False+ , optScript = False+ , optVerbose = False+ , optTargets = NonEmpty.fromList ["one"]+ }++------------------------------------------------------------------------------++testCaseSensitive :: TestTree+testCaseSensitive = testCase "CaseSensitive" . runMockT $ do+ inSequence+ [ expect $ MakeAbsolute "one" |-> Right "/a/b/one"+ , expect $ GetFileStatus "/a/b/one" |-> Right (FileStatus 11 True 10000)+ , expect $ GetFileStatus "/a/b" |-> Right (FileStatus 11 True 100)+ , expect $ DoesPathExist "/a/b/one-phat" |-> Right False+ , expect $ RenameDirectory "/a/b/one" "/a/b/one-phat" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ CreateDirectory "/a/b/one" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ ListDirectory "/a/b/one-phat" |->+ Right ["Uno.mp3", "dos.mp3", "tres.mp3"]+ , expect $ GetFileStatus "/a/b/one-phat/Uno.mp3" |->+ Right (FileStatus 11 False 1000)+ , expect $ GetFileStatus "/a/b/one-phat/dos.mp3" |->+ Right (FileStatus 11 False 2000)+ , expect $ GetFileStatus "/a/b/one-phat/tres.mp3" |->+ Right (FileStatus 11 False 3000)+ , expect $ RenameFile "/a/b/one-phat/Uno.mp3" "/a/b/one/Uno.mp3" |->+ Right ()+ , expect $ Sync |-> ()+ , expect $ RenameFile "/a/b/one-phat/dos.mp3" "/a/b/one/dos.mp3" |->+ Right ()+ , expect $ Sync |-> ()+ , expect $ RenameFile "/a/b/one-phat/tres.mp3" "/a/b/one/tres.mp3" |->+ Right ()+ , expect $ Sync |-> ()+ , expect $ RemoveDirectory "/a/b/one-phat" |-> Right ()+ , expect $ Sync |-> ()+ ]+ assertSuccess <=< Error.run $ run defaultOptions++testCaseSensitiveScript :: TestTree+testCaseSensitiveScript = testCase "CaseSensitiveScript" . runMockT $ do+ inSequence+ [ expect $ MakeAbsolute "one" |-> Right "/a/b/one"+ , expect $ GetFileStatus "/a/b/one" |-> Right (FileStatus 11 True 10000)+ , expect $ GetFileStatus "/a/b" |-> Right (FileStatus 11 True 100)+ , expect $ DoesPathExist "/a/b/one-phat" |-> Right False+ , expect $ PutStrLn "mv /a/b/one /a/b/one-phat" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "mkdir /a/b/one" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ ListDirectory "/a/b/one" |->+ Right ["Uno.mp3", "dos.mp3", "tres.mp3"]+ , expect $ GetFileStatus "/a/b/one/Uno.mp3" |->+ Right (FileStatus 11 False 1000)+ , expect $ GetFileStatus "/a/b/one/dos.mp3" |->+ Right (FileStatus 11 False 2000)+ , expect $ GetFileStatus "/a/b/one/tres.mp3" |->+ Right (FileStatus 11 False 3000)+ , expect $ PutStrLn "mv /a/b/one-phat/Uno.mp3 /a/b/one/Uno.mp3" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "mv /a/b/one-phat/dos.mp3 /a/b/one/dos.mp3" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "mv /a/b/one-phat/tres.mp3 /a/b/one/tres.mp3" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "rmdir /a/b/one-phat" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ ]+ assertSuccess <=< Error.run $ run defaultOptions+ { optScript = True+ }++------------------------------------------------------------------------------++testCaseInsensitive :: TestTree+testCaseInsensitive = testCase "CaseInsensitive" . runMockT $ do+ inSequence+ [ expect $ MakeAbsolute "one" |-> Right "/a/b/one"+ , expect $ GetFileStatus "/a/b/one" |-> Right (FileStatus 11 True 10000)+ , expect $ GetFileStatus "/a/b" |-> Right (FileStatus 11 True 100)+ , expect $ DoesPathExist "/a/b/one-phat" |-> Right False+ , expect $ RenameDirectory "/a/b/one" "/a/b/one-phat" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ CreateDirectory "/a/b/one" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ ListDirectory "/a/b/one-phat" |->+ Right ["Uno.mp3", "dos.mp3", "tres.mp3"]+ , expect $ GetFileStatus "/a/b/one-phat/Uno.mp3" |->+ Right (FileStatus 11 False 1000)+ , expect $ GetFileStatus "/a/b/one-phat/dos.mp3" |->+ Right (FileStatus 11 False 2000)+ , expect $ GetFileStatus "/a/b/one-phat/tres.mp3" |->+ Right (FileStatus 11 False 3000)+ , expect $ RenameFile "/a/b/one-phat/dos.mp3" "/a/b/one/dos.mp3" |->+ Right ()+ , expect $ Sync |-> ()+ , expect $ RenameFile "/a/b/one-phat/tres.mp3" "/a/b/one/tres.mp3" |->+ Right ()+ , expect $ Sync |-> ()+ , expect $ RenameFile "/a/b/one-phat/Uno.mp3" "/a/b/one/Uno.mp3" |->+ Right ()+ , expect $ Sync |-> ()+ , expect $ RemoveDirectory "/a/b/one-phat" |-> Right ()+ , expect $ Sync |-> ()+ ]+ assertSuccess <=< Error.run $ run defaultOptions+ { optCase = CaseInsensitive+ }++testCaseInsensitiveScript :: TestTree+testCaseInsensitiveScript = testCase "CaseInsensitiveScript" . runMockT $ do+ inSequence+ [ expect $ MakeAbsolute "one" |-> Right "/a/b/one"+ , expect $ GetFileStatus "/a/b/one" |-> Right (FileStatus 11 True 10000)+ , expect $ GetFileStatus "/a/b" |-> Right (FileStatus 11 True 100)+ , expect $ DoesPathExist "/a/b/one-phat" |-> Right False+ , expect $ PutStrLn "mv /a/b/one /a/b/one-phat" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "mkdir /a/b/one" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ ListDirectory "/a/b/one" |->+ Right ["Uno.mp3", "dos.mp3", "tres.mp3"]+ , expect $ GetFileStatus "/a/b/one/Uno.mp3" |->+ Right (FileStatus 11 False 1000)+ , expect $ GetFileStatus "/a/b/one/dos.mp3" |->+ Right (FileStatus 11 False 2000)+ , expect $ GetFileStatus "/a/b/one/tres.mp3" |->+ Right (FileStatus 11 False 3000)+ , expect $ PutStrLn "mv /a/b/one-phat/dos.mp3 /a/b/one/dos.mp3" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "mv /a/b/one-phat/tres.mp3 /a/b/one/tres.mp3" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "mv /a/b/one-phat/Uno.mp3 /a/b/one/Uno.mp3" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "rmdir /a/b/one-phat" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ ]+ assertSuccess <=< Error.run $ run defaultOptions+ { optCase = CaseInsensitive+ , optScript = True+ }++------------------------------------------------------------------------------++testFirstNone :: TestTree+testFirstNone = testCase "FirstNone" . runMockT $ do+ inSequence+ [ expect $ MakeAbsolute "one" |-> Right "/a/b/one"+ , expect $ GetFileStatus "/a/b/one" |-> Right (FileStatus 11 True 10000)+ , expect $ GetFileStatus "/a/b" |-> Right (FileStatus 11 True 100)+ , expect $ DoesPathExist "/a/b/one-phat" |-> Right False+ , expect $ RenameDirectory "/a/b/one" "/a/b/one-phat" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ CreateDirectory "/a/b/one" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ ListDirectory "/a/b/one-phat" |->+ Right ["Uno.mp3", "three", "dos.mp3", "two", "tres.mp3"]+ , expect $ GetFileStatus "/a/b/one-phat/Uno.mp3" |->+ Right (FileStatus 11 False 1000)+ , expect $ GetFileStatus "/a/b/one-phat/three" |->+ Right (FileStatus 11 True 3000)+ , expect $ GetFileStatus "/a/b/one-phat/dos.mp3" |->+ Right (FileStatus 11 False 2000)+ , expect $ GetFileStatus "/a/b/one-phat/two" |->+ Right (FileStatus 11 True 2000)+ , expect $ GetFileStatus "/a/b/one-phat/tres.mp3" |->+ Right (FileStatus 11 False 3000)+ , expect $ RenameFile "/a/b/one-phat/Uno.mp3" "/a/b/one/Uno.mp3" |->+ Right ()+ , expect $ Sync |-> ()+ , expect $ RenameFile "/a/b/one-phat/dos.mp3" "/a/b/one/dos.mp3" |->+ Right ()+ , expect $ Sync |-> ()+ , expect $ CreateDirectory "/a/b/one/three" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ ListDirectory "/a/b/one-phat/three" |-> Right []+ , expect $ RemoveDirectory "/a/b/one-phat/three" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ RenameFile "/a/b/one-phat/tres.mp3" "/a/b/one/tres.mp3" |->+ Right ()+ , expect $ Sync |-> ()+ , expect $ CreateDirectory "/a/b/one/two" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ ListDirectory "/a/b/one-phat/two" |-> Right []+ , expect $ RemoveDirectory "/a/b/one-phat/two" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ RemoveDirectory "/a/b/one-phat" |-> Right ()+ , expect $ Sync |-> ()+ ]+ assertSuccess <=< Error.run $ run defaultOptions++testFirstNoneScript :: TestTree+testFirstNoneScript = testCase "FirstNoneScript" . runMockT $ do+ inSequence+ [ expect $ MakeAbsolute "one" |-> Right "/a/b/one"+ , expect $ GetFileStatus "/a/b/one" |-> Right (FileStatus 11 True 10000)+ , expect $ GetFileStatus "/a/b" |-> Right (FileStatus 11 True 100)+ , expect $ DoesPathExist "/a/b/one-phat" |-> Right False+ , expect $ PutStrLn "mv /a/b/one /a/b/one-phat" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "mkdir /a/b/one" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ ListDirectory "/a/b/one" |->+ Right ["Uno.mp3", "three", "dos.mp3", "two", "tres.mp3"]+ , expect $ GetFileStatus "/a/b/one/Uno.mp3" |->+ Right (FileStatus 11 False 1000)+ , expect $ GetFileStatus "/a/b/one/three" |->+ Right (FileStatus 11 True 3000)+ , expect $ GetFileStatus "/a/b/one/dos.mp3" |->+ Right (FileStatus 11 False 2000)+ , expect $ GetFileStatus "/a/b/one/two" |->+ Right (FileStatus 11 True 2000)+ , expect $ GetFileStatus "/a/b/one/tres.mp3" |->+ Right (FileStatus 11 False 3000)+ , expect $ PutStrLn "mv /a/b/one-phat/Uno.mp3 /a/b/one/Uno.mp3" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "mv /a/b/one-phat/dos.mp3 /a/b/one/dos.mp3" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "mkdir /a/b/one/three" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ ListDirectory "/a/b/one/three" |-> Right []+ , expect $ PutStrLn "rmdir /a/b/one-phat/three" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "mv /a/b/one-phat/tres.mp3 /a/b/one/tres.mp3" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "mkdir /a/b/one/two" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ ListDirectory "/a/b/one/two" |-> Right []+ , expect $ PutStrLn "rmdir /a/b/one-phat/two" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "rmdir /a/b/one-phat" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ ]+ assertSuccess <=< Error.run $ run defaultOptions+ { optScript = True+ }++------------------------------------------------------------------------------++testFirstDirs :: TestTree+testFirstDirs = testCase "FirstDirs" . runMockT$ do+ inSequence+ [ expect $ MakeAbsolute "one" |-> Right "/a/b/one"+ , expect $ GetFileStatus "/a/b/one" |-> Right (FileStatus 11 True 10000)+ , expect $ GetFileStatus "/a/b" |-> Right (FileStatus 11 True 100)+ , expect $ DoesPathExist "/a/b/one-phat" |-> Right False+ , expect $ RenameDirectory "/a/b/one" "/a/b/one-phat" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ CreateDirectory "/a/b/one" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ ListDirectory "/a/b/one-phat" |->+ Right ["Uno.mp3", "three", "dos.mp3", "two", "tres.mp3"]+ , expect $ GetFileStatus "/a/b/one-phat/Uno.mp3" |->+ Right (FileStatus 11 False 1000)+ , expect $ GetFileStatus "/a/b/one-phat/three" |->+ Right (FileStatus 11 True 3000)+ , expect $ GetFileStatus "/a/b/one-phat/dos.mp3" |->+ Right (FileStatus 11 False 2000)+ , expect $ GetFileStatus "/a/b/one-phat/two" |->+ Right (FileStatus 11 True 2000)+ , expect $ GetFileStatus "/a/b/one-phat/tres.mp3" |->+ Right (FileStatus 11 False 3000)+ , expect $ CreateDirectory "/a/b/one/three" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ ListDirectory "/a/b/one-phat/three" |-> Right []+ , expect $ RemoveDirectory "/a/b/one-phat/three" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ CreateDirectory "/a/b/one/two" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ ListDirectory "/a/b/one-phat/two" |-> Right []+ , expect $ RemoveDirectory "/a/b/one-phat/two" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ RenameFile "/a/b/one-phat/Uno.mp3" "/a/b/one/Uno.mp3" |->+ Right ()+ , expect $ Sync |-> ()+ , expect $ RenameFile "/a/b/one-phat/dos.mp3" "/a/b/one/dos.mp3" |->+ Right ()+ , expect $ Sync |-> ()+ , expect $ RenameFile "/a/b/one-phat/tres.mp3" "/a/b/one/tres.mp3" |->+ Right ()+ , expect $ Sync |-> ()+ , expect $ RemoveDirectory "/a/b/one-phat" |-> Right ()+ , expect $ Sync |-> ()+ ]+ assertSuccess <=< Error.run $ run defaultOptions+ { optFirst = FirstDirs+ }++testFirstDirsScript :: TestTree+testFirstDirsScript = testCase "FirstDirsScript" . runMockT $ do+ inSequence+ [ expect $ MakeAbsolute "one" |-> Right "/a/b/one"+ , expect $ GetFileStatus "/a/b/one" |-> Right (FileStatus 11 True 10000)+ , expect $ GetFileStatus "/a/b" |-> Right (FileStatus 11 True 100)+ , expect $ DoesPathExist "/a/b/one-phat" |-> Right False+ , expect $ PutStrLn "mv /a/b/one /a/b/one-phat" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "mkdir /a/b/one" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ ListDirectory "/a/b/one" |->+ Right ["Uno.mp3", "three", "dos.mp3", "two", "tres.mp3"]+ , expect $ GetFileStatus "/a/b/one/Uno.mp3" |->+ Right (FileStatus 11 False 1000)+ , expect $ GetFileStatus "/a/b/one/three" |->+ Right (FileStatus 11 True 3000)+ , expect $ GetFileStatus "/a/b/one/dos.mp3" |->+ Right (FileStatus 11 False 2000)+ , expect $ GetFileStatus "/a/b/one/two" |->+ Right (FileStatus 11 True 2000)+ , expect $ GetFileStatus "/a/b/one/tres.mp3" |->+ Right (FileStatus 11 False 3000)+ , expect $ PutStrLn "mkdir /a/b/one/three" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ ListDirectory "/a/b/one/three" |-> Right []+ , expect $ PutStrLn "rmdir /a/b/one-phat/three" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "mkdir /a/b/one/two" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ ListDirectory "/a/b/one/two" |-> Right []+ , expect $ PutStrLn "rmdir /a/b/one-phat/two" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "mv /a/b/one-phat/Uno.mp3 /a/b/one/Uno.mp3" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "mv /a/b/one-phat/dos.mp3 /a/b/one/dos.mp3" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "mv /a/b/one-phat/tres.mp3 /a/b/one/tres.mp3" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "rmdir /a/b/one-phat" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ ]+ assertSuccess <=< Error.run $ run defaultOptions+ { optFirst = FirstDirs+ , optScript = True+ }++------------------------------------------------------------------------------++testFirstFiles :: TestTree+testFirstFiles = testCase "FirstFiles" . runMockT $ do+ inSequence+ [ expect $ MakeAbsolute "one" |-> Right "/a/b/one"+ , expect $ GetFileStatus "/a/b/one" |-> Right (FileStatus 11 True 10000)+ , expect $ GetFileStatus "/a/b" |-> Right (FileStatus 11 True 100)+ , expect $ DoesPathExist "/a/b/one-phat" |-> Right False+ , expect $ RenameDirectory "/a/b/one" "/a/b/one-phat" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ CreateDirectory "/a/b/one" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ ListDirectory "/a/b/one-phat" |->+ Right ["Uno.mp3", "three", "dos.mp3", "two", "tres.mp3"]+ , expect $ GetFileStatus "/a/b/one-phat/Uno.mp3" |->+ Right (FileStatus 11 False 1000)+ , expect $ GetFileStatus "/a/b/one-phat/three" |->+ Right (FileStatus 11 True 3000)+ , expect $ GetFileStatus "/a/b/one-phat/dos.mp3" |->+ Right (FileStatus 11 False 2000)+ , expect $ GetFileStatus "/a/b/one-phat/two" |->+ Right (FileStatus 11 True 2000)+ , expect $ GetFileStatus "/a/b/one-phat/tres.mp3" |->+ Right (FileStatus 11 False 3000)+ , expect $ RenameFile "/a/b/one-phat/Uno.mp3" "/a/b/one/Uno.mp3" |->+ Right ()+ , expect $ Sync |-> ()+ , expect $ RenameFile "/a/b/one-phat/dos.mp3" "/a/b/one/dos.mp3" |->+ Right ()+ , expect $ Sync |-> ()+ , expect $ RenameFile "/a/b/one-phat/tres.mp3" "/a/b/one/tres.mp3" |->+ Right ()+ , expect $ Sync |-> ()+ , expect $ CreateDirectory "/a/b/one/three" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ ListDirectory "/a/b/one-phat/three" |-> Right []+ , expect $ RemoveDirectory "/a/b/one-phat/three" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ CreateDirectory "/a/b/one/two" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ ListDirectory "/a/b/one-phat/two" |-> Right []+ , expect $ RemoveDirectory "/a/b/one-phat/two" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ RemoveDirectory "/a/b/one-phat" |-> Right ()+ , expect $ Sync |-> ()+ ]+ assertSuccess <=< Error.run $ run defaultOptions+ { optFirst = FirstFiles+ }++testFirstFilesScript :: TestTree+testFirstFilesScript = testCase "FirstFilesScript" . runMockT $ do+ inSequence+ [ expect $ MakeAbsolute "one" |-> Right "/a/b/one"+ , expect $ GetFileStatus "/a/b/one" |-> Right (FileStatus 11 True 10000)+ , expect $ GetFileStatus "/a/b" |-> Right (FileStatus 11 True 100)+ , expect $ DoesPathExist "/a/b/one-phat" |-> Right False+ , expect $ PutStrLn "mv /a/b/one /a/b/one-phat" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "mkdir /a/b/one" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ ListDirectory "/a/b/one" |->+ Right ["Uno.mp3", "three", "dos.mp3", "two", "tres.mp3"]+ , expect $ GetFileStatus "/a/b/one/Uno.mp3" |->+ Right (FileStatus 11 False 1000)+ , expect $ GetFileStatus "/a/b/one/three" |->+ Right (FileStatus 11 True 3000)+ , expect $ GetFileStatus "/a/b/one/dos.mp3" |->+ Right (FileStatus 11 False 2000)+ , expect $ GetFileStatus "/a/b/one/two" |->+ Right (FileStatus 11 True 2000)+ , expect $ GetFileStatus "/a/b/one/tres.mp3" |->+ Right (FileStatus 11 False 3000)+ , expect $ PutStrLn "mv /a/b/one-phat/Uno.mp3 /a/b/one/Uno.mp3" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "mv /a/b/one-phat/dos.mp3 /a/b/one/dos.mp3" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "mv /a/b/one-phat/tres.mp3 /a/b/one/tres.mp3" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "mkdir /a/b/one/three" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ ListDirectory "/a/b/one/three" |-> Right []+ , expect $ PutStrLn "rmdir /a/b/one-phat/three" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "mkdir /a/b/one/two" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ ListDirectory "/a/b/one/two" |-> Right []+ , expect $ PutStrLn "rmdir /a/b/one-phat/two" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "rmdir /a/b/one-phat" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ ]+ assertSuccess <=< Error.run $ run defaultOptions+ { optFirst = FirstFiles+ , optScript = True+ }++------------------------------------------------------------------------------++testNoSync :: TestTree+testNoSync = testCase "NoSync" . runMockT $ do+ inSequence+ [ expect $ MakeAbsolute "one" |-> Right "/a/b/one"+ , expect $ GetFileStatus "/a/b/one" |-> Right (FileStatus 11 True 10000)+ , expect $ GetFileStatus "/a/b" |-> Right (FileStatus 11 True 100)+ , expect $ DoesPathExist "/a/b/one-phat" |-> Right False+ , expect $ RenameDirectory "/a/b/one" "/a/b/one-phat" |-> Right ()+ , expect $ CreateDirectory "/a/b/one" |-> Right ()+ , expect $ ListDirectory "/a/b/one-phat" |->+ Right ["Uno.mp3", "dos.mp3", "tres.mp3"]+ , expect $ GetFileStatus "/a/b/one-phat/Uno.mp3" |->+ Right (FileStatus 11 False 1000)+ , expect $ GetFileStatus "/a/b/one-phat/dos.mp3" |->+ Right (FileStatus 11 False 2000)+ , expect $ GetFileStatus "/a/b/one-phat/tres.mp3" |->+ Right (FileStatus 11 False 3000)+ , expect $ RenameFile "/a/b/one-phat/Uno.mp3" "/a/b/one/Uno.mp3" |->+ Right ()+ , expect $ RenameFile "/a/b/one-phat/dos.mp3" "/a/b/one/dos.mp3" |->+ Right ()+ , expect $ RenameFile "/a/b/one-phat/tres.mp3" "/a/b/one/tres.mp3" |->+ Right ()+ , expect $ RemoveDirectory "/a/b/one-phat" |-> Right ()+ ]+ assertSuccess <=< Error.run $ run defaultOptions+ { optSync = False+ }++testNoSyncScript :: TestTree+testNoSyncScript = testCase "NoSyncScript" . runMockT $ do+ inSequence+ [ expect $ MakeAbsolute "one" |-> Right "/a/b/one"+ , expect $ GetFileStatus "/a/b/one" |-> Right (FileStatus 11 True 10000)+ , expect $ GetFileStatus "/a/b" |-> Right (FileStatus 11 True 100)+ , expect $ DoesPathExist "/a/b/one-phat" |-> Right False+ , expect $ PutStrLn "mv /a/b/one /a/b/one-phat" |-> ()+ , expect $ PutStrLn "mkdir /a/b/one" |-> ()+ , expect $ ListDirectory "/a/b/one" |->+ Right ["Uno.mp3", "dos.mp3", "tres.mp3"]+ , expect $ GetFileStatus "/a/b/one/Uno.mp3" |->+ Right (FileStatus 11 False 1000)+ , expect $ GetFileStatus "/a/b/one/dos.mp3" |->+ Right (FileStatus 11 False 2000)+ , expect $ GetFileStatus "/a/b/one/tres.mp3" |->+ Right (FileStatus 11 False 3000)+ , expect $ PutStrLn "mv /a/b/one-phat/Uno.mp3 /a/b/one/Uno.mp3" |-> ()+ , expect $ PutStrLn "mv /a/b/one-phat/dos.mp3 /a/b/one/dos.mp3" |-> ()+ , expect $ PutStrLn "mv /a/b/one-phat/tres.mp3 /a/b/one/tres.mp3" |-> ()+ , expect $ PutStrLn "rmdir /a/b/one-phat" |-> ()+ ]+ assertSuccess <=< Error.run $ run defaultOptions+ { optSync = False+ , optScript = True+ }++------------------------------------------------------------------------------++testOrderNameReverse :: TestTree+testOrderNameReverse = testCase "OrderNameReverse" . runMockT $ do+ inSequence+ [ expect $ MakeAbsolute "one" |-> Right "/a/b/one"+ , expect $ GetFileStatus "/a/b/one" |-> Right (FileStatus 11 True 10000)+ , expect $ GetFileStatus "/a/b" |-> Right (FileStatus 11 True 100)+ , expect $ DoesPathExist "/a/b/one-phat" |-> Right False+ , expect $ RenameDirectory "/a/b/one" "/a/b/one-phat" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ CreateDirectory "/a/b/one" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ ListDirectory "/a/b/one-phat" |->+ Right ["Uno.mp3", "three", "dos.mp3", "two", "tres.mp3"]+ , expect $ GetFileStatus "/a/b/one-phat/Uno.mp3" |->+ Right (FileStatus 11 False 1000)+ , expect $ GetFileStatus "/a/b/one-phat/three" |->+ Right (FileStatus 11 True 3000)+ , expect $ GetFileStatus "/a/b/one-phat/dos.mp3" |->+ Right (FileStatus 11 False 2000)+ , expect $ GetFileStatus "/a/b/one-phat/two" |->+ Right (FileStatus 11 True 2000)+ , expect $ GetFileStatus "/a/b/one-phat/tres.mp3" |->+ Right (FileStatus 11 False 3000)+ , expect $ CreateDirectory "/a/b/one/two" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ ListDirectory "/a/b/one-phat/two" |-> Right []+ , expect $ RemoveDirectory "/a/b/one-phat/two" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ CreateDirectory "/a/b/one/three" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ ListDirectory "/a/b/one-phat/three" |-> Right []+ , expect $ RemoveDirectory "/a/b/one-phat/three" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ RenameFile "/a/b/one-phat/tres.mp3" "/a/b/one/tres.mp3" |->+ Right ()+ , expect $ Sync |-> ()+ , expect $ RenameFile "/a/b/one-phat/dos.mp3" "/a/b/one/dos.mp3" |->+ Right ()+ , expect $ Sync |-> ()+ , expect $ RenameFile "/a/b/one-phat/Uno.mp3" "/a/b/one/Uno.mp3" |->+ Right ()+ , expect $ Sync |-> ()+ , expect $ RemoveDirectory "/a/b/one-phat" |-> Right ()+ , expect $ Sync |-> ()+ ]+ assertSuccess <=< Error.run $ run defaultOptions+ { optFirst = FirstDirs+ , optReverse = True+ }++testOrderNameReverseScript :: TestTree+testOrderNameReverseScript = testCase "OrderNameReverseScript" . runMockT $ do+ inSequence+ [ expect $ MakeAbsolute "one" |-> Right "/a/b/one"+ , expect $ GetFileStatus "/a/b/one" |-> Right (FileStatus 11 True 10000)+ , expect $ GetFileStatus "/a/b" |-> Right (FileStatus 11 True 100)+ , expect $ DoesPathExist "/a/b/one-phat" |-> Right False+ , expect $ PutStrLn "mv /a/b/one /a/b/one-phat" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "mkdir /a/b/one" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ ListDirectory "/a/b/one" |->+ Right ["Uno.mp3", "three", "dos.mp3", "two", "tres.mp3"]+ , expect $ GetFileStatus "/a/b/one/Uno.mp3" |->+ Right (FileStatus 11 False 1000)+ , expect $ GetFileStatus "/a/b/one/three" |->+ Right (FileStatus 11 True 3000)+ , expect $ GetFileStatus "/a/b/one/dos.mp3" |->+ Right (FileStatus 11 False 2000)+ , expect $ GetFileStatus "/a/b/one/two" |->+ Right (FileStatus 11 True 2000)+ , expect $ GetFileStatus "/a/b/one/tres.mp3" |->+ Right (FileStatus 11 False 3000)+ , expect $ PutStrLn "mkdir /a/b/one/two" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ ListDirectory "/a/b/one/two" |-> Right []+ , expect $ PutStrLn "rmdir /a/b/one-phat/two" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "mkdir /a/b/one/three" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ ListDirectory "/a/b/one/three" |-> Right []+ , expect $ PutStrLn "rmdir /a/b/one-phat/three" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "mv /a/b/one-phat/tres.mp3 /a/b/one/tres.mp3" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "mv /a/b/one-phat/dos.mp3 /a/b/one/dos.mp3" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "mv /a/b/one-phat/Uno.mp3 /a/b/one/Uno.mp3" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "rmdir /a/b/one-phat" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ ]+ assertSuccess <=< Error.run $ run defaultOptions+ { optFirst = FirstDirs+ , optReverse = True+ , optScript = True+ }++------------------------------------------------------------------------------++testOrderTime :: TestTree+testOrderTime = testCase "OrderTime" . runMockT $ do+ inSequence+ [ expect $ MakeAbsolute "one" |-> Right "/a/b/one"+ , expect $ GetFileStatus "/a/b/one" |-> Right (FileStatus 11 True 10000)+ , expect $ GetFileStatus "/a/b" |-> Right (FileStatus 11 True 100)+ , expect $ DoesPathExist "/a/b/one-phat" |-> Right False+ , expect $ RenameDirectory "/a/b/one" "/a/b/one-phat" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ CreateDirectory "/a/b/one" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ ListDirectory "/a/b/one-phat" |->+ Right ["Uno.mp3", "three", "dos.mp3", "two", "tres.mp3"]+ , expect $ GetFileStatus "/a/b/one-phat/Uno.mp3" |->+ Right (FileStatus 11 False 10000)+ , expect $ GetFileStatus "/a/b/one-phat/three" |->+ Right (FileStatus 11 True 3000)+ , expect $ GetFileStatus "/a/b/one-phat/dos.mp3" |->+ Right (FileStatus 11 False 20000)+ , expect $ GetFileStatus "/a/b/one-phat/two" |->+ Right (FileStatus 11 True 2000)+ , expect $ GetFileStatus "/a/b/one-phat/tres.mp3" |->+ Right (FileStatus 11 False 3000)+ , expect $ CreateDirectory "/a/b/one/two" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ ListDirectory "/a/b/one-phat/two" |-> Right []+ , expect $ RemoveDirectory "/a/b/one-phat/two" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ CreateDirectory "/a/b/one/three" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ ListDirectory "/a/b/one-phat/three" |-> Right []+ , expect $ RemoveDirectory "/a/b/one-phat/three" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ RenameFile "/a/b/one-phat/tres.mp3" "/a/b/one/tres.mp3" |->+ Right ()+ , expect $ Sync |-> ()+ , expect $ RenameFile "/a/b/one-phat/Uno.mp3" "/a/b/one/Uno.mp3" |->+ Right ()+ , expect $ Sync |-> ()+ , expect $ RenameFile "/a/b/one-phat/dos.mp3" "/a/b/one/dos.mp3" |->+ Right ()+ , expect $ Sync |-> ()+ , expect $ RemoveDirectory "/a/b/one-phat" |-> Right ()+ , expect $ Sync |-> ()+ ]+ assertSuccess <=< Error.run $ run defaultOptions+ { optFirst = FirstDirs+ , optOrder = OrderTime+ }++testOrderTimeScript :: TestTree+testOrderTimeScript = testCase "OrderTimeScript" . runMockT $ do+ inSequence+ [ expect $ MakeAbsolute "one" |-> Right "/a/b/one"+ , expect $ GetFileStatus "/a/b/one" |-> Right (FileStatus 11 True 10000)+ , expect $ GetFileStatus "/a/b" |-> Right (FileStatus 11 True 100)+ , expect $ DoesPathExist "/a/b/one-phat" |-> Right False+ , expect $ PutStrLn "mv /a/b/one /a/b/one-phat" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "mkdir /a/b/one" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ ListDirectory "/a/b/one" |->+ Right ["Uno.mp3", "three", "dos.mp3", "two", "tres.mp3"]+ , expect $ GetFileStatus "/a/b/one/Uno.mp3" |->+ Right (FileStatus 11 False 10000)+ , expect $ GetFileStatus "/a/b/one/three" |->+ Right (FileStatus 11 True 3000)+ , expect $ GetFileStatus "/a/b/one/dos.mp3" |->+ Right (FileStatus 11 False 20000)+ , expect $ GetFileStatus "/a/b/one/two" |->+ Right (FileStatus 11 True 2000)+ , expect $ GetFileStatus "/a/b/one/tres.mp3" |->+ Right (FileStatus 11 False 3000)+ , expect $ PutStrLn "mkdir /a/b/one/two" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ ListDirectory "/a/b/one/two" |-> Right []+ , expect $ PutStrLn "rmdir /a/b/one-phat/two" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "mkdir /a/b/one/three" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ ListDirectory "/a/b/one/three" |-> Right []+ , expect $ PutStrLn "rmdir /a/b/one-phat/three" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "mv /a/b/one-phat/tres.mp3 /a/b/one/tres.mp3" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "mv /a/b/one-phat/Uno.mp3 /a/b/one/Uno.mp3" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "mv /a/b/one-phat/dos.mp3 /a/b/one/dos.mp3" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "rmdir /a/b/one-phat" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ ]+ assertSuccess <=< Error.run $ run defaultOptions+ { optFirst = FirstDirs+ , optOrder = OrderTime+ , optScript = True+ }++------------------------------------------------------------------------------++testOrderTimeReverse :: TestTree+testOrderTimeReverse = testCase "OrderTimeReverse" . runMockT $ do+ inSequence+ [ expect $ MakeAbsolute "one" |-> Right "/a/b/one"+ , expect $ GetFileStatus "/a/b/one" |-> Right (FileStatus 11 True 10000)+ , expect $ GetFileStatus "/a/b" |-> Right (FileStatus 11 True 100)+ , expect $ DoesPathExist "/a/b/one-phat" |-> Right False+ , expect $ RenameDirectory "/a/b/one" "/a/b/one-phat" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ CreateDirectory "/a/b/one" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ ListDirectory "/a/b/one-phat" |->+ Right ["Uno.mp3", "three", "dos.mp3", "two", "tres.mp3"]+ , expect $ GetFileStatus "/a/b/one-phat/Uno.mp3" |->+ Right (FileStatus 11 False 10000)+ , expect $ GetFileStatus "/a/b/one-phat/three" |->+ Right (FileStatus 11 True 3000)+ , expect $ GetFileStatus "/a/b/one-phat/dos.mp3" |->+ Right (FileStatus 11 False 20000)+ , expect $ GetFileStatus "/a/b/one-phat/two" |->+ Right (FileStatus 11 True 2000)+ , expect $ GetFileStatus "/a/b/one-phat/tres.mp3" |->+ Right (FileStatus 11 False 3000)+ , expect $ CreateDirectory "/a/b/one/three" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ ListDirectory "/a/b/one-phat/three" |-> Right []+ , expect $ RemoveDirectory "/a/b/one-phat/three" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ CreateDirectory "/a/b/one/two" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ ListDirectory "/a/b/one-phat/two" |-> Right []+ , expect $ RemoveDirectory "/a/b/one-phat/two" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ RenameFile "/a/b/one-phat/dos.mp3" "/a/b/one/dos.mp3" |->+ Right ()+ , expect $ Sync |-> ()+ , expect $ RenameFile "/a/b/one-phat/Uno.mp3" "/a/b/one/Uno.mp3" |->+ Right ()+ , expect $ Sync |-> ()+ , expect $ RenameFile "/a/b/one-phat/tres.mp3" "/a/b/one/tres.mp3" |->+ Right ()+ , expect $ Sync |-> ()+ , expect $ RemoveDirectory "/a/b/one-phat" |-> Right ()+ , expect $ Sync |-> ()+ ]+ assertSuccess <=< Error.run $ run defaultOptions+ { optFirst = FirstDirs+ , optOrder = OrderTime+ , optReverse = True+ }++testOrderTimeReverseScript :: TestTree+testOrderTimeReverseScript = testCase "OrderTimeReverseScript" . runMockT $ do+ inSequence+ [ expect $ MakeAbsolute "one" |-> Right "/a/b/one"+ , expect $ GetFileStatus "/a/b/one" |-> Right (FileStatus 11 True 10000)+ , expect $ GetFileStatus "/a/b" |-> Right (FileStatus 11 True 100)+ , expect $ DoesPathExist "/a/b/one-phat" |-> Right False+ , expect $ PutStrLn "mv /a/b/one /a/b/one-phat" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "mkdir /a/b/one" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ ListDirectory "/a/b/one" |->+ Right ["Uno.mp3", "three", "dos.mp3", "two", "tres.mp3"]+ , expect $ GetFileStatus "/a/b/one/Uno.mp3" |->+ Right (FileStatus 11 False 10000)+ , expect $ GetFileStatus "/a/b/one/three" |->+ Right (FileStatus 11 True 3000)+ , expect $ GetFileStatus "/a/b/one/dos.mp3" |->+ Right (FileStatus 11 False 20000)+ , expect $ GetFileStatus "/a/b/one/two" |->+ Right (FileStatus 11 True 2000)+ , expect $ GetFileStatus "/a/b/one/tres.mp3" |->+ Right (FileStatus 11 False 3000)+ , expect $ PutStrLn "mkdir /a/b/one/three" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ ListDirectory "/a/b/one/three" |-> Right []+ , expect $ PutStrLn "rmdir /a/b/one-phat/three" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "mkdir /a/b/one/two" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ ListDirectory "/a/b/one/two" |-> Right []+ , expect $ PutStrLn "rmdir /a/b/one-phat/two" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "mv /a/b/one-phat/dos.mp3 /a/b/one/dos.mp3" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "mv /a/b/one-phat/Uno.mp3 /a/b/one/Uno.mp3" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "mv /a/b/one-phat/tres.mp3 /a/b/one/tres.mp3" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "rmdir /a/b/one-phat" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ ]+ assertSuccess <=< Error.run $ run defaultOptions+ { optFirst = FirstDirs+ , optOrder = OrderTime+ , optReverse = True+ , optScript = True+ }++------------------------------------------------------------------------------++testOrderRandom :: TestTree+testOrderRandom = testCase "OrderRandom" . runMockT $ do+ inSequence+ [ expect $ MakeAbsolute "one" |-> Right "/a/b/one"+ , expect $ GetFileStatus "/a/b/one" |-> Right (FileStatus 11 True 10000)+ , expect $ GetFileStatus "/a/b" |-> Right (FileStatus 11 True 100)+ , expect $ DoesPathExist "/a/b/one-phat" |-> Right False+ , expect $ RenameDirectory "/a/b/one" "/a/b/one-phat" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ CreateDirectory "/a/b/one" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ ListDirectory "/a/b/one-phat" |->+ Right ["x.mp3", "y.mp3", "z.mp3", "d1", "d2", "d3"]+ , expect $ GetFileStatus "/a/b/one-phat/x.mp3" |->+ Right (FileStatus 11 False 1000)+ , expect $ GetFileStatus "/a/b/one-phat/y.mp3" |->+ Right (FileStatus 11 False 2000)+ , expect $ GetFileStatus "/a/b/one-phat/z.mp3" |->+ Right (FileStatus 11 False 3000)+ , expect $ GetFileStatus "/a/b/one-phat/d1" |->+ Right (FileStatus 11 True 1000)+ , expect $ GetFileStatus "/a/b/one-phat/d2" |->+ Right (FileStatus 11 True 2000)+ , expect $ GetFileStatus "/a/b/one-phat/d3" |->+ Right (FileStatus 11 True 3000)+ , inAnyOrder+ [ inSequence+ [ expect $ CreateDirectory "/a/b/one/d1" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ ListDirectory "/a/b/one-phat/d1" |-> Right []+ , expect $ RemoveDirectory "/a/b/one-phat/d1" |-> Right ()+ , expect $ Sync |-> ()+ ]+ , inSequence+ [ expect $ CreateDirectory "/a/b/one/d2" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ ListDirectory "/a/b/one-phat/d2" |-> Right []+ , expect $ RemoveDirectory "/a/b/one-phat/d2" |-> Right ()+ , expect $ Sync |-> ()+ ]+ , inSequence+ [ expect $ CreateDirectory "/a/b/one/d3" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ ListDirectory "/a/b/one-phat/d3" |-> Right []+ , expect $ RemoveDirectory "/a/b/one-phat/d3" |-> Right ()+ , expect $ Sync |-> ()+ ]+ ]+ , inAnyOrder+ [ inSequence+ [ expect $+ RenameFile "/a/b/one-phat/x.mp3" "/a/b/one/x.mp3" |->+ Right ()+ , expect $ Sync |-> ()+ ]+ , inSequence+ [ expect $+ RenameFile "/a/b/one-phat/y.mp3" "/a/b/one/y.mp3" |->+ Right ()+ , expect $ Sync |-> ()+ ]+ , inSequence+ [ expect $+ RenameFile "/a/b/one-phat/z.mp3" "/a/b/one/z.mp3" |->+ Right ()+ , expect $ Sync |-> ()+ ]+ ]+ , expect $ RemoveDirectory "/a/b/one-phat" |-> Right ()+ , expect $ Sync |-> ()+ ]+ assertSuccess <=< Error.run $ run defaultOptions+ { optFirst = FirstDirs+ , optOrder = OrderRandom+ }++testOrderRandomScript :: TestTree+testOrderRandomScript = testCase "OrderRandomScript" . runMockT $ do+ inSequence+ [ expect $ MakeAbsolute "one" |-> Right "/a/b/one"+ , expect $ GetFileStatus "/a/b/one" |-> Right (FileStatus 11 True 10000)+ , expect $ GetFileStatus "/a/b" |-> Right (FileStatus 11 True 100)+ , expect $ DoesPathExist "/a/b/one-phat" |-> Right False+ , expect $ PutStrLn "mv /a/b/one /a/b/one-phat" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "mkdir /a/b/one" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ ListDirectory "/a/b/one" |->+ Right ["x.mp3", "y.mp3", "z.mp3", "d1", "d2", "d3"]+ , expect $ GetFileStatus "/a/b/one/x.mp3" |->+ Right (FileStatus 11 False 1000)+ , expect $ GetFileStatus "/a/b/one/y.mp3" |->+ Right (FileStatus 11 False 2000)+ , expect $ GetFileStatus "/a/b/one/z.mp3" |->+ Right (FileStatus 11 False 3000)+ , expect $ GetFileStatus "/a/b/one/d1" |->+ Right (FileStatus 11 True 1000)+ , expect $ GetFileStatus "/a/b/one/d2" |->+ Right (FileStatus 11 True 2000)+ , expect $ GetFileStatus "/a/b/one/d3" |->+ Right (FileStatus 11 True 3000)+ , inAnyOrder+ [ inSequence+ [ expect $ PutStrLn "mkdir /a/b/one/d1" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ ListDirectory "/a/b/one/d1" |-> Right []+ , expect $ PutStrLn "rmdir /a/b/one-phat/d1" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ ]+ , inSequence+ [ expect $ PutStrLn "mkdir /a/b/one/d2" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ ListDirectory "/a/b/one/d2" |-> Right []+ , expect $ PutStrLn "rmdir /a/b/one-phat/d2" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ ]+ , inSequence+ [ expect $ PutStrLn "mkdir /a/b/one/d3" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ ListDirectory "/a/b/one/d3" |-> Right []+ , expect $ PutStrLn "rmdir /a/b/one-phat/d3" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ ]+ ]+ , inAnyOrder+ [ inSequence+ [ expect $ PutStrLn "mv /a/b/one-phat/x.mp3 /a/b/one/x.mp3" |->+ ()+ , expect $ PutStrLn "sync" |-> ()+ ]+ , inSequence+ [ expect $ PutStrLn "mv /a/b/one-phat/y.mp3 /a/b/one/y.mp3" |->+ ()+ , expect $ PutStrLn "sync" |-> ()+ ]+ , inSequence+ [ expect $ PutStrLn "mv /a/b/one-phat/z.mp3 /a/b/one/z.mp3" |->+ ()+ , expect $ PutStrLn "sync" |-> ()+ ]+ ]+ , expect $ PutStrLn "rmdir /a/b/one-phat" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ ]+ assertSuccess <=< Error.run $ run defaultOptions+ { optFirst = FirstDirs+ , optOrder = OrderRandom+ , optScript = True+ }++------------------------------------------------------------------------------++testVerbose :: TestTree+testVerbose = testCase "Verbose" . runMockT $ do+ inSequence+ [ expect $ MakeAbsolute "one" |-> Right "/a/b/one"+ , expect $ GetFileStatus "/a/b/one" |-> Right (FileStatus 11 True 10000)+ , expect $ GetFileStatus "/a/b" |-> Right (FileStatus 11 True 100)+ , expect $ DoesPathExist "/a/b/one-phat" |-> Right False+ , expect $ PutStrLn "one" |-> ()+ , expect $ RenameDirectory "/a/b/one" "/a/b/one-phat" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ CreateDirectory "/a/b/one" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ ListDirectory "/a/b/one-phat" |->+ Right ["Uno.mp3", "dos.mp3", "tres.mp3"]+ , expect $ GetFileStatus "/a/b/one-phat/Uno.mp3" |->+ Right (FileStatus 11 False 1000)+ , expect $ GetFileStatus "/a/b/one-phat/dos.mp3" |->+ Right (FileStatus 11 False 2000)+ , expect $ GetFileStatus "/a/b/one-phat/tres.mp3" |->+ Right (FileStatus 11 False 3000)+ , expect $ PutStrLn "one/Uno.mp3" |-> ()+ , expect $ RenameFile "/a/b/one-phat/Uno.mp3" "/a/b/one/Uno.mp3" |->+ Right ()+ , expect $ Sync |-> ()+ , expect $ PutStrLn "one/dos.mp3" |-> ()+ , expect $ RenameFile "/a/b/one-phat/dos.mp3" "/a/b/one/dos.mp3" |->+ Right ()+ , expect $ Sync |-> ()+ , expect $ PutStrLn "one/tres.mp3" |-> ()+ , expect $ RenameFile "/a/b/one-phat/tres.mp3" "/a/b/one/tres.mp3" |->+ Right ()+ , expect $ Sync |-> ()+ , expect $ RemoveDirectory "/a/b/one-phat" |-> Right ()+ , expect $ Sync |-> ()+ ]+ assertSuccess <=< Error.run $ run defaultOptions+ { optVerbose = True+ }++testVerboseScript :: TestTree+testVerboseScript = testCase "VerboseScript" . runMockT $ do+ inSequence+ [ expect $ MakeAbsolute "one" |-> Right "/a/b/one"+ , expect $ GetFileStatus "/a/b/one" |-> Right (FileStatus 11 True 10000)+ , expect $ GetFileStatus "/a/b" |-> Right (FileStatus 11 True 100)+ , expect $ DoesPathExist "/a/b/one-phat" |-> Right False+ , expect $ PutStrLn "echo one" |-> ()+ , expect $ PutStrLn "mv /a/b/one /a/b/one-phat" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "mkdir /a/b/one" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ ListDirectory "/a/b/one" |->+ Right ["Uno.mp3", "dos.mp3", "tres.mp3"]+ , expect $ GetFileStatus "/a/b/one/Uno.mp3" |->+ Right (FileStatus 11 False 1000)+ , expect $ GetFileStatus "/a/b/one/dos.mp3" |->+ Right (FileStatus 11 False 2000)+ , expect $ GetFileStatus "/a/b/one/tres.mp3" |->+ Right (FileStatus 11 False 3000)+ , expect $ PutStrLn "echo one/Uno.mp3" |-> ()+ , expect $ PutStrLn "mv /a/b/one-phat/Uno.mp3 /a/b/one/Uno.mp3" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "echo one/dos.mp3" |-> ()+ , expect $ PutStrLn "mv /a/b/one-phat/dos.mp3 /a/b/one/dos.mp3" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "echo one/tres.mp3" |-> ()+ , expect $ PutStrLn "mv /a/b/one-phat/tres.mp3 /a/b/one/tres.mp3" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "rmdir /a/b/one-phat" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ ]+ assertSuccess <=< Error.run $ run defaultOptions+ { optScript = True+ , optVerbose = True+ }++------------------------------------------------------------------------------++testMultipleTargets :: TestTree+testMultipleTargets = testCase "MultipleTargets" . runMockT $ do+ inSequence+ [ expect $ MakeAbsolute "one" |-> Right "/a/b/one"+ , expect $ GetFileStatus "/a/b/one" |-> Right (FileStatus 11 True 10000)+ , expect $ GetFileStatus "/a/b" |-> Right (FileStatus 11 True 100)+ , expect $ DoesPathExist "/a/b/one-phat" |-> Right False+ , expect $ MakeAbsolute "two" |-> Right "/a/b/two"+ , expect $ GetFileStatus "/a/b/two" |-> Right (FileStatus 11 True 10000)+ , expect $ GetFileStatus "/a/b" |-> Right (FileStatus 11 True 100)+ , expect $ DoesPathExist "/a/b/two-phat" |-> Right False+ , expect $ RenameDirectory "/a/b/one" "/a/b/one-phat" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ CreateDirectory "/a/b/one" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ ListDirectory "/a/b/one-phat" |->+ Right ["Uno.mp3", "dos.mp3", "tres.mp3"]+ , expect $ GetFileStatus "/a/b/one-phat/Uno.mp3" |->+ Right (FileStatus 11 False 1000)+ , expect $ GetFileStatus "/a/b/one-phat/dos.mp3" |->+ Right (FileStatus 11 False 2000)+ , expect $ GetFileStatus "/a/b/one-phat/tres.mp3" |->+ Right (FileStatus 11 False 3000)+ , expect $ RenameFile "/a/b/one-phat/Uno.mp3" "/a/b/one/Uno.mp3" |->+ Right ()+ , expect $ Sync |-> ()+ , expect $ RenameFile "/a/b/one-phat/dos.mp3" "/a/b/one/dos.mp3" |->+ Right ()+ , expect $ Sync |-> ()+ , expect $ RenameFile "/a/b/one-phat/tres.mp3" "/a/b/one/tres.mp3" |->+ Right ()+ , expect $ Sync |-> ()+ , expect $ RemoveDirectory "/a/b/one-phat" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ RenameDirectory "/a/b/two" "/a/b/two-phat" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ CreateDirectory "/a/b/two" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ ListDirectory "/a/b/two-phat" |->+ Right ["san.mp3", "ni.mp3", "ichi.mp3"]+ , expect $ GetFileStatus "/a/b/two-phat/san.mp3" |->+ Right (FileStatus 11 False 3000)+ , expect $ GetFileStatus "/a/b/two-phat/ni.mp3" |->+ Right (FileStatus 11 False 2000)+ , expect $ GetFileStatus "/a/b/two-phat/ichi.mp3" |->+ Right (FileStatus 11 False 1000)+ , expect $ RenameFile "/a/b/two-phat/ichi.mp3" "/a/b/two/ichi.mp3" |->+ Right ()+ , expect $ Sync |-> ()+ , expect $ RenameFile "/a/b/two-phat/ni.mp3" "/a/b/two/ni.mp3" |->+ Right ()+ , expect $ Sync |-> ()+ , expect $ RenameFile "/a/b/two-phat/san.mp3" "/a/b/two/san.mp3" |->+ Right ()+ , expect $ Sync |-> ()+ , expect $ RemoveDirectory "/a/b/two-phat" |-> Right ()+ , expect $ Sync |-> ()+ ]+ assertSuccess <=< Error.run $ run defaultOptions+ { optTargets = NonEmpty.fromList ["one", "two"]+ }++testMultipleTargetsScript :: TestTree+testMultipleTargetsScript = testCase "MultipleTargetsScript" . runMockT $ do+ inSequence+ [ expect $ MakeAbsolute "one" |-> Right "/a/b/one"+ , expect $ GetFileStatus "/a/b/one" |-> Right (FileStatus 11 True 10000)+ , expect $ GetFileStatus "/a/b" |-> Right (FileStatus 11 True 100)+ , expect $ DoesPathExist "/a/b/one-phat" |-> Right False+ , expect $ MakeAbsolute "two" |-> Right "/a/b/two"+ , expect $ GetFileStatus "/a/b/two" |-> Right (FileStatus 11 True 10000)+ , expect $ GetFileStatus "/a/b" |-> Right (FileStatus 11 True 100)+ , expect $ DoesPathExist "/a/b/two-phat" |-> Right False+ , expect $ PutStrLn "mv /a/b/one /a/b/one-phat" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "mkdir /a/b/one" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ ListDirectory "/a/b/one" |->+ Right ["Uno.mp3", "dos.mp3", "tres.mp3"]+ , expect $ GetFileStatus "/a/b/one/Uno.mp3" |->+ Right (FileStatus 11 False 1000)+ , expect $ GetFileStatus "/a/b/one/dos.mp3" |->+ Right (FileStatus 11 False 2000)+ , expect $ GetFileStatus "/a/b/one/tres.mp3" |->+ Right (FileStatus 11 False 3000)+ , expect $ PutStrLn "mv /a/b/one-phat/Uno.mp3 /a/b/one/Uno.mp3" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "mv /a/b/one-phat/dos.mp3 /a/b/one/dos.mp3" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "mv /a/b/one-phat/tres.mp3 /a/b/one/tres.mp3" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "rmdir /a/b/one-phat" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "mv /a/b/two /a/b/two-phat" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "mkdir /a/b/two" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ ListDirectory "/a/b/two" |->+ Right ["san.mp3", "ni.mp3", "ichi.mp3"]+ , expect $ GetFileStatus "/a/b/two/san.mp3" |->+ Right (FileStatus 11 False 3000)+ , expect $ GetFileStatus "/a/b/two/ni.mp3" |->+ Right (FileStatus 11 False 2000)+ , expect $ GetFileStatus "/a/b/two/ichi.mp3" |->+ Right (FileStatus 11 False 1000)+ , expect $ PutStrLn "mv /a/b/two-phat/ichi.mp3 /a/b/two/ichi.mp3" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "mv /a/b/two-phat/ni.mp3 /a/b/two/ni.mp3" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "mv /a/b/two-phat/san.mp3 /a/b/two/san.mp3" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "rmdir /a/b/two-phat" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ ]+ assertSuccess <=< Error.run $ run defaultOptions+ { optScript = True+ , optTargets = NonEmpty.fromList ["one", "two"]+ }++------------------------------------------------------------------------------++testTargetNotFound :: TestTree+testTargetNotFound = testCase "TargetNotFound" . runMockT $ do+ inSequence+ [ expect $ MakeAbsolute "one" |-> Right "/a/b/one"+ , expect $ GetFileStatus "/a/b/one" |->+ Left (userError "file not found: one")+ ]+ assertError "user error (file not found: one)" <=< Error.run $+ run defaultOptions++------------------------------------------------------------------------------++testPhatTarget :: TestTree+testPhatTarget = testCase "PhatTarget" . runMockT $+ assertError "-phat directory: one-phat" <=< Error.run $+ run defaultOptions+ { optTargets = NonEmpty.fromList ["one-phat"]+ }++------------------------------------------------------------------------------++testTargetNotDirectory :: TestTree+testTargetNotDirectory = testCase "TargetNotDirectory" . runMockT $ do+ inSequence+ [ expect $ MakeAbsolute "one" |-> Right "/a/b/one"+ , expect $ GetFileStatus "/a/b/one" |->+ Right (FileStatus 11 False 10000)+ ]+ assertError "not a directory: one" <=< Error.run $ run defaultOptions++------------------------------------------------------------------------------++testTargetMountPoint :: TestTree+testTargetMountPoint = testCase "TargetMountPoint" . runMockT $ do+ inSequence+ [ expect $ MakeAbsolute "one" |-> Right "/a/one"+ , expect $ GetFileStatus "/a/one" |->+ Right (FileStatus 11 True 10000)+ , expect $ GetFileStatus "/a" |-> Right (FileStatus 10 True 100)+ ]+ assertError "mount point: one" <=< Error.run $ run defaultOptions++------------------------------------------------------------------------------++testTargetPhatExists :: TestTree+testTargetPhatExists = testCase "TargetPhatExists" . runMockT $ do+ inSequence+ [ expect $ MakeAbsolute "one" |-> Right "/a/b/one"+ , expect $ GetFileStatus "/a/b/one" |->+ Right (FileStatus 11 True 10000)+ , expect $ GetFileStatus "/a/b" |-> Right (FileStatus 11 True 100)+ , expect $ DoesPathExist "/a/b/one-phat" |-> Right True+ ]+ assertError "already exists: one-phat" <=< Error.run $ run defaultOptions++------------------------------------------------------------------------------++testLarge :: TestTree+testLarge = testCase "Large" . runMockT $ do+ inSequence+ [ expect $ MakeAbsolute "c" |-> Right "/a/b/c"+ , expect $ GetFileStatus "/a/b/c" |-> Right (FileStatus 11 True 1000)+ , expect $ GetFileStatus "/a/b" |-> Right (FileStatus 11 True 100)+ , expect $ DoesPathExist "/a/b/c-phat" |-> Right False+ , expect $ MakeAbsolute "d" |-> Right "/a/b/d"+ , expect $ GetFileStatus "/a/b/d" |-> Right (FileStatus 11 True 2000)+ , expect $ GetFileStatus "/a/b" |-> Right (FileStatus 11 True 100)+ , expect $ DoesPathExist "/a/b/d-phat" |-> Right False+ , expect $ RenameDirectory "/a/b/c" "/a/b/c-phat" |-> Right ()+ , expect $ CreateDirectory "/a/b/c" |-> Right ()+ , expect $ ListDirectory "/a/b/c-phat" |-> Right ["two", "one"]+ , expect $ GetFileStatus "/a/b/c-phat/two" |->+ Right (FileStatus 11 True 2000)+ , expect $ GetFileStatus "/a/b/c-phat/one" |->+ Right (FileStatus 11 True 1000)+ , expect $ CreateDirectory "/a/b/c/one" |-> Right ()+ , expect $ ListDirectory "/a/b/c-phat/one" |->+ Right ["11.mp3", "six", "13.mp3", "eight", "12.mp3", "14.mp3"]+ , expect $ GetFileStatus "/a/b/c-phat/one/11.mp3" |->+ Right (FileStatus 11 False 1100)+ , expect $ GetFileStatus "/a/b/c-phat/one/six" |->+ Right (FileStatus 11 True 6000)+ , expect $ GetFileStatus "/a/b/c-phat/one/13.mp3" |->+ Right (FileStatus 11 False 1300)+ , expect $ GetFileStatus "/a/b/c-phat/one/eight" |->+ Right (FileStatus 11 True 8000)+ , expect $ GetFileStatus "/a/b/c-phat/one/12.mp3" |->+ Right (FileStatus 11 False 1200)+ , expect $ GetFileStatus "/a/b/c-phat/one/14.mp3" |->+ Right (FileStatus 11 False 1400)+ , expect $ RenameFile "/a/b/c-phat/one/11.mp3" "/a/b/c/one/11.mp3" |->+ Right ()+ , expect $ RenameFile "/a/b/c-phat/one/12.mp3" "/a/b/c/one/12.mp3" |->+ Right ()+ , expect $ RenameFile "/a/b/c-phat/one/13.mp3" "/a/b/c/one/13.mp3" |->+ Right ()+ , expect $ RenameFile "/a/b/c-phat/one/14.mp3" "/a/b/c/one/14.mp3" |->+ Right ()+ , expect $ CreateDirectory "/a/b/c/one/eight" |-> Right ()+ , expect $ ListDirectory "/a/b/c-phat/one/eight" |->+ Right ["83.mp3", "81.mp3", "82.mp3"]+ , expect $ GetFileStatus "/a/b/c-phat/one/eight/83.mp3" |->+ Right (FileStatus 11 False 8300)+ , expect $ GetFileStatus "/a/b/c-phat/one/eight/81.mp3" |->+ Right (FileStatus 11 False 8100)+ , expect $ GetFileStatus "/a/b/c-phat/one/eight/82.mp3" |->+ Right (FileStatus 11 False 8200)+ , expect $+ RenameFile+ "/a/b/c-phat/one/eight/81.mp3"+ "/a/b/c/one/eight/81.mp3"+ |-> Right ()+ , expect $+ RenameFile+ "/a/b/c-phat/one/eight/82.mp3"+ "/a/b/c/one/eight/82.mp3"+ |-> Right ()+ , expect $+ RenameFile+ "/a/b/c-phat/one/eight/83.mp3"+ "/a/b/c/one/eight/83.mp3"+ |-> Right ()+ , expect $ RemoveDirectory "/a/b/c-phat/one/eight" |-> Right ()+ , expect $ CreateDirectory "/a/b/c/one/six" |-> Right ()+ , expect $ ListDirectory "/a/b/c-phat/one/six" |-> Right []+ , expect $ RemoveDirectory "/a/b/c-phat/one/six" |-> Right ()+ , expect $ RemoveDirectory "/a/b/c-phat/one" |-> Right ()+ , expect $ CreateDirectory "/a/b/c/two" |-> Right ()+ , expect $ ListDirectory "/a/b/c-phat/two" |->+ Right ["23.mp3", "22.mp3", "seven", "five", "21.mp3"]+ , expect $ GetFileStatus "/a/b/c-phat/two/23.mp3" |->+ Right (FileStatus 11 False 2300)+ , expect $ GetFileStatus "/a/b/c-phat/two/22.mp3" |->+ Right (FileStatus 11 False 2200)+ , expect $ GetFileStatus "/a/b/c-phat/two/seven" |->+ Right (FileStatus 11 True 7000)+ , expect $ GetFileStatus "/a/b/c-phat/two/five" |->+ Right (FileStatus 11 True 5000)+ , expect $ GetFileStatus "/a/b/c-phat/two/21.mp3" |->+ Right (FileStatus 11 False 2100)+ , expect $ RenameFile "/a/b/c-phat/two/21.mp3" "/a/b/c/two/21.mp3" |->+ Right ()+ , expect $ RenameFile "/a/b/c-phat/two/22.mp3" "/a/b/c/two/22.mp3" |->+ Right ()+ , expect $ RenameFile "/a/b/c-phat/two/23.mp3" "/a/b/c/two/23.mp3" |->+ Right ()+ , expect $ CreateDirectory "/a/b/c/two/five" |-> Right ()+ , expect $ ListDirectory "/a/b/c-phat/two/five" |-> Right ["51.mp3"]+ , expect $ GetFileStatus "/a/b/c-phat/two/five/51.mp3" |->+ Right (FileStatus 11 False 5100)+ , expect $+ RenameFile "/a/b/c-phat/two/five/51.mp3" "/a/b/c/two/five/51.mp3"+ |-> Right ()+ , expect $ RemoveDirectory "/a/b/c-phat/two/five" |-> Right ()+ , expect $ CreateDirectory "/a/b/c/two/seven" |-> Right ()+ , expect $ ListDirectory "/a/b/c-phat/two/seven" |->+ Right ["72.mp3", "71.mp3"]+ , expect $ GetFileStatus "/a/b/c-phat/two/seven/72.mp3" |->+ Right (FileStatus 11 False 7200)+ , expect $ GetFileStatus "/a/b/c-phat/two/seven/71.mp3" |->+ Right (FileStatus 11 False 7100)+ , expect $+ RenameFile+ "/a/b/c-phat/two/seven/71.mp3"+ "/a/b/c/two/seven/71.mp3"+ |-> Right ()+ , expect $+ RenameFile+ "/a/b/c-phat/two/seven/72.mp3"+ "/a/b/c/two/seven/72.mp3"+ |-> Right ()+ , expect $ RemoveDirectory "/a/b/c-phat/two/seven" |-> Right ()+ , expect $ RemoveDirectory "/a/b/c-phat/two" |-> Right ()+ , expect $ RemoveDirectory "/a/b/c-phat" |-> Right ()+ , expect $ RenameDirectory "/a/b/d" "/a/b/d-phat" |-> Right ()+ , expect $ CreateDirectory "/a/b/d" |-> Right ()+ , expect $ ListDirectory "/a/b/d-phat" |-> Right ["three"]+ , expect $ GetFileStatus "/a/b/d-phat/three" |->+ Right (FileStatus 11 True 3000)+ , expect $ CreateDirectory "/a/b/d/three" |-> Right ()+ , expect $ ListDirectory "/a/b/d-phat/three" |->+ Right ["32.mp3", "31.mp3", "four", "34.mp3", "nine", "33.mp3"]+ , expect $ GetFileStatus "/a/b/d-phat/three/32.mp3" |->+ Right (FileStatus 11 False 3200)+ , expect $ GetFileStatus "/a/b/d-phat/three/31.mp3" |->+ Right (FileStatus 11 False 3100)+ , expect $ GetFileStatus "/a/b/d-phat/three/four" |->+ Right (FileStatus 11 True 4000)+ , expect $ GetFileStatus "/a/b/d-phat/three/34.mp3" |->+ Right (FileStatus 11 False 3400)+ , expect $ GetFileStatus "/a/b/d-phat/three/nine" |->+ Right (FileStatus 11 True 9000)+ , expect $ GetFileStatus "/a/b/d-phat/three/33.mp3" |->+ Right (FileStatus 11 False 3300)+ , expect $+ RenameFile "/a/b/d-phat/three/31.mp3" "/a/b/d/three/31.mp3" |->+ Right ()+ , expect $+ RenameFile "/a/b/d-phat/three/32.mp3" "/a/b/d/three/32.mp3" |->+ Right ()+ , expect $+ RenameFile "/a/b/d-phat/three/33.mp3" "/a/b/d/three/33.mp3" |->+ Right ()+ , expect $+ RenameFile "/a/b/d-phat/three/34.mp3" "/a/b/d/three/34.mp3" |->+ Right ()+ , expect $ CreateDirectory "/a/b/d/three/four" |-> Right ()+ , expect $ ListDirectory "/a/b/d-phat/three/four" |->+ Right ["41.mp3", "42.mp3"]+ , expect $ GetFileStatus "/a/b/d-phat/three/four/41.mp3" |->+ Right (FileStatus 11 False 4100)+ , expect $ GetFileStatus "/a/b/d-phat/three/four/42.mp3" |->+ Right (FileStatus 11 False 4200)+ , expect $+ RenameFile+ "/a/b/d-phat/three/four/41.mp3"+ "/a/b/d/three/four/41.mp3"+ |-> Right ()+ , expect $+ RenameFile+ "/a/b/d-phat/three/four/42.mp3"+ "/a/b/d/three/four/42.mp3"+ |-> Right ()+ , expect $ RemoveDirectory "/a/b/d-phat/three/four" |-> Right ()+ , expect $ CreateDirectory "/a/b/d/three/nine" |-> Right ()+ , expect $ ListDirectory "/a/b/d-phat/three/nine" |-> Right ["ten"]+ , expect $ GetFileStatus "/a/b/d-phat/three/nine/ten" |->+ Right (FileStatus 11 True 10000)+ , expect $ CreateDirectory "/a/b/d/three/nine/ten" |-> Right ()+ , expect $ ListDirectory "/a/b/d-phat/three/nine/ten" |->+ Right ["102.mp3", "101.mp3"]+ , expect $ GetFileStatus "/a/b/d-phat/three/nine/ten/102.mp3" |->+ Right (FileStatus 11 False 10200)+ , expect $ GetFileStatus "/a/b/d-phat/three/nine/ten/101.mp3" |->+ Right (FileStatus 11 False 10100)+ , expect $+ RenameFile+ "/a/b/d-phat/three/nine/ten/101.mp3"+ "/a/b/d/three/nine/ten/101.mp3"+ |-> Right ()+ , expect $+ RenameFile+ "/a/b/d-phat/three/nine/ten/102.mp3"+ "/a/b/d/three/nine/ten/102.mp3"+ |-> Right ()+ , expect $ RemoveDirectory "/a/b/d-phat/three/nine/ten" |-> Right ()+ , expect $ RemoveDirectory "/a/b/d-phat/three/nine" |-> Right ()+ , expect $ RemoveDirectory "/a/b/d-phat/three" |-> Right ()+ , expect $ RemoveDirectory "/a/b/d-phat" |-> Right ()+ ]+ assertSuccess <=< Error.run $ run defaultOptions+ { optSync = False+ , optTargets = NonEmpty.fromList ["c", "d"]+ }++testLargeScript :: TestTree+testLargeScript = testCase "LargeScript" . runMockT $ do+ inSequence+ [ expect $ MakeAbsolute "c" |-> Right "/a/b/c"+ , expect $ GetFileStatus "/a/b/c" |-> Right (FileStatus 11 True 1000)+ , expect $ GetFileStatus "/a/b" |-> Right (FileStatus 11 True 100)+ , expect $ DoesPathExist "/a/b/c-phat" |-> Right False+ , expect $ MakeAbsolute "d" |-> Right "/a/b/d"+ , expect $ GetFileStatus "/a/b/d" |-> Right (FileStatus 11 True 2000)+ , expect $ GetFileStatus "/a/b" |-> Right (FileStatus 11 True 100)+ , expect $ DoesPathExist "/a/b/d-phat" |-> Right False+ , expect $ PutStrLn "mv /a/b/c /a/b/c-phat" |-> ()+ , expect $ PutStrLn "mkdir /a/b/c" |-> ()+ , expect $ ListDirectory "/a/b/c" |-> Right ["two", "one"]+ , expect $ GetFileStatus "/a/b/c/two" |->+ Right (FileStatus 11 True 2000)+ , expect $ GetFileStatus "/a/b/c/one" |->+ Right (FileStatus 11 True 1000)+ , expect $ PutStrLn "mkdir /a/b/c/one" |-> ()+ , expect $ ListDirectory "/a/b/c/one" |->+ Right ["11.mp3", "six", "13.mp3", "eight", "12.mp3", "14.mp3"]+ , expect $ GetFileStatus "/a/b/c/one/11.mp3" |->+ Right (FileStatus 11 False 1100)+ , expect $ GetFileStatus "/a/b/c/one/six" |->+ Right (FileStatus 11 True 6000)+ , expect $ GetFileStatus "/a/b/c/one/13.mp3" |->+ Right (FileStatus 11 False 1300)+ , expect $ GetFileStatus "/a/b/c/one/eight" |->+ Right (FileStatus 11 True 8000)+ , expect $ GetFileStatus "/a/b/c/one/12.mp3" |->+ Right (FileStatus 11 False 1200)+ , expect $ GetFileStatus "/a/b/c/one/14.mp3" |->+ Right (FileStatus 11 False 1400)+ , expect $ PutStrLn "mv /a/b/c-phat/one/11.mp3 /a/b/c/one/11.mp3" |-> ()+ , expect $ PutStrLn "mv /a/b/c-phat/one/12.mp3 /a/b/c/one/12.mp3" |-> ()+ , expect $ PutStrLn "mv /a/b/c-phat/one/13.mp3 /a/b/c/one/13.mp3" |-> ()+ , expect $ PutStrLn "mv /a/b/c-phat/one/14.mp3 /a/b/c/one/14.mp3" |-> ()+ , expect $ PutStrLn "mkdir /a/b/c/one/eight" |-> ()+ , expect $ ListDirectory "/a/b/c/one/eight" |->+ Right ["83.mp3", "81.mp3", "82.mp3"]+ , expect $ GetFileStatus "/a/b/c/one/eight/83.mp3" |->+ Right (FileStatus 11 False 8300)+ , expect $ GetFileStatus "/a/b/c/one/eight/81.mp3" |->+ Right (FileStatus 11 False 8100)+ , expect $ GetFileStatus "/a/b/c/one/eight/82.mp3" |->+ Right (FileStatus 11 False 8200)+ , expect $+ PutStrLn+ "mv /a/b/c-phat/one/eight/81.mp3 /a/b/c/one/eight/81.mp3"+ |-> ()+ , expect $+ PutStrLn+ "mv /a/b/c-phat/one/eight/82.mp3 /a/b/c/one/eight/82.mp3"+ |-> ()+ , expect $+ PutStrLn+ "mv /a/b/c-phat/one/eight/83.mp3 /a/b/c/one/eight/83.mp3"+ |-> ()+ , expect $ PutStrLn "rmdir /a/b/c-phat/one/eight" |-> ()+ , expect $ PutStrLn "mkdir /a/b/c/one/six" |-> ()+ , expect $ ListDirectory "/a/b/c/one/six" |-> Right []+ , expect $ PutStrLn "rmdir /a/b/c-phat/one/six" |-> ()+ , expect $ PutStrLn "rmdir /a/b/c-phat/one" |-> ()+ , expect $ PutStrLn "mkdir /a/b/c/two" |-> ()+ , expect $ ListDirectory "/a/b/c/two" |->+ Right ["23.mp3", "22.mp3", "seven", "five", "21.mp3"]+ , expect $ GetFileStatus "/a/b/c/two/23.mp3" |->+ Right (FileStatus 11 False 2300)+ , expect $ GetFileStatus "/a/b/c/two/22.mp3" |->+ Right (FileStatus 11 False 2200)+ , expect $ GetFileStatus "/a/b/c/two/seven" |->+ Right (FileStatus 11 True 7000)+ , expect $ GetFileStatus "/a/b/c/two/five" |->+ Right (FileStatus 11 True 5000)+ , expect $ GetFileStatus "/a/b/c/two/21.mp3" |->+ Right (FileStatus 11 False 2100)+ , expect $ PutStrLn "mv /a/b/c-phat/two/21.mp3 /a/b/c/two/21.mp3" |-> ()+ , expect $ PutStrLn "mv /a/b/c-phat/two/22.mp3 /a/b/c/two/22.mp3" |-> ()+ , expect $ PutStrLn "mv /a/b/c-phat/two/23.mp3 /a/b/c/two/23.mp3" |-> ()+ , expect $ PutStrLn "mkdir /a/b/c/two/five" |-> ()+ , expect $ ListDirectory "/a/b/c/two/five" |-> Right ["51.mp3"]+ , expect $ GetFileStatus "/a/b/c/two/five/51.mp3" |->+ Right (FileStatus 11 False 5100)+ , expect $+ PutStrLn "mv /a/b/c-phat/two/five/51.mp3 /a/b/c/two/five/51.mp3" |->+ ()+ , expect $ PutStrLn "rmdir /a/b/c-phat/two/five" |-> ()+ , expect $ PutStrLn "mkdir /a/b/c/two/seven" |-> ()+ , expect $ ListDirectory "/a/b/c/two/seven" |->+ Right ["72.mp3", "71.mp3"]+ , expect $ GetFileStatus "/a/b/c/two/seven/72.mp3" |->+ Right (FileStatus 11 False 7200)+ , expect $ GetFileStatus "/a/b/c/two/seven/71.mp3" |->+ Right (FileStatus 11 False 7100)+ , expect $+ PutStrLn+ "mv /a/b/c-phat/two/seven/71.mp3 /a/b/c/two/seven/71.mp3"+ |-> ()+ , expect $+ PutStrLn+ "mv /a/b/c-phat/two/seven/72.mp3 /a/b/c/two/seven/72.mp3"+ |-> ()+ , expect $ PutStrLn "rmdir /a/b/c-phat/two/seven" |-> ()+ , expect $ PutStrLn "rmdir /a/b/c-phat/two" |-> ()+ , expect $ PutStrLn "rmdir /a/b/c-phat" |-> ()+ , expect $ PutStrLn "mv /a/b/d /a/b/d-phat" |-> ()+ , expect $ PutStrLn "mkdir /a/b/d" |-> ()+ , expect $ ListDirectory "/a/b/d" |-> Right ["three"]+ , expect $ GetFileStatus "/a/b/d/three" |->+ Right (FileStatus 11 True 3000)+ , expect $ PutStrLn "mkdir /a/b/d/three" |-> ()+ , expect $ ListDirectory "/a/b/d/three" |->+ Right ["32.mp3", "31.mp3", "four", "34.mp3", "nine", "33.mp3"]+ , expect $ GetFileStatus "/a/b/d/three/32.mp3" |->+ Right (FileStatus 11 False 3200)+ , expect $ GetFileStatus "/a/b/d/three/31.mp3" |->+ Right (FileStatus 11 False 3100)+ , expect $ GetFileStatus "/a/b/d/three/four" |->+ Right (FileStatus 11 True 4000)+ , expect $ GetFileStatus "/a/b/d/three/34.mp3" |->+ Right (FileStatus 11 False 3400)+ , expect $ GetFileStatus "/a/b/d/three/nine" |->+ Right (FileStatus 11 True 9000)+ , expect $ GetFileStatus "/a/b/d/three/33.mp3" |->+ Right (FileStatus 11 False 3300)+ , expect $+ PutStrLn "mv /a/b/d-phat/three/31.mp3 /a/b/d/three/31.mp3" |-> ()+ , expect $+ PutStrLn "mv /a/b/d-phat/three/32.mp3 /a/b/d/three/32.mp3" |-> ()+ , expect $+ PutStrLn "mv /a/b/d-phat/three/33.mp3 /a/b/d/three/33.mp3" |-> ()+ , expect $+ PutStrLn "mv /a/b/d-phat/three/34.mp3 /a/b/d/three/34.mp3" |-> ()+ , expect $ PutStrLn "mkdir /a/b/d/three/four" |-> ()+ , expect $ ListDirectory "/a/b/d/three/four" |->+ Right ["41.mp3", "42.mp3"]+ , expect $ GetFileStatus "/a/b/d/three/four/41.mp3" |->+ Right (FileStatus 11 False 4100)+ , expect $ GetFileStatus "/a/b/d/three/four/42.mp3" |->+ Right (FileStatus 11 False 4200)+ , expect $+ PutStrLn+ "mv /a/b/d-phat/three/four/41.mp3 /a/b/d/three/four/41.mp3"+ |-> ()+ , expect $+ PutStrLn+ "mv /a/b/d-phat/three/four/42.mp3 /a/b/d/three/four/42.mp3"+ |-> ()+ , expect $ PutStrLn "rmdir /a/b/d-phat/three/four" |-> ()+ , expect $ PutStrLn "mkdir /a/b/d/three/nine" |-> ()+ , expect $ ListDirectory "/a/b/d/three/nine" |-> Right ["ten"]+ , expect $ GetFileStatus "/a/b/d/three/nine/ten" |->+ Right (FileStatus 11 True 10000)+ , expect $ PutStrLn "mkdir /a/b/d/three/nine/ten" |-> ()+ , expect $ ListDirectory "/a/b/d/three/nine/ten" |->+ Right ["102.mp3", "101.mp3"]+ , expect $ GetFileStatus "/a/b/d/three/nine/ten/102.mp3" |->+ Right (FileStatus 11 False 10200)+ , expect $ GetFileStatus "/a/b/d/three/nine/ten/101.mp3" |->+ Right (FileStatus 11 False 10100)+ , expect $+ PutStrLn+ "mv /a/b/d-phat/three/nine/ten/101.mp3 /a/b/d/three/nine/ten/101.mp3"+ |-> ()+ , expect $+ PutStrLn+ "mv /a/b/d-phat/three/nine/ten/102.mp3 /a/b/d/three/nine/ten/102.mp3"+ |-> ()+ , expect $ PutStrLn "rmdir /a/b/d-phat/three/nine/ten" |-> ()+ , expect $ PutStrLn "rmdir /a/b/d-phat/three/nine" |-> ()+ , expect $ PutStrLn "rmdir /a/b/d-phat/three" |-> ()+ , expect $ PutStrLn "rmdir /a/b/d-phat" |-> ()+ ]+ assertSuccess <=< Error.run $ run defaultOptions+ { optSync = False+ , optScript = True+ , optTargets = NonEmpty.fromList ["c", "d"]+ }++------------------------------------------------------------------------------++tests :: TestTree+tests = testGroup "phatsort:Mock"+ [ testCaseSensitive+ , testCaseSensitiveScript+ , testCaseInsensitive+ , testCaseInsensitiveScript+ , testFirstNone+ , testFirstNoneScript+ , testFirstDirs+ , testFirstDirsScript+ , testFirstFiles+ , testFirstFilesScript+ , testNoSync+ , testNoSyncScript+ , testOrderNameReverse+ , testOrderNameReverseScript+ , testOrderTime+ , testOrderTimeScript+ , testOrderTimeReverse+ , testOrderTimeReverseScript+ , testOrderRandom+ , testOrderRandomScript+ , testVerbose+ , testVerboseScript+ , testMultipleTargets+ , testMultipleTargetsScript+ , testTargetNotFound+ , testPhatTarget+ , testTargetNotDirectory+ , testTargetMountPoint+ , testTargetPhatExists+ , testLarge+ , testLargeScript+ ]
+ test/PhatSort/Cmd/SeqCp/Mock.hs view
@@ -0,0 +1,2237 @@+module PhatSort.Cmd.SeqCp.Mock (tests) where++-- https://hackage.haskell.org/package/base+import Control.Monad ((<=<))+import qualified Data.List.NonEmpty as NonEmpty++-- https://hackage.haskell.org/package/HMock+import Test.HMock ((|->), expect, inAnyOrder, inSequence, runMockT)++-- https://hackage.haskell.org/package/tasty+import Test.Tasty (TestTree, testGroup)++-- https://hackage.haskell.org/package/tasty-hunit+import Test.Tasty.HUnit (testCase)++-- (phatsort)+import PhatSort.Cmd.SeqCp+ ( Options+ ( Options, optCase, optDestination, optFirst, optOrder, optReverse+ , optScript, optSources, optSync, optVerbose+ )+ , run+ )+import PhatSort.Monad.FileSystem (FileStatus(FileStatus))+import qualified PhatSort.Monad.Trans.Error as Error+import PhatSort.SortOptions+ ( SortCase(CaseInsensitive, CaseSensitive)+ , SortFirst(FirstDirs, FirstFiles, FirstNone)+ , SortOrder(OrderName, OrderRandom, OrderTime)+ )++-- (phatsort:test)+import TestLib++------------------------------------------------------------------------------++defaultOptions :: Options+defaultOptions = Options+ { optCase = CaseSensitive+ , optFirst = FirstNone+ , optSync = True+ , optOrder = OrderName+ , optReverse = False+ , optScript = False+ , optVerbose = False+ , optSources = NonEmpty.fromList ["one"]+ , optDestination = "/z"+ }++sourcesOptions :: Options+sourcesOptions = defaultOptions+ { optSources =+ NonEmpty.fromList ["python.mp4", "/a/b/two", "one", "c/Haskell.mp4"]+ }++------------------------------------------------------------------------------++testCaseSensitive :: TestTree+testCaseSensitive = testCase "CaseSensitive" . runMockT $ do+ inSequence+ [ expect $ MakeAbsolute "/z" |-> Right "/z"+ , expect $ GetFileStatus "/z" |-> Right (FileStatus 10 True 90000)+ , expect $ MakeAbsolute "one" |-> Right "/a/b/one"+ , expect $ GetFileStatus "/a/b/one" |-> Right (FileStatus 11 True 10000)+ , expect $ DoesPathExist "/z/one" |-> Right False+ , expect $ CreateDirectory "/z/one" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ ListDirectory "/a/b/one" |->+ Right ["dos.mp3", "Uno.mp3", "tres.mp3"]+ , expect $ GetFileStatus "/a/b/one/dos.mp3" |->+ Right (FileStatus 11 False 2000)+ , expect $ GetFileStatus "/a/b/one/Uno.mp3" |->+ Right (FileStatus 11 False 1000)+ , expect $ GetFileStatus "/a/b/one/tres.mp3" |->+ Right (FileStatus 11 False 3000)+ , expect $ CopyFile "/a/b/one/Uno.mp3" "/z/one/Uno.mp3" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ CopyFile "/a/b/one/dos.mp3" "/z/one/dos.mp3" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ CopyFile "/a/b/one/tres.mp3" "/z/one/tres.mp3" |-> Right ()+ , expect $ Sync |-> ()+ ]+ assertSuccess <=< Error.run $ run defaultOptions++------------------------------------------------------------------------------++testCaseSensitiveScript :: TestTree+testCaseSensitiveScript = testCase "CaseSensitiveScript" . runMockT $ do+ inSequence+ [ expect $ MakeAbsolute "/z" |-> Right "/z"+ , expect $ GetFileStatus "/z" |-> Right (FileStatus 10 True 90000)+ , expect $ MakeAbsolute "one" |-> Right "/a/b/one"+ , expect $ GetFileStatus "/a/b/one" |-> Right (FileStatus 11 True 10000)+ , expect $ DoesPathExist "/z/one" |-> Right False+ , expect $ PutStrLn "mkdir /z/one" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ ListDirectory "/a/b/one" |->+ Right ["dos.mp3", "Uno.mp3", "tres.mp3"]+ , expect $ GetFileStatus "/a/b/one/dos.mp3" |->+ Right (FileStatus 11 False 2000)+ , expect $ GetFileStatus "/a/b/one/Uno.mp3" |->+ Right (FileStatus 11 False 1000)+ , expect $ GetFileStatus "/a/b/one/tres.mp3" |->+ Right (FileStatus 11 False 3000)+ , expect $ PutStrLn "cp /a/b/one/Uno.mp3 /z/one/Uno.mp3" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "cp /a/b/one/dos.mp3 /z/one/dos.mp3" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "cp /a/b/one/tres.mp3 /z/one/tres.mp3" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ ]+ assertSuccess <=< Error.run $ run defaultOptions+ { optScript = True+ }++------------------------------------------------------------------------------++testSourcesCaseSensitive :: TestTree+testSourcesCaseSensitive = testCase "SourcesCaseSensitive" . runMockT $ do+ inSequence+ [ expect $ MakeAbsolute "/z" |-> Right "/z"+ , expect $ GetFileStatus "/z" |-> Right (FileStatus 10 True 90000)+ , expect $ MakeAbsolute "python.mp4" |-> Right "/a/b/python.mp4"+ , expect $ GetFileStatus "/a/b/python.mp4" |->+ Right (FileStatus 11 False 30000)+ , expect $ DoesPathExist "/z/python.mp4" |-> Right False+ , expect $ MakeAbsolute "/a/b/two" |-> Right "/a/b/two"+ , expect $ GetFileStatus "/a/b/two" |-> Right (FileStatus 11 True 20000)+ , expect $ DoesPathExist "/z/two" |-> Right False+ , expect $ MakeAbsolute "one" |-> Right "/a/b/one"+ , expect $ GetFileStatus "/a/b/one" |-> Right (FileStatus 11 True 10000)+ , expect $ DoesPathExist "/z/one" |-> Right False+ , expect $ MakeAbsolute "c/Haskell.mp4" |-> Right "/a/b/c/Haskell.mp4"+ , expect $ GetFileStatus "/a/b/c/Haskell.mp4" |->+ Right (FileStatus 11 False 40000)+ , expect $ DoesPathExist "/z/Haskell.mp4" |-> Right False+ , expect $ CopyFile "/a/b/c/Haskell.mp4" "/z/Haskell.mp4" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ CreateDirectory "/z/one" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ ListDirectory "/a/b/one" |->+ Right ["dos.mp3", "Uno.mp3", "tres.mp3"]+ , expect $ GetFileStatus "/a/b/one/dos.mp3" |->+ Right (FileStatus 11 False 2000)+ , expect $ GetFileStatus "/a/b/one/Uno.mp3" |->+ Right (FileStatus 11 False 1000)+ , expect $ GetFileStatus "/a/b/one/tres.mp3" |->+ Right (FileStatus 11 False 3000)+ , expect $ CopyFile "/a/b/one/Uno.mp3" "/z/one/Uno.mp3" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ CopyFile "/a/b/one/dos.mp3" "/z/one/dos.mp3" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ CopyFile "/a/b/one/tres.mp3" "/z/one/tres.mp3" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ CopyFile "/a/b/python.mp4" "/z/python.mp4" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ CreateDirectory "/z/two" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ ListDirectory "/a/b/two" |-> Right []+ ]+ assertSuccess <=< Error.run $ run sourcesOptions++------------------------------------------------------------------------------++testSourcesCaseSensitiveScript :: TestTree+testSourcesCaseSensitiveScript =+ testCase "SourcesCaseSensitiveScript" . runMockT $ do+ inSequence+ [ expect $ MakeAbsolute "/z" |-> Right "/z"+ , expect $ GetFileStatus "/z" |-> Right (FileStatus 10 True 90000)+ , expect $ MakeAbsolute "python.mp4" |-> Right "/a/b/python.mp4"+ , expect $ GetFileStatus "/a/b/python.mp4" |->+ Right (FileStatus 11 False 30000)+ , expect $ DoesPathExist "/z/python.mp4" |-> Right False+ , expect $ MakeAbsolute "/a/b/two" |-> Right "/a/b/two"+ , expect $ GetFileStatus "/a/b/two" |->+ Right (FileStatus 11 True 20000)+ , expect $ DoesPathExist "/z/two" |-> Right False+ , expect $ MakeAbsolute "one" |-> Right "/a/b/one"+ , expect $ GetFileStatus "/a/b/one" |->+ Right (FileStatus 11 True 10000)+ , expect $ DoesPathExist "/z/one" |-> Right False+ , expect $ MakeAbsolute "c/Haskell.mp4" |-> Right "/a/b/c/Haskell.mp4"+ , expect $ GetFileStatus "/a/b/c/Haskell.mp4" |->+ Right (FileStatus 11 False 40000)+ , expect $ DoesPathExist "/z/Haskell.mp4" |-> Right False+ , expect $ PutStrLn "cp /a/b/c/Haskell.mp4 /z/Haskell.mp4" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "mkdir /z/one" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ ListDirectory "/a/b/one" |->+ Right ["dos.mp3", "Uno.mp3", "tres.mp3"]+ , expect $ GetFileStatus "/a/b/one/dos.mp3" |->+ Right (FileStatus 11 False 2000)+ , expect $ GetFileStatus "/a/b/one/Uno.mp3" |->+ Right (FileStatus 11 False 1000)+ , expect $ GetFileStatus "/a/b/one/tres.mp3" |->+ Right (FileStatus 11 False 3000)+ , expect $ PutStrLn "cp /a/b/one/Uno.mp3 /z/one/Uno.mp3" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "cp /a/b/one/dos.mp3 /z/one/dos.mp3" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "cp /a/b/one/tres.mp3 /z/one/tres.mp3" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "cp /a/b/python.mp4 /z/python.mp4" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "mkdir /z/two" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ ListDirectory "/a/b/two" |-> Right []+ ]+ assertSuccess <=< Error.run $ run sourcesOptions+ { optScript = True+ }++------------------------------------------------------------------------------++testCaseInsensitive :: TestTree+testCaseInsensitive = testCase "CaseInsensitive" . runMockT $ do+ inSequence+ [ expect $ MakeAbsolute "/z" |-> Right "/z"+ , expect $ GetFileStatus "/z" |-> Right (FileStatus 10 True 90000)+ , expect $ MakeAbsolute "one" |-> Right "/a/b/one"+ , expect $ GetFileStatus "/a/b/one" |-> Right (FileStatus 11 True 10000)+ , expect $ DoesPathExist "/z/one" |-> Right False+ , expect $ CreateDirectory "/z/one" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ ListDirectory "/a/b/one" |->+ Right ["dos.mp3", "Uno.mp3", "tres.mp3"]+ , expect $ GetFileStatus "/a/b/one/dos.mp3" |->+ Right (FileStatus 11 False 2000)+ , expect $ GetFileStatus "/a/b/one/Uno.mp3" |->+ Right (FileStatus 11 False 1000)+ , expect $ GetFileStatus "/a/b/one/tres.mp3" |->+ Right (FileStatus 11 False 3000)+ , expect $ CopyFile "/a/b/one/dos.mp3" "/z/one/dos.mp3" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ CopyFile "/a/b/one/tres.mp3" "/z/one/tres.mp3" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ CopyFile "/a/b/one/Uno.mp3" "/z/one/Uno.mp3" |-> Right ()+ , expect $ Sync |-> ()+ ]+ assertSuccess <=< Error.run $ run defaultOptions+ { optCase = CaseInsensitive+ }++------------------------------------------------------------------------------++testCaseInsensitiveScript :: TestTree+testCaseInsensitiveScript = testCase "CaseInsensitiveScript" . runMockT $ do+ inSequence+ [ expect $ MakeAbsolute "/z" |-> Right "/z"+ , expect $ GetFileStatus "/z" |-> Right (FileStatus 10 True 90000)+ , expect $ MakeAbsolute "one" |-> Right "/a/b/one"+ , expect $ GetFileStatus "/a/b/one" |-> Right (FileStatus 11 True 10000)+ , expect $ DoesPathExist "/z/one" |-> Right False+ , expect $ PutStrLn "mkdir /z/one" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ ListDirectory "/a/b/one" |->+ Right ["dos.mp3", "Uno.mp3", "tres.mp3"]+ , expect $ GetFileStatus "/a/b/one/dos.mp3" |->+ Right (FileStatus 11 False 2000)+ , expect $ GetFileStatus "/a/b/one/Uno.mp3" |->+ Right (FileStatus 11 False 1000)+ , expect $ GetFileStatus "/a/b/one/tres.mp3" |->+ Right (FileStatus 11 False 3000)+ , expect $ PutStrLn "cp /a/b/one/dos.mp3 /z/one/dos.mp3" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "cp /a/b/one/tres.mp3 /z/one/tres.mp3" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "cp /a/b/one/Uno.mp3 /z/one/Uno.mp3" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ ]+ assertSuccess <=< Error.run $ run defaultOptions+ { optCase = CaseInsensitive+ , optScript = True+ }++------------------------------------------------------------------------------++testSourcesCaseInsensitive :: TestTree+testSourcesCaseInsensitive = testCase "SourcesCaseInsensitive" . runMockT $ do+ inSequence+ [ expect $ MakeAbsolute "/z" |-> Right "/z"+ , expect $ GetFileStatus "/z" |-> Right (FileStatus 10 True 90000)+ , expect $ MakeAbsolute "python.mp4" |-> Right "/a/b/python.mp4"+ , expect $ GetFileStatus "/a/b/python.mp4" |->+ Right (FileStatus 11 False 30000)+ , expect $ DoesPathExist "/z/python.mp4" |-> Right False+ , expect $ MakeAbsolute "/a/b/two" |-> Right "/a/b/two"+ , expect $ GetFileStatus "/a/b/two" |-> Right (FileStatus 11 True 20000)+ , expect $ DoesPathExist "/z/two" |-> Right False+ , expect $ MakeAbsolute "one" |-> Right "/a/b/one"+ , expect $ GetFileStatus "/a/b/one" |-> Right (FileStatus 11 True 10000)+ , expect $ DoesPathExist "/z/one" |-> Right False+ , expect $ MakeAbsolute "c/Haskell.mp4" |-> Right "/a/b/c/Haskell.mp4"+ , expect $ GetFileStatus "/a/b/c/Haskell.mp4" |->+ Right (FileStatus 11 False 40000)+ , expect $ DoesPathExist "/z/Haskell.mp4" |-> Right False+ , expect $ CopyFile "/a/b/c/Haskell.mp4" "/z/Haskell.mp4" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ CreateDirectory "/z/one" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ ListDirectory "/a/b/one" |->+ Right ["dos.mp3", "Uno.mp3", "tres.mp3"]+ , expect $ GetFileStatus "/a/b/one/dos.mp3" |->+ Right (FileStatus 11 False 2000)+ , expect $ GetFileStatus "/a/b/one/Uno.mp3" |->+ Right (FileStatus 11 False 1000)+ , expect $ GetFileStatus "/a/b/one/tres.mp3" |->+ Right (FileStatus 11 False 3000)+ , expect $ CopyFile "/a/b/one/dos.mp3" "/z/one/dos.mp3" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ CopyFile "/a/b/one/tres.mp3" "/z/one/tres.mp3" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ CopyFile "/a/b/one/Uno.mp3" "/z/one/Uno.mp3" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ CopyFile "/a/b/python.mp4" "/z/python.mp4" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ CreateDirectory "/z/two" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ ListDirectory "/a/b/two" |-> Right []+ ]+ assertSuccess <=< Error.run $ run sourcesOptions+ { optCase = CaseInsensitive+ }++------------------------------------------------------------------------------++testSourcesCaseInsensitiveScript :: TestTree+testSourcesCaseInsensitiveScript =+ testCase "SourcesCaseInsensitiveScript" . runMockT $ do+ inSequence+ [ expect $ MakeAbsolute "/z" |-> Right "/z"+ , expect $ GetFileStatus "/z" |-> Right (FileStatus 10 True 90000)+ , expect $ MakeAbsolute "python.mp4" |-> Right "/a/b/python.mp4"+ , expect $ GetFileStatus "/a/b/python.mp4" |->+ Right (FileStatus 11 False 30000)+ , expect $ DoesPathExist "/z/python.mp4" |-> Right False+ , expect $ MakeAbsolute "/a/b/two" |-> Right "/a/b/two"+ , expect $ GetFileStatus "/a/b/two" |->+ Right (FileStatus 11 True 20000)+ , expect $ DoesPathExist "/z/two" |-> Right False+ , expect $ MakeAbsolute "one" |-> Right "/a/b/one"+ , expect $ GetFileStatus "/a/b/one" |->+ Right (FileStatus 11 True 10000)+ , expect $ DoesPathExist "/z/one" |-> Right False+ , expect $ MakeAbsolute "c/Haskell.mp4" |-> Right "/a/b/c/Haskell.mp4"+ , expect $ GetFileStatus "/a/b/c/Haskell.mp4" |->+ Right (FileStatus 11 False 40000)+ , expect $ DoesPathExist "/z/Haskell.mp4" |-> Right False+ , expect $ PutStrLn "cp /a/b/c/Haskell.mp4 /z/Haskell.mp4" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "mkdir /z/one" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ ListDirectory "/a/b/one" |->+ Right ["dos.mp3", "Uno.mp3", "tres.mp3"]+ , expect $ GetFileStatus "/a/b/one/dos.mp3" |->+ Right (FileStatus 11 False 2000)+ , expect $ GetFileStatus "/a/b/one/Uno.mp3" |->+ Right (FileStatus 11 False 1000)+ , expect $ GetFileStatus "/a/b/one/tres.mp3" |->+ Right (FileStatus 11 False 3000)+ , expect $ PutStrLn "cp /a/b/one/dos.mp3 /z/one/dos.mp3" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "cp /a/b/one/tres.mp3 /z/one/tres.mp3" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "cp /a/b/one/Uno.mp3 /z/one/Uno.mp3" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "cp /a/b/python.mp4 /z/python.mp4" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "mkdir /z/two" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ ListDirectory "/a/b/two" |-> Right []+ ]+ assertSuccess <=< Error.run $ run sourcesOptions+ { optCase = CaseInsensitive+ , optScript = True+ }++------------------------------------------------------------------------------++testFirstNone :: TestTree+testFirstNone = testCase "FirstNone" . runMockT $ do+ inSequence+ [ expect $ MakeAbsolute "/z" |-> Right "/z"+ , expect $ GetFileStatus "/z" |-> Right (FileStatus 10 True 90000)+ , expect $ MakeAbsolute "one" |-> Right "/a/b/one"+ , expect $ GetFileStatus "/a/b/one" |-> Right (FileStatus 11 True 10000)+ , expect $ DoesPathExist "/z/one" |-> Right False+ , expect $ CreateDirectory "/z/one" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ ListDirectory "/a/b/one" |->+ Right ["dos.mp3", "three", "Uno.mp3", "two", "tres.mp3"]+ , expect $ GetFileStatus "/a/b/one/dos.mp3" |->+ Right (FileStatus 11 False 2000)+ , expect $ GetFileStatus "/a/b/one/three" |->+ Right (FileStatus 11 True 3000)+ , expect $ GetFileStatus "/a/b/one/Uno.mp3" |->+ Right (FileStatus 11 False 1000)+ , expect $ GetFileStatus "/a/b/one/two" |->+ Right (FileStatus 11 True 2000)+ , expect $ GetFileStatus "/a/b/one/tres.mp3" |->+ Right (FileStatus 11 False 3000)+ , expect $ CopyFile "/a/b/one/Uno.mp3" "/z/one/Uno.mp3" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ CopyFile "/a/b/one/dos.mp3" "/z/one/dos.mp3" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ CreateDirectory "/z/one/three" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ ListDirectory "/a/b/one/three" |-> Right []+ , expect $ CopyFile "/a/b/one/tres.mp3" "/z/one/tres.mp3" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ CreateDirectory "/z/one/two" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ ListDirectory "/a/b/one/two" |-> Right []+ ]+ assertSuccess <=< Error.run $ run defaultOptions++------------------------------------------------------------------------------++testFirstNoneScript :: TestTree+testFirstNoneScript = testCase "FirstNoneScript" . runMockT $ do+ inSequence+ [ expect $ MakeAbsolute "/z" |-> Right "/z"+ , expect $ GetFileStatus "/z" |-> Right (FileStatus 10 True 90000)+ , expect $ MakeAbsolute "one" |-> Right "/a/b/one"+ , expect $ GetFileStatus "/a/b/one" |-> Right (FileStatus 11 True 10000)+ , expect $ DoesPathExist "/z/one" |-> Right False+ , expect $ PutStrLn "mkdir /z/one" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ ListDirectory "/a/b/one" |->+ Right ["dos.mp3", "three", "Uno.mp3", "two", "tres.mp3"]+ , expect $ GetFileStatus "/a/b/one/dos.mp3" |->+ Right (FileStatus 11 False 2000)+ , expect $ GetFileStatus "/a/b/one/three" |->+ Right (FileStatus 11 True 3000)+ , expect $ GetFileStatus "/a/b/one/Uno.mp3" |->+ Right (FileStatus 11 False 1000)+ , expect $ GetFileStatus "/a/b/one/two" |->+ Right (FileStatus 11 True 2000)+ , expect $ GetFileStatus "/a/b/one/tres.mp3" |->+ Right (FileStatus 11 False 3000)+ , expect $ PutStrLn "cp /a/b/one/Uno.mp3 /z/one/Uno.mp3" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "cp /a/b/one/dos.mp3 /z/one/dos.mp3" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "mkdir /z/one/three" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ ListDirectory "/a/b/one/three" |-> Right []+ , expect $ PutStrLn "cp /a/b/one/tres.mp3 /z/one/tres.mp3" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "mkdir /z/one/two" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ ListDirectory "/a/b/one/two" |-> Right []+ ]+ assertSuccess <=< Error.run $ run defaultOptions+ { optScript = True+ }++------------------------------------------------------------------------------++testFirstDirs :: TestTree+testFirstDirs = testCase "FirstDirs" . runMockT $ do+ inSequence+ [ expect $ MakeAbsolute "/z" |-> Right "/z"+ , expect $ GetFileStatus "/z" |-> Right (FileStatus 10 True 90000)+ , expect $ MakeAbsolute "one" |-> Right "/a/b/one"+ , expect $ GetFileStatus "/a/b/one" |-> Right (FileStatus 11 True 10000)+ , expect $ DoesPathExist "/z/one" |-> Right False+ , expect $ CreateDirectory "/z/one" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ ListDirectory "/a/b/one" |->+ Right ["dos.mp3", "three", "Uno.mp3", "two", "tres.mp3"]+ , expect $ GetFileStatus "/a/b/one/dos.mp3" |->+ Right (FileStatus 11 False 2000)+ , expect $ GetFileStatus "/a/b/one/three" |->+ Right (FileStatus 11 True 3000)+ , expect $ GetFileStatus "/a/b/one/Uno.mp3" |->+ Right (FileStatus 11 False 1000)+ , expect $ GetFileStatus "/a/b/one/two" |->+ Right (FileStatus 11 True 2000)+ , expect $ GetFileStatus "/a/b/one/tres.mp3" |->+ Right (FileStatus 11 False 3000)+ , expect $ CreateDirectory "/z/one/three" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ ListDirectory "/a/b/one/three" |-> Right []+ , expect $ CreateDirectory "/z/one/two" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ ListDirectory "/a/b/one/two" |-> Right []+ , expect $ CopyFile "/a/b/one/Uno.mp3" "/z/one/Uno.mp3" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ CopyFile "/a/b/one/dos.mp3" "/z/one/dos.mp3" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ CopyFile "/a/b/one/tres.mp3" "/z/one/tres.mp3" |-> Right ()+ , expect $ Sync |-> ()+ ]+ assertSuccess <=< Error.run $ run defaultOptions+ { optFirst = FirstDirs+ }++------------------------------------------------------------------------------++testFirstDirsScript :: TestTree+testFirstDirsScript = testCase "FirstDirsScript" . runMockT $ do+ inSequence+ [ expect $ MakeAbsolute "/z" |-> Right "/z"+ , expect $ GetFileStatus "/z" |-> Right (FileStatus 10 True 90000)+ , expect $ MakeAbsolute "one" |-> Right "/a/b/one"+ , expect $ GetFileStatus "/a/b/one" |-> Right (FileStatus 11 True 10000)+ , expect $ DoesPathExist "/z/one" |-> Right False+ , expect $ PutStrLn "mkdir /z/one" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ ListDirectory "/a/b/one" |->+ Right ["dos.mp3", "three", "Uno.mp3", "two", "tres.mp3"]+ , expect $ GetFileStatus "/a/b/one/dos.mp3" |->+ Right (FileStatus 11 False 2000)+ , expect $ GetFileStatus "/a/b/one/three" |->+ Right (FileStatus 11 True 3000)+ , expect $ GetFileStatus "/a/b/one/Uno.mp3" |->+ Right (FileStatus 11 False 1000)+ , expect $ GetFileStatus "/a/b/one/two" |->+ Right (FileStatus 11 True 2000)+ , expect $ GetFileStatus "/a/b/one/tres.mp3" |->+ Right (FileStatus 11 False 3000)+ , expect $ PutStrLn "mkdir /z/one/three" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ ListDirectory "/a/b/one/three" |-> Right []+ , expect $ PutStrLn "mkdir /z/one/two" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ ListDirectory "/a/b/one/two" |-> Right []+ , expect $ PutStrLn "cp /a/b/one/Uno.mp3 /z/one/Uno.mp3" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "cp /a/b/one/dos.mp3 /z/one/dos.mp3" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "cp /a/b/one/tres.mp3 /z/one/tres.mp3" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ ]+ assertSuccess <=< Error.run $ run defaultOptions+ { optFirst = FirstDirs+ , optScript = True+ }++------------------------------------------------------------------------------++testSourcesFirstDirs :: TestTree+testSourcesFirstDirs = testCase "SourcesFirstDirs" . runMockT $ do+ inSequence+ [ expect $ MakeAbsolute "/z" |-> Right "/z"+ , expect $ GetFileStatus "/z" |-> Right (FileStatus 10 True 90000)+ , expect $ MakeAbsolute "python.mp4" |-> Right "/a/b/python.mp4"+ , expect $ GetFileStatus "/a/b/python.mp4" |->+ Right (FileStatus 11 False 30000)+ , expect $ DoesPathExist "/z/python.mp4" |-> Right False+ , expect $ MakeAbsolute "/a/b/two" |-> Right "/a/b/two"+ , expect $ GetFileStatus "/a/b/two" |-> Right (FileStatus 11 True 20000)+ , expect $ DoesPathExist "/z/two" |-> Right False+ , expect $ MakeAbsolute "one" |-> Right "/a/b/one"+ , expect $ GetFileStatus "/a/b/one" |-> Right (FileStatus 11 True 10000)+ , expect $ DoesPathExist "/z/one" |-> Right False+ , expect $ MakeAbsolute "c/Haskell.mp4" |-> Right "/a/b/c/Haskell.mp4"+ , expect $ GetFileStatus "/a/b/c/Haskell.mp4" |->+ Right (FileStatus 11 False 40000)+ , expect $ DoesPathExist "/z/Haskell.mp4" |-> Right False+ , expect $ CreateDirectory "/z/one" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ ListDirectory "/a/b/one" |->+ Right ["dos.mp3", "Uno.mp3", "tres.mp3"]+ , expect $ GetFileStatus "/a/b/one/dos.mp3" |->+ Right (FileStatus 11 False 2000)+ , expect $ GetFileStatus "/a/b/one/Uno.mp3" |->+ Right (FileStatus 11 False 1000)+ , expect $ GetFileStatus "/a/b/one/tres.mp3" |->+ Right (FileStatus 11 False 3000)+ , expect $ CopyFile "/a/b/one/Uno.mp3" "/z/one/Uno.mp3" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ CopyFile "/a/b/one/dos.mp3" "/z/one/dos.mp3" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ CopyFile "/a/b/one/tres.mp3" "/z/one/tres.mp3" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ CreateDirectory "/z/two" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ ListDirectory "/a/b/two" |-> Right []+ , expect $ CopyFile "/a/b/c/Haskell.mp4" "/z/Haskell.mp4" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ CopyFile "/a/b/python.mp4" "/z/python.mp4" |-> Right ()+ , expect $ Sync |-> ()+ ]+ assertSuccess <=< Error.run $ run sourcesOptions+ { optFirst = FirstDirs+ }++------------------------------------------------------------------------------++testSourcesFirstDirsScript :: TestTree+testSourcesFirstDirsScript = testCase "SourcesFirstDirsScript" . runMockT $ do+ inSequence+ [ expect $ MakeAbsolute "/z" |-> Right "/z"+ , expect $ GetFileStatus "/z" |-> Right (FileStatus 10 True 90000)+ , expect $ MakeAbsolute "python.mp4" |-> Right "/a/b/python.mp4"+ , expect $ GetFileStatus "/a/b/python.mp4" |->+ Right (FileStatus 11 False 30000)+ , expect $ DoesPathExist "/z/python.mp4" |-> Right False+ , expect $ MakeAbsolute "/a/b/two" |-> Right "/a/b/two"+ , expect $ GetFileStatus "/a/b/two" |->+ Right (FileStatus 11 True 20000)+ , expect $ DoesPathExist "/z/two" |-> Right False+ , expect $ MakeAbsolute "one" |-> Right "/a/b/one"+ , expect $ GetFileStatus "/a/b/one" |->+ Right (FileStatus 11 True 10000)+ , expect $ DoesPathExist "/z/one" |-> Right False+ , expect $ MakeAbsolute "c/Haskell.mp4" |-> Right "/a/b/c/Haskell.mp4"+ , expect $ GetFileStatus "/a/b/c/Haskell.mp4" |->+ Right (FileStatus 11 False 40000)+ , expect $ DoesPathExist "/z/Haskell.mp4" |-> Right False+ , expect $ PutStrLn "mkdir /z/one" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ ListDirectory "/a/b/one" |->+ Right ["dos.mp3", "Uno.mp3", "tres.mp3"]+ , expect $ GetFileStatus "/a/b/one/dos.mp3" |->+ Right (FileStatus 11 False 2000)+ , expect $ GetFileStatus "/a/b/one/Uno.mp3" |->+ Right (FileStatus 11 False 1000)+ , expect $ GetFileStatus "/a/b/one/tres.mp3" |->+ Right (FileStatus 11 False 3000)+ , expect $ PutStrLn "cp /a/b/one/Uno.mp3 /z/one/Uno.mp3" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "cp /a/b/one/dos.mp3 /z/one/dos.mp3" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "cp /a/b/one/tres.mp3 /z/one/tres.mp3" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "mkdir /z/two" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ ListDirectory "/a/b/two" |-> Right []+ , expect $ PutStrLn "cp /a/b/c/Haskell.mp4 /z/Haskell.mp4" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "cp /a/b/python.mp4 /z/python.mp4" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ ]+ assertSuccess <=< Error.run $ run sourcesOptions+ { optFirst = FirstDirs+ , optScript = True+ }++------------------------------------------------------------------------------++testFirstFiles :: TestTree+testFirstFiles = testCase "FirstFiles" . runMockT $ do+ inSequence+ [ expect $ MakeAbsolute "/z" |-> Right "/z"+ , expect $ GetFileStatus "/z" |-> Right (FileStatus 10 True 90000)+ , expect $ MakeAbsolute "one" |-> Right "/a/b/one"+ , expect $ GetFileStatus "/a/b/one" |-> Right (FileStatus 11 True 10000)+ , expect $ DoesPathExist "/z/one" |-> Right False+ , expect $ CreateDirectory "/z/one" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ ListDirectory "/a/b/one" |->+ Right ["dos.mp3", "three", "Uno.mp3", "two", "tres.mp3"]+ , expect $ GetFileStatus "/a/b/one/dos.mp3" |->+ Right (FileStatus 11 False 2000)+ , expect $ GetFileStatus "/a/b/one/three" |->+ Right (FileStatus 11 True 3000)+ , expect $ GetFileStatus "/a/b/one/Uno.mp3" |->+ Right (FileStatus 11 False 1000)+ , expect $ GetFileStatus "/a/b/one/two" |->+ Right (FileStatus 11 True 2000)+ , expect $ GetFileStatus "/a/b/one/tres.mp3" |->+ Right (FileStatus 11 False 3000)+ , expect $ CopyFile "/a/b/one/Uno.mp3" "/z/one/Uno.mp3" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ CopyFile "/a/b/one/dos.mp3" "/z/one/dos.mp3" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ CopyFile "/a/b/one/tres.mp3" "/z/one/tres.mp3" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ CreateDirectory "/z/one/three" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ ListDirectory "/a/b/one/three" |-> Right []+ , expect $ CreateDirectory "/z/one/two" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ ListDirectory "/a/b/one/two" |-> Right []+ ]+ assertSuccess <=< Error.run $ run defaultOptions+ { optFirst = FirstFiles+ }++------------------------------------------------------------------------------++testFirstFilesScript :: TestTree+testFirstFilesScript = testCase "FirstFilesScript" . runMockT $ do+ inSequence+ [ expect $ MakeAbsolute "/z" |-> Right "/z"+ , expect $ GetFileStatus "/z" |-> Right (FileStatus 10 True 90000)+ , expect $ MakeAbsolute "one" |-> Right "/a/b/one"+ , expect $ GetFileStatus "/a/b/one" |-> Right (FileStatus 11 True 10000)+ , expect $ DoesPathExist "/z/one" |-> Right False+ , expect $ PutStrLn "mkdir /z/one" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ ListDirectory "/a/b/one" |->+ Right ["dos.mp3", "three", "Uno.mp3", "two", "tres.mp3"]+ , expect $ GetFileStatus "/a/b/one/dos.mp3" |->+ Right (FileStatus 11 False 2000)+ , expect $ GetFileStatus "/a/b/one/three" |->+ Right (FileStatus 11 True 3000)+ , expect $ GetFileStatus "/a/b/one/Uno.mp3" |->+ Right (FileStatus 11 False 1000)+ , expect $ GetFileStatus "/a/b/one/two" |->+ Right (FileStatus 11 True 2000)+ , expect $ GetFileStatus "/a/b/one/tres.mp3" |->+ Right (FileStatus 11 False 3000)+ , expect $ PutStrLn "cp /a/b/one/Uno.mp3 /z/one/Uno.mp3" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "cp /a/b/one/dos.mp3 /z/one/dos.mp3" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "cp /a/b/one/tres.mp3 /z/one/tres.mp3" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "mkdir /z/one/three" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ ListDirectory "/a/b/one/three" |-> Right []+ , expect $ PutStrLn "mkdir /z/one/two" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ ListDirectory "/a/b/one/two" |-> Right []++ ]+ assertSuccess <=< Error.run $ run defaultOptions+ { optFirst = FirstFiles+ , optScript = True+ }++------------------------------------------------------------------------------++testSourcesFirstFiles :: TestTree+testSourcesFirstFiles = testCase "SourcesFirstFiles" . runMockT $ do+ inSequence+ [ expect $ MakeAbsolute "/z" |-> Right "/z"+ , expect $ GetFileStatus "/z" |-> Right (FileStatus 10 True 90000)+ , expect $ MakeAbsolute "python.mp4" |-> Right "/a/b/python.mp4"+ , expect $ GetFileStatus "/a/b/python.mp4" |->+ Right (FileStatus 11 False 30000)+ , expect $ DoesPathExist "/z/python.mp4" |-> Right False+ , expect $ MakeAbsolute "/a/b/two" |-> Right "/a/b/two"+ , expect $ GetFileStatus "/a/b/two" |-> Right (FileStatus 11 True 20000)+ , expect $ DoesPathExist "/z/two" |-> Right False+ , expect $ MakeAbsolute "one" |-> Right "/a/b/one"+ , expect $ GetFileStatus "/a/b/one" |-> Right (FileStatus 11 True 10000)+ , expect $ DoesPathExist "/z/one" |-> Right False+ , expect $ MakeAbsolute "c/Haskell.mp4" |-> Right "/a/b/c/Haskell.mp4"+ , expect $ GetFileStatus "/a/b/c/Haskell.mp4" |->+ Right (FileStatus 11 False 40000)+ , expect $ DoesPathExist "/z/Haskell.mp4" |-> Right False+ , expect $ CopyFile "/a/b/c/Haskell.mp4" "/z/Haskell.mp4" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ CopyFile "/a/b/python.mp4" "/z/python.mp4" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ CreateDirectory "/z/one" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ ListDirectory "/a/b/one" |->+ Right ["dos.mp3", "Uno.mp3", "tres.mp3"]+ , expect $ GetFileStatus "/a/b/one/dos.mp3" |->+ Right (FileStatus 11 False 2000)+ , expect $ GetFileStatus "/a/b/one/Uno.mp3" |->+ Right (FileStatus 11 False 1000)+ , expect $ GetFileStatus "/a/b/one/tres.mp3" |->+ Right (FileStatus 11 False 3000)+ , expect $ CopyFile "/a/b/one/Uno.mp3" "/z/one/Uno.mp3" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ CopyFile "/a/b/one/dos.mp3" "/z/one/dos.mp3" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ CopyFile "/a/b/one/tres.mp3" "/z/one/tres.mp3" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ CreateDirectory "/z/two" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ ListDirectory "/a/b/two" |-> Right []+ ]+ assertSuccess <=< Error.run $ run sourcesOptions+ { optFirst = FirstFiles+ }++------------------------------------------------------------------------------++testSourcesFirstFilesScript :: TestTree+testSourcesFirstFilesScript =+ testCase "SourcesFirstFilesScript" . runMockT $ do+ inSequence+ [ expect $ MakeAbsolute "/z" |-> Right "/z"+ , expect $ GetFileStatus "/z" |-> Right (FileStatus 10 True 90000)+ , expect $ MakeAbsolute "python.mp4" |-> Right "/a/b/python.mp4"+ , expect $ GetFileStatus "/a/b/python.mp4" |->+ Right (FileStatus 11 False 30000)+ , expect $ DoesPathExist "/z/python.mp4" |-> Right False+ , expect $ MakeAbsolute "/a/b/two" |-> Right "/a/b/two"+ , expect $ GetFileStatus "/a/b/two" |->+ Right (FileStatus 11 True 20000)+ , expect $ DoesPathExist "/z/two" |-> Right False+ , expect $ MakeAbsolute "one" |-> Right "/a/b/one"+ , expect $ GetFileStatus "/a/b/one" |->+ Right (FileStatus 11 True 10000)+ , expect $ DoesPathExist "/z/one" |-> Right False+ , expect $ MakeAbsolute "c/Haskell.mp4" |-> Right "/a/b/c/Haskell.mp4"+ , expect $ GetFileStatus "/a/b/c/Haskell.mp4" |->+ Right (FileStatus 11 False 40000)+ , expect $ DoesPathExist "/z/Haskell.mp4" |-> Right False+ , expect $ PutStrLn "cp /a/b/c/Haskell.mp4 /z/Haskell.mp4" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "cp /a/b/python.mp4 /z/python.mp4" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "mkdir /z/one" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ ListDirectory "/a/b/one" |->+ Right ["dos.mp3", "Uno.mp3", "tres.mp3"]+ , expect $ GetFileStatus "/a/b/one/dos.mp3" |->+ Right (FileStatus 11 False 2000)+ , expect $ GetFileStatus "/a/b/one/Uno.mp3" |->+ Right (FileStatus 11 False 1000)+ , expect $ GetFileStatus "/a/b/one/tres.mp3" |->+ Right (FileStatus 11 False 3000)+ , expect $ PutStrLn "cp /a/b/one/Uno.mp3 /z/one/Uno.mp3" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "cp /a/b/one/dos.mp3 /z/one/dos.mp3" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "cp /a/b/one/tres.mp3 /z/one/tres.mp3" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "mkdir /z/two" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ ListDirectory "/a/b/two" |-> Right []+ ]+ assertSuccess <=< Error.run $ run sourcesOptions+ { optFirst = FirstFiles+ , optScript = True+ }++------------------------------------------------------------------------------++testNoSync :: TestTree+testNoSync = testCase "NoSync" . runMockT $ do+ inSequence+ [ expect $ MakeAbsolute "/z" |-> Right "/z"+ , expect $ GetFileStatus "/z" |-> Right (FileStatus 10 True 90000)+ , expect $ MakeAbsolute "one" |-> Right "/a/b/one"+ , expect $ GetFileStatus "/a/b/one" |-> Right (FileStatus 11 True 10000)+ , expect $ DoesPathExist "/z/one" |-> Right False+ , expect $ CreateDirectory "/z/one" |-> Right ()+ , expect $ ListDirectory "/a/b/one" |->+ Right ["dos.mp3", "Uno.mp3", "tres.mp3"]+ , expect $ GetFileStatus "/a/b/one/dos.mp3" |->+ Right (FileStatus 11 False 2000)+ , expect $ GetFileStatus "/a/b/one/Uno.mp3" |->+ Right (FileStatus 11 False 1000)+ , expect $ GetFileStatus "/a/b/one/tres.mp3" |->+ Right (FileStatus 11 False 3000)+ , expect $ CopyFile "/a/b/one/Uno.mp3" "/z/one/Uno.mp3" |-> Right ()+ , expect $ CopyFile "/a/b/one/dos.mp3" "/z/one/dos.mp3" |-> Right ()+ , expect $ CopyFile "/a/b/one/tres.mp3" "/z/one/tres.mp3" |-> Right ()+ ]+ assertSuccess <=< Error.run $ run defaultOptions+ { optSync = False+ }++------------------------------------------------------------------------------++testNoSyncScript :: TestTree+testNoSyncScript = testCase "NoSyncScript" . runMockT $ do+ inSequence+ [ expect $ MakeAbsolute "/z" |-> Right "/z"+ , expect $ GetFileStatus "/z" |-> Right (FileStatus 10 True 90000)+ , expect $ MakeAbsolute "one" |-> Right "/a/b/one"+ , expect $ GetFileStatus "/a/b/one" |-> Right (FileStatus 11 True 10000)+ , expect $ DoesPathExist "/z/one" |-> Right False+ , expect $ PutStrLn "mkdir /z/one" |-> ()+ , expect $ ListDirectory "/a/b/one" |->+ Right ["dos.mp3", "Uno.mp3", "tres.mp3"]+ , expect $ GetFileStatus "/a/b/one/dos.mp3" |->+ Right (FileStatus 11 False 2000)+ , expect $ GetFileStatus "/a/b/one/Uno.mp3" |->+ Right (FileStatus 11 False 1000)+ , expect $ GetFileStatus "/a/b/one/tres.mp3" |->+ Right (FileStatus 11 False 3000)+ , expect $ PutStrLn "cp /a/b/one/Uno.mp3 /z/one/Uno.mp3" |-> ()+ , expect $ PutStrLn "cp /a/b/one/dos.mp3 /z/one/dos.mp3" |-> ()+ , expect $ PutStrLn "cp /a/b/one/tres.mp3 /z/one/tres.mp3" |-> ()+ ]+ assertSuccess <=< Error.run $ run defaultOptions+ { optSync = False+ , optScript = True+ }++------------------------------------------------------------------------------++testOrderNameReverse :: TestTree+testOrderNameReverse = testCase "OrderNameReverse" . runMockT $ do+ inSequence+ [ expect $ MakeAbsolute "/z" |-> Right "/z"+ , expect $ GetFileStatus "/z" |-> Right (FileStatus 10 True 90000)+ , expect $ MakeAbsolute "one" |-> Right "/a/b/one"+ , expect $ GetFileStatus "/a/b/one" |-> Right (FileStatus 11 True 10000)+ , expect $ DoesPathExist "/z/one" |-> Right False+ , expect $ CreateDirectory "/z/one" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ ListDirectory "/a/b/one" |->+ Right ["dos.mp3", "three", "Uno.mp3", "two", "tres.mp3"]+ , expect $ GetFileStatus "/a/b/one/dos.mp3" |->+ Right (FileStatus 11 False 2000)+ , expect $ GetFileStatus "/a/b/one/three" |->+ Right (FileStatus 11 True 3000)+ , expect $ GetFileStatus "/a/b/one/Uno.mp3" |->+ Right (FileStatus 11 False 1000)+ , expect $ GetFileStatus "/a/b/one/two" |->+ Right (FileStatus 11 True 2000)+ , expect $ GetFileStatus "/a/b/one/tres.mp3" |->+ Right (FileStatus 11 False 3000)+ , expect $ CreateDirectory "/z/one/two" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ ListDirectory "/a/b/one/two" |-> Right []+ , expect $ CreateDirectory "/z/one/three" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ ListDirectory "/a/b/one/three" |-> Right []+ , expect $ CopyFile "/a/b/one/tres.mp3" "/z/one/tres.mp3" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ CopyFile "/a/b/one/dos.mp3" "/z/one/dos.mp3" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ CopyFile "/a/b/one/Uno.mp3" "/z/one/Uno.mp3" |-> Right ()+ , expect $ Sync |-> ()+ ]+ assertSuccess <=< Error.run $ run defaultOptions+ { optFirst = FirstDirs+ , optReverse = True+ }++------------------------------------------------------------------------------++testOrderNameReverseScript :: TestTree+testOrderNameReverseScript = testCase "OrderNameReverseScript" . runMockT $ do+ inSequence+ [ expect $ MakeAbsolute "/z" |-> Right "/z"+ , expect $ GetFileStatus "/z" |-> Right (FileStatus 10 True 90000)+ , expect $ MakeAbsolute "one" |-> Right "/a/b/one"+ , expect $ GetFileStatus "/a/b/one" |-> Right (FileStatus 11 True 10000)+ , expect $ DoesPathExist "/z/one" |-> Right False+ , expect $ PutStrLn "mkdir /z/one" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ ListDirectory "/a/b/one" |->+ Right ["dos.mp3", "three", "Uno.mp3", "two", "tres.mp3"]+ , expect $ GetFileStatus "/a/b/one/dos.mp3" |->+ Right (FileStatus 11 False 2000)+ , expect $ GetFileStatus "/a/b/one/three" |->+ Right (FileStatus 11 True 3000)+ , expect $ GetFileStatus "/a/b/one/Uno.mp3" |->+ Right (FileStatus 11 False 1000)+ , expect $ GetFileStatus "/a/b/one/two" |->+ Right (FileStatus 11 True 2000)+ , expect $ GetFileStatus "/a/b/one/tres.mp3" |->+ Right (FileStatus 11 False 3000)+ , expect $ PutStrLn "mkdir /z/one/two" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ ListDirectory "/a/b/one/two" |-> Right []+ , expect $ PutStrLn "mkdir /z/one/three" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ ListDirectory "/a/b/one/three" |-> Right []+ , expect $ PutStrLn "cp /a/b/one/tres.mp3 /z/one/tres.mp3" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "cp /a/b/one/dos.mp3 /z/one/dos.mp3" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "cp /a/b/one/Uno.mp3 /z/one/Uno.mp3" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ ]+ assertSuccess <=< Error.run $ run defaultOptions+ { optFirst = FirstDirs+ , optReverse = True+ , optScript = True+ }++------------------------------------------------------------------------------++testSourcesOrderNameReverse :: TestTree+testSourcesOrderNameReverse =+ testCase "SourcesOrderNameReverse" . runMockT $ do+ inSequence+ [ expect $ MakeAbsolute "/z" |-> Right "/z"+ , expect $ GetFileStatus "/z" |-> Right (FileStatus 10 True 90000)+ , expect $ MakeAbsolute "python.mp4" |-> Right "/a/b/python.mp4"+ , expect $ GetFileStatus "/a/b/python.mp4" |->+ Right (FileStatus 11 False 30000)+ , expect $ DoesPathExist "/z/python.mp4" |-> Right False+ , expect $ MakeAbsolute "/a/b/two" |-> Right "/a/b/two"+ , expect $ GetFileStatus "/a/b/two" |->+ Right (FileStatus 11 True 20000)+ , expect $ DoesPathExist "/z/two" |-> Right False+ , expect $ MakeAbsolute "one" |-> Right "/a/b/one"+ , expect $ GetFileStatus "/a/b/one" |->+ Right (FileStatus 11 True 10000)+ , expect $ DoesPathExist "/z/one" |-> Right False+ , expect $ MakeAbsolute "c/Haskell.mp4" |-> Right "/a/b/c/Haskell.mp4"+ , expect $ GetFileStatus "/a/b/c/Haskell.mp4" |->+ Right (FileStatus 11 False 40000)+ , expect $ DoesPathExist "/z/Haskell.mp4" |-> Right False+ , expect $ CreateDirectory "/z/two" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ ListDirectory "/a/b/two" |-> Right []+ , expect $ CreateDirectory "/z/one" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ ListDirectory "/a/b/one" |->+ Right ["dos.mp3", "Uno.mp3", "tres.mp3"]+ , expect $ GetFileStatus "/a/b/one/dos.mp3" |->+ Right (FileStatus 11 False 2000)+ , expect $ GetFileStatus "/a/b/one/Uno.mp3" |->+ Right (FileStatus 11 False 1000)+ , expect $ GetFileStatus "/a/b/one/tres.mp3" |->+ Right (FileStatus 11 False 3000)+ , expect $ CopyFile "/a/b/one/tres.mp3" "/z/one/tres.mp3" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ CopyFile "/a/b/one/dos.mp3" "/z/one/dos.mp3" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ CopyFile "/a/b/one/Uno.mp3" "/z/one/Uno.mp3" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ CopyFile "/a/b/python.mp4" "/z/python.mp4" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ CopyFile "/a/b/c/Haskell.mp4" "/z/Haskell.mp4" |-> Right ()+ , expect $ Sync |-> ()+ ]+ assertSuccess <=< Error.run $ run sourcesOptions+ { optFirst = FirstDirs+ , optReverse = True+ }++------------------------------------------------------------------------------++testSourcesOrderNameReverseScript :: TestTree+testSourcesOrderNameReverseScript =+ testCase "SourcesOrderNameReverseScript" . runMockT $ do+ inSequence+ [ expect $ MakeAbsolute "/z" |-> Right "/z"+ , expect $ GetFileStatus "/z" |-> Right (FileStatus 10 True 90000)+ , expect $ MakeAbsolute "python.mp4" |-> Right "/a/b/python.mp4"+ , expect $ GetFileStatus "/a/b/python.mp4" |->+ Right (FileStatus 11 False 30000)+ , expect $ DoesPathExist "/z/python.mp4" |-> Right False+ , expect $ MakeAbsolute "/a/b/two" |-> Right "/a/b/two"+ , expect $ GetFileStatus "/a/b/two" |->+ Right (FileStatus 11 True 20000)+ , expect $ DoesPathExist "/z/two" |-> Right False+ , expect $ MakeAbsolute "one" |-> Right "/a/b/one"+ , expect $ GetFileStatus "/a/b/one" |->+ Right (FileStatus 11 True 10000)+ , expect $ DoesPathExist "/z/one" |-> Right False+ , expect $ MakeAbsolute "c/Haskell.mp4" |-> Right "/a/b/c/Haskell.mp4"+ , expect $ GetFileStatus "/a/b/c/Haskell.mp4" |->+ Right (FileStatus 11 False 40000)+ , expect $ DoesPathExist "/z/Haskell.mp4" |-> Right False+ , expect $ PutStrLn "mkdir /z/two" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ ListDirectory "/a/b/two" |-> Right []+ , expect $ PutStrLn "mkdir /z/one" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ ListDirectory "/a/b/one" |->+ Right ["dos.mp3", "Uno.mp3", "tres.mp3"]+ , expect $ GetFileStatus "/a/b/one/dos.mp3" |->+ Right (FileStatus 11 False 2000)+ , expect $ GetFileStatus "/a/b/one/Uno.mp3" |->+ Right (FileStatus 11 False 1000)+ , expect $ GetFileStatus "/a/b/one/tres.mp3" |->+ Right (FileStatus 11 False 3000)+ , expect $ PutStrLn "cp /a/b/one/tres.mp3 /z/one/tres.mp3" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "cp /a/b/one/dos.mp3 /z/one/dos.mp3" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "cp /a/b/one/Uno.mp3 /z/one/Uno.mp3" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "cp /a/b/python.mp4 /z/python.mp4" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "cp /a/b/c/Haskell.mp4 /z/Haskell.mp4" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ ]+ assertSuccess <=< Error.run $ run sourcesOptions+ { optFirst = FirstDirs+ , optReverse = True+ , optScript = True+ }++------------------------------------------------------------------------------++testOrderTime :: TestTree+testOrderTime = testCase "OrderTime" . runMockT $ do+ inSequence+ [ expect $ MakeAbsolute "/z" |-> Right "/z"+ , expect $ GetFileStatus "/z" |-> Right (FileStatus 10 True 90000)+ , expect $ MakeAbsolute "one" |-> Right "/a/b/one"+ , expect $ GetFileStatus "/a/b/one" |-> Right (FileStatus 11 True 10000)+ , expect $ DoesPathExist "/z/one" |-> Right False+ , expect $ CreateDirectory "/z/one" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ ListDirectory "/a/b/one" |->+ Right ["dos.mp3", "three", "Uno.mp3", "two", "tres.mp3"]+ , expect $ GetFileStatus "/a/b/one/dos.mp3" |->+ Right (FileStatus 11 False 20000)+ , expect $ GetFileStatus "/a/b/one/three" |->+ Right (FileStatus 11 True 3000)+ , expect $ GetFileStatus "/a/b/one/Uno.mp3" |->+ Right (FileStatus 11 False 10000)+ , expect $ GetFileStatus "/a/b/one/two" |->+ Right (FileStatus 11 True 2000)+ , expect $ GetFileStatus "/a/b/one/tres.mp3" |->+ Right (FileStatus 11 False 3000)+ , expect $ CreateDirectory "/z/one/two" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ ListDirectory "/a/b/one/two" |-> Right []+ , expect $ CreateDirectory "/z/one/three" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ ListDirectory "/a/b/one/three" |-> Right []+ , expect $ CopyFile "/a/b/one/tres.mp3" "/z/one/tres.mp3" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ CopyFile "/a/b/one/Uno.mp3" "/z/one/Uno.mp3" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ CopyFile "/a/b/one/dos.mp3" "/z/one/dos.mp3" |-> Right ()+ , expect $ Sync |-> ()+ ]+ assertSuccess <=< Error.run $ run defaultOptions+ { optFirst = FirstDirs+ , optOrder = OrderTime+ }++------------------------------------------------------------------------------++testOrderTimeScript :: TestTree+testOrderTimeScript = testCase "OrderTimeScript" . runMockT $ do+ inSequence+ [ expect $ MakeAbsolute "/z" |-> Right "/z"+ , expect $ GetFileStatus "/z" |-> Right (FileStatus 10 True 90000)+ , expect $ MakeAbsolute "one" |-> Right "/a/b/one"+ , expect $ GetFileStatus "/a/b/one" |-> Right (FileStatus 11 True 10000)+ , expect $ DoesPathExist "/z/one" |-> Right False+ , expect $ PutStrLn "mkdir /z/one" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ ListDirectory "/a/b/one" |->+ Right ["dos.mp3", "three", "Uno.mp3", "two", "tres.mp3"]+ , expect $ GetFileStatus "/a/b/one/dos.mp3" |->+ Right (FileStatus 11 False 20000)+ , expect $ GetFileStatus "/a/b/one/three" |->+ Right (FileStatus 11 True 3000)+ , expect $ GetFileStatus "/a/b/one/Uno.mp3" |->+ Right (FileStatus 11 False 10000)+ , expect $ GetFileStatus "/a/b/one/two" |->+ Right (FileStatus 11 True 2000)+ , expect $ GetFileStatus "/a/b/one/tres.mp3" |->+ Right (FileStatus 11 False 3000)+ , expect $ PutStrLn "mkdir /z/one/two" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ ListDirectory "/a/b/one/two" |-> Right []+ , expect $ PutStrLn "mkdir /z/one/three" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ ListDirectory "/a/b/one/three" |-> Right []+ , expect $ PutStrLn "cp /a/b/one/tres.mp3 /z/one/tres.mp3" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "cp /a/b/one/Uno.mp3 /z/one/Uno.mp3" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "cp /a/b/one/dos.mp3 /z/one/dos.mp3" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ ]+ assertSuccess <=< Error.run $ run defaultOptions+ { optFirst = FirstDirs+ , optOrder = OrderTime+ , optScript = True+ }++------------------------------------------------------------------------------++testSourcesOrderTime :: TestTree+testSourcesOrderTime = testCase "SourcesOrderTime" . runMockT $ do+ inSequence+ [ expect $ MakeAbsolute "/z" |-> Right "/z"+ , expect $ GetFileStatus "/z" |-> Right (FileStatus 10 True 90000)+ , expect $ MakeAbsolute "python.mp4" |-> Right "/a/b/python.mp4"+ , expect $ GetFileStatus "/a/b/python.mp4" |->+ Right (FileStatus 11 False 30000)+ , expect $ DoesPathExist "/z/python.mp4" |-> Right False+ , expect $ MakeAbsolute "/a/b/two" |-> Right "/a/b/two"+ , expect $ GetFileStatus "/a/b/two" |-> Right (FileStatus 11 True 32000)+ , expect $ DoesPathExist "/z/two" |-> Right False+ , expect $ MakeAbsolute "one" |-> Right "/a/b/one"+ , expect $ GetFileStatus "/a/b/one" |-> Right (FileStatus 11 True 33000)+ , expect $ DoesPathExist "/z/one" |-> Right False+ , expect $ MakeAbsolute "c/Haskell.mp4" |-> Right "/a/b/c/Haskell.mp4"+ , expect $ GetFileStatus "/a/b/c/Haskell.mp4" |->+ Right (FileStatus 11 False 40000)+ , expect $ DoesPathExist "/z/Haskell.mp4" |-> Right False+ , expect $ CreateDirectory "/z/two" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ ListDirectory "/a/b/two" |-> Right []+ , expect $ CreateDirectory "/z/one" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ ListDirectory "/a/b/one" |->+ Right ["dos.mp3", "Uno.mp3", "tres.mp3"]+ , expect $ GetFileStatus "/a/b/one/dos.mp3" |->+ Right (FileStatus 11 False 20000)+ , expect $ GetFileStatus "/a/b/one/Uno.mp3" |->+ Right (FileStatus 11 False 10000)+ , expect $ GetFileStatus "/a/b/one/tres.mp3" |->+ Right (FileStatus 11 False 3000)+ , expect $ CopyFile "/a/b/one/tres.mp3" "/z/one/tres.mp3" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ CopyFile "/a/b/one/Uno.mp3" "/z/one/Uno.mp3" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ CopyFile "/a/b/one/dos.mp3" "/z/one/dos.mp3" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ CopyFile "/a/b/python.mp4" "/z/python.mp4" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ CopyFile "/a/b/c/Haskell.mp4" "/z/Haskell.mp4" |-> Right ()+ , expect $ Sync |-> ()+ ]+ assertSuccess <=< Error.run $ run sourcesOptions+ { optFirst = FirstDirs+ , optOrder = OrderTime+ }++------------------------------------------------------------------------------++testSourcesOrderTimeScript :: TestTree+testSourcesOrderTimeScript = testCase "SourcesOrderTimeScript" . runMockT $ do+ inSequence+ [ expect $ MakeAbsolute "/z" |-> Right "/z"+ , expect $ GetFileStatus "/z" |-> Right (FileStatus 10 True 90000)+ , expect $ MakeAbsolute "python.mp4" |-> Right "/a/b/python.mp4"+ , expect $ GetFileStatus "/a/b/python.mp4" |->+ Right (FileStatus 11 False 30000)+ , expect $ DoesPathExist "/z/python.mp4" |-> Right False+ , expect $ MakeAbsolute "/a/b/two" |-> Right "/a/b/two"+ , expect $ GetFileStatus "/a/b/two" |-> Right (FileStatus 11 True 32000)+ , expect $ DoesPathExist "/z/two" |-> Right False+ , expect $ MakeAbsolute "one" |-> Right "/a/b/one"+ , expect $ GetFileStatus "/a/b/one" |-> Right (FileStatus 11 True 33000)+ , expect $ DoesPathExist "/z/one" |-> Right False+ , expect $ MakeAbsolute "c/Haskell.mp4" |-> Right "/a/b/c/Haskell.mp4"+ , expect $ GetFileStatus "/a/b/c/Haskell.mp4" |->+ Right (FileStatus 11 False 40000)+ , expect $ DoesPathExist "/z/Haskell.mp4" |-> Right False+ , expect $ PutStrLn "mkdir /z/two" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ ListDirectory "/a/b/two" |-> Right []+ , expect $ PutStrLn "mkdir /z/one" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ ListDirectory "/a/b/one" |->+ Right ["dos.mp3", "Uno.mp3", "tres.mp3"]+ , expect $ GetFileStatus "/a/b/one/dos.mp3" |->+ Right (FileStatus 11 False 20000)+ , expect $ GetFileStatus "/a/b/one/Uno.mp3" |->+ Right (FileStatus 11 False 10000)+ , expect $ GetFileStatus "/a/b/one/tres.mp3" |->+ Right (FileStatus 11 False 3000)+ , expect $ PutStrLn "cp /a/b/one/tres.mp3 /z/one/tres.mp3" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "cp /a/b/one/Uno.mp3 /z/one/Uno.mp3" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "cp /a/b/one/dos.mp3 /z/one/dos.mp3" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "cp /a/b/python.mp4 /z/python.mp4" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "cp /a/b/c/Haskell.mp4 /z/Haskell.mp4" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ ]+ assertSuccess <=< Error.run $ run sourcesOptions+ { optFirst = FirstDirs+ , optOrder = OrderTime+ , optScript = True+ }++------------------------------------------------------------------------------++testOrderTimeReverse :: TestTree+testOrderTimeReverse = testCase "OrderTimeReverse" . runMockT $ do+ inSequence+ [ expect $ MakeAbsolute "/z" |-> Right "/z"+ , expect $ GetFileStatus "/z" |-> Right (FileStatus 10 True 90000)+ , expect $ MakeAbsolute "one" |-> Right "/a/b/one"+ , expect $ GetFileStatus "/a/b/one" |-> Right (FileStatus 11 True 10000)+ , expect $ DoesPathExist "/z/one" |-> Right False+ , expect $ CreateDirectory "/z/one" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ ListDirectory "/a/b/one" |->+ Right ["dos.mp3", "three", "Uno.mp3", "two", "tres.mp3"]+ , expect $ GetFileStatus "/a/b/one/dos.mp3" |->+ Right (FileStatus 11 False 20000)+ , expect $ GetFileStatus "/a/b/one/three" |->+ Right (FileStatus 11 True 3000)+ , expect $ GetFileStatus "/a/b/one/Uno.mp3" |->+ Right (FileStatus 11 False 10000)+ , expect $ GetFileStatus "/a/b/one/two" |->+ Right (FileStatus 11 True 2000)+ , expect $ GetFileStatus "/a/b/one/tres.mp3" |->+ Right (FileStatus 11 False 3000)+ , expect $ CreateDirectory "/z/one/three" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ ListDirectory "/a/b/one/three" |-> Right []+ , expect $ CreateDirectory "/z/one/two" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ ListDirectory "/a/b/one/two" |-> Right []+ , expect $ CopyFile "/a/b/one/dos.mp3" "/z/one/dos.mp3" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ CopyFile "/a/b/one/Uno.mp3" "/z/one/Uno.mp3" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ CopyFile "/a/b/one/tres.mp3" "/z/one/tres.mp3" |-> Right ()+ , expect $ Sync |-> ()+ ]+ assertSuccess <=< Error.run $ run defaultOptions+ { optFirst = FirstDirs+ , optOrder = OrderTime+ , optReverse = True+ }++------------------------------------------------------------------------------++testOrderTimeReverseScript :: TestTree+testOrderTimeReverseScript = testCase "OrderTimeReverseScript" . runMockT $ do+ inSequence+ [ expect $ MakeAbsolute "/z" |-> Right "/z"+ , expect $ GetFileStatus "/z" |-> Right (FileStatus 10 True 90000)+ , expect $ MakeAbsolute "one" |-> Right "/a/b/one"+ , expect $ GetFileStatus "/a/b/one" |-> Right (FileStatus 11 True 10000)+ , expect $ DoesPathExist "/z/one" |-> Right False+ , expect $ PutStrLn "mkdir /z/one" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ ListDirectory "/a/b/one" |->+ Right ["dos.mp3", "three", "Uno.mp3", "two", "tres.mp3"]+ , expect $ GetFileStatus "/a/b/one/dos.mp3" |->+ Right (FileStatus 11 False 20000)+ , expect $ GetFileStatus "/a/b/one/three" |->+ Right (FileStatus 11 True 3000)+ , expect $ GetFileStatus "/a/b/one/Uno.mp3" |->+ Right (FileStatus 11 False 10000)+ , expect $ GetFileStatus "/a/b/one/two" |->+ Right (FileStatus 11 True 2000)+ , expect $ GetFileStatus "/a/b/one/tres.mp3" |->+ Right (FileStatus 11 False 3000)+ , expect $ PutStrLn "mkdir /z/one/three" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ ListDirectory "/a/b/one/three" |-> Right []+ , expect $ PutStrLn "mkdir /z/one/two" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ ListDirectory "/a/b/one/two" |-> Right []+ , expect $ PutStrLn "cp /a/b/one/dos.mp3 /z/one/dos.mp3" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "cp /a/b/one/Uno.mp3 /z/one/Uno.mp3" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "cp /a/b/one/tres.mp3 /z/one/tres.mp3" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ ]+ assertSuccess <=< Error.run $ run defaultOptions+ { optFirst = FirstDirs+ , optOrder = OrderTime+ , optReverse = True+ , optScript = True+ }++------------------------------------------------------------------------------++testSourcesOrderTimeReverse :: TestTree+testSourcesOrderTimeReverse =+ testCase "SourcesOrderTimeReverse" . runMockT $ do+ inSequence+ [ expect $ MakeAbsolute "/z" |-> Right "/z"+ , expect $ GetFileStatus "/z" |-> Right (FileStatus 10 True 90000)+ , expect $ MakeAbsolute "python.mp4" |-> Right "/a/b/python.mp4"+ , expect $ GetFileStatus "/a/b/python.mp4" |->+ Right (FileStatus 11 False 30000)+ , expect $ DoesPathExist "/z/python.mp4" |-> Right False+ , expect $ MakeAbsolute "/a/b/two" |-> Right "/a/b/two"+ , expect $ GetFileStatus "/a/b/two" |->+ Right (FileStatus 11 True 32000)+ , expect $ DoesPathExist "/z/two" |-> Right False+ , expect $ MakeAbsolute "one" |-> Right "/a/b/one"+ , expect $ GetFileStatus "/a/b/one" |->+ Right (FileStatus 11 True 33000)+ , expect $ DoesPathExist "/z/one" |-> Right False+ , expect $ MakeAbsolute "c/Haskell.mp4" |-> Right "/a/b/c/Haskell.mp4"+ , expect $ GetFileStatus "/a/b/c/Haskell.mp4" |->+ Right (FileStatus 11 False 40000)+ , expect $ DoesPathExist "/z/Haskell.mp4" |-> Right False+ , expect $ CreateDirectory "/z/one" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ ListDirectory "/a/b/one" |->+ Right ["dos.mp3", "Uno.mp3", "tres.mp3"]+ , expect $ GetFileStatus "/a/b/one/dos.mp3" |->+ Right (FileStatus 11 False 20000)+ , expect $ GetFileStatus "/a/b/one/Uno.mp3" |->+ Right (FileStatus 11 False 10000)+ , expect $ GetFileStatus "/a/b/one/tres.mp3" |->+ Right (FileStatus 11 False 3000)+ , expect $ CopyFile "/a/b/one/dos.mp3" "/z/one/dos.mp3" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ CopyFile "/a/b/one/Uno.mp3" "/z/one/Uno.mp3" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ CopyFile "/a/b/one/tres.mp3" "/z/one/tres.mp3" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ CreateDirectory "/z/two" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ ListDirectory "/a/b/two" |-> Right []+ , expect $ CopyFile "/a/b/c/Haskell.mp4" "/z/Haskell.mp4" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ CopyFile "/a/b/python.mp4" "/z/python.mp4" |-> Right ()+ , expect $ Sync |-> ()+ ]+ assertSuccess <=< Error.run $ run sourcesOptions+ { optFirst = FirstDirs+ , optOrder = OrderTime+ , optReverse = True+ }++------------------------------------------------------------------------------++testSourcesOrderTimeReverseScript :: TestTree+testSourcesOrderTimeReverseScript =+ testCase "SourcesOrderTimeReverseScript" . runMockT $ do+ inSequence+ [ expect $ MakeAbsolute "/z" |-> Right "/z"+ , expect $ GetFileStatus "/z" |-> Right (FileStatus 10 True 90000)+ , expect $ MakeAbsolute "python.mp4" |-> Right "/a/b/python.mp4"+ , expect $ GetFileStatus "/a/b/python.mp4" |->+ Right (FileStatus 11 False 30000)+ , expect $ DoesPathExist "/z/python.mp4" |-> Right False+ , expect $ MakeAbsolute "/a/b/two" |-> Right "/a/b/two"+ , expect $ GetFileStatus "/a/b/two" |->+ Right (FileStatus 11 True 32000)+ , expect $ DoesPathExist "/z/two" |-> Right False+ , expect $ MakeAbsolute "one" |-> Right "/a/b/one"+ , expect $ GetFileStatus "/a/b/one" |->+ Right (FileStatus 11 True 33000)+ , expect $ DoesPathExist "/z/one" |-> Right False+ , expect $ MakeAbsolute "c/Haskell.mp4" |-> Right "/a/b/c/Haskell.mp4"+ , expect $ GetFileStatus "/a/b/c/Haskell.mp4" |->+ Right (FileStatus 11 False 40000)+ , expect $ DoesPathExist "/z/Haskell.mp4" |-> Right False+ , expect $ PutStrLn "mkdir /z/one" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ ListDirectory "/a/b/one" |->+ Right ["dos.mp3", "Uno.mp3", "tres.mp3"]+ , expect $ GetFileStatus "/a/b/one/dos.mp3" |->+ Right (FileStatus 11 False 20000)+ , expect $ GetFileStatus "/a/b/one/Uno.mp3" |->+ Right (FileStatus 11 False 10000)+ , expect $ GetFileStatus "/a/b/one/tres.mp3" |->+ Right (FileStatus 11 False 3000)+ , expect $ PutStrLn "cp /a/b/one/dos.mp3 /z/one/dos.mp3" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "cp /a/b/one/Uno.mp3 /z/one/Uno.mp3" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "cp /a/b/one/tres.mp3 /z/one/tres.mp3" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "mkdir /z/two" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ ListDirectory "/a/b/two" |-> Right []+ , expect $ PutStrLn "cp /a/b/c/Haskell.mp4 /z/Haskell.mp4" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "cp /a/b/python.mp4 /z/python.mp4" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ ]+ assertSuccess <=< Error.run $ run sourcesOptions+ { optFirst = FirstDirs+ , optOrder = OrderTime+ , optReverse = True+ , optScript = True+ }++------------------------------------------------------------------------------++testOrderRandom :: TestTree+testOrderRandom = testCase "OrderRandom" . runMockT $ do+ inSequence+ [ expect $ MakeAbsolute "/z" |-> Right "/z"+ , expect $ GetFileStatus "/z" |-> Right (FileStatus 10 True 90000)+ , expect $ MakeAbsolute "one" |-> Right "/a/b/one"+ , expect $ GetFileStatus "/a/b/one" |-> Right (FileStatus 11 True 10000)+ , expect $ DoesPathExist "/z/one" |-> Right False+ , expect $ CreateDirectory "/z/one" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ ListDirectory "/a/b/one" |->+ Right ["dos.mp3", "three", "Uno.mp3", "two", "tres.mp3"]+ , expect $ GetFileStatus "/a/b/one/dos.mp3" |->+ Right (FileStatus 11 False 2000)+ , expect $ GetFileStatus "/a/b/one/three" |->+ Right (FileStatus 11 True 3000)+ , expect $ GetFileStatus "/a/b/one/Uno.mp3" |->+ Right (FileStatus 11 False 1000)+ , expect $ GetFileStatus "/a/b/one/two" |->+ Right (FileStatus 11 True 2000)+ , expect $ GetFileStatus "/a/b/one/tres.mp3" |->+ Right (FileStatus 11 False 3000)+ , inAnyOrder+ [ inSequence+ [ expect $ CreateDirectory "/z/one/three" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ ListDirectory "/a/b/one/three" |-> Right []+ ]+ , inSequence+ [ expect $ CreateDirectory "/z/one/two" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ ListDirectory "/a/b/one/two" |-> Right []+ ]+ ]+ , inAnyOrder+ [ inSequence+ [ expect $ CopyFile "/a/b/one/Uno.mp3" "/z/one/Uno.mp3" |->+ Right ()+ , expect $ Sync |-> ()+ ]+ , inSequence+ [ expect $ CopyFile "/a/b/one/dos.mp3" "/z/one/dos.mp3" |->+ Right ()+ , expect $ Sync |-> ()+ ]+ , inSequence+ [ expect $ CopyFile "/a/b/one/tres.mp3" "/z/one/tres.mp3" |->+ Right ()+ , expect $ Sync |-> ()+ ]+ ]+ ]+ assertSuccess <=< Error.run $ run defaultOptions+ { optFirst = FirstDirs+ , optOrder = OrderRandom+ }++------------------------------------------------------------------------------++testOrderRandomScript :: TestTree+testOrderRandomScript = testCase "OrderRandomScript" . runMockT $ do+ inSequence+ [ expect $ MakeAbsolute "/z" |-> Right "/z"+ , expect $ GetFileStatus "/z" |-> Right (FileStatus 10 True 90000)+ , expect $ MakeAbsolute "one" |-> Right "/a/b/one"+ , expect $ GetFileStatus "/a/b/one" |-> Right (FileStatus 11 True 10000)+ , expect $ DoesPathExist "/z/one" |-> Right False+ , expect $ PutStrLn "mkdir /z/one" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ ListDirectory "/a/b/one" |->+ Right ["dos.mp3", "three", "Uno.mp3", "two", "tres.mp3"]+ , expect $ GetFileStatus "/a/b/one/dos.mp3" |->+ Right (FileStatus 11 False 2000)+ , expect $ GetFileStatus "/a/b/one/three" |->+ Right (FileStatus 11 True 3000)+ , expect $ GetFileStatus "/a/b/one/Uno.mp3" |->+ Right (FileStatus 11 False 1000)+ , expect $ GetFileStatus "/a/b/one/two" |->+ Right (FileStatus 11 True 2000)+ , expect $ GetFileStatus "/a/b/one/tres.mp3" |->+ Right (FileStatus 11 False 3000)+ , inAnyOrder+ [ inSequence+ [ expect $ PutStrLn "mkdir /z/one/three" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ ListDirectory "/a/b/one/three" |-> Right []+ ]+ , inSequence+ [ expect $ PutStrLn "mkdir /z/one/two" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ ListDirectory "/a/b/one/two" |-> Right []+ ]+ ]+ , inAnyOrder+ [ inSequence+ [ expect $ PutStrLn "cp /a/b/one/Uno.mp3 /z/one/Uno.mp3" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ ]+ , inSequence+ [ expect $ PutStrLn "cp /a/b/one/dos.mp3 /z/one/dos.mp3" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ ]+ , inSequence+ [ expect $ PutStrLn "cp /a/b/one/tres.mp3 /z/one/tres.mp3" |->+ ()+ , expect $ PutStrLn "sync" |-> ()+ ]+ ]+ ]+ assertSuccess <=< Error.run $ run defaultOptions+ { optFirst = FirstDirs+ , optOrder = OrderRandom+ , optScript = True+ }++------------------------------------------------------------------------------++testSourcesOrderRandom :: TestTree+testSourcesOrderRandom = testCase "SourcesOrderRandom" . runMockT $ do+ inSequence+ [ expect $ MakeAbsolute "/z" |-> Right "/z"+ , expect $ GetFileStatus "/z" |-> Right (FileStatus 10 True 90000)+ , expect $ MakeAbsolute "python.mp4" |-> Right "/a/b/python.mp4"+ , expect $ GetFileStatus "/a/b/python.mp4" |->+ Right (FileStatus 11 False 30000)+ , expect $ DoesPathExist "/z/python.mp4" |-> Right False+ , expect $ MakeAbsolute "/a/b/two" |-> Right "/a/b/two"+ , expect $ GetFileStatus "/a/b/two" |-> Right (FileStatus 11 True 20000)+ , expect $ DoesPathExist "/z/two" |-> Right False+ , expect $ MakeAbsolute "one" |-> Right "/a/b/one"+ , expect $ GetFileStatus "/a/b/one" |-> Right (FileStatus 11 True 10000)+ , expect $ DoesPathExist "/z/one" |-> Right False+ , expect $ MakeAbsolute "c/Haskell.mp4" |-> Right "/a/b/c/Haskell.mp4"+ , expect $ GetFileStatus "/a/b/c/Haskell.mp4" |->+ Right (FileStatus 11 False 40000)+ , expect $ DoesPathExist "/z/Haskell.mp4" |-> Right False+ , inAnyOrder+ [ inSequence+ [ expect $ CreateDirectory "/z/one" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ ListDirectory "/a/b/one" |->+ Right ["dos.mp3", "Uno.mp3", "tres.mp3"]+ , expect $ GetFileStatus "/a/b/one/dos.mp3" |->+ Right (FileStatus 11 False 2000)+ , expect $ GetFileStatus "/a/b/one/Uno.mp3" |->+ Right (FileStatus 11 False 1000)+ , expect $ GetFileStatus "/a/b/one/tres.mp3" |->+ Right (FileStatus 11 False 3000)+ , inAnyOrder+ [ inSequence+ [ expect $+ CopyFile "/a/b/one/Uno.mp3" "/z/one/Uno.mp3" |->+ Right ()+ , expect $ Sync |-> ()+ ]+ , inSequence+ [ expect $+ CopyFile "/a/b/one/dos.mp3" "/z/one/dos.mp3" |->+ Right ()+ , expect $ Sync |-> ()+ ]+ , inSequence+ [ expect $+ CopyFile "/a/b/one/tres.mp3" "/z/one/tres.mp3" |->+ Right ()+ , expect $ Sync |-> ()+ ]+ ]+ ]+ , inSequence+ [ expect $ CreateDirectory "/z/two" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ ListDirectory "/a/b/two" |-> Right []+ ]+ ]+ , inAnyOrder+ [ inSequence+ [ expect $ CopyFile "/a/b/c/Haskell.mp4" "/z/Haskell.mp4" |->+ Right ()+ , expect $ Sync |-> ()+ ]+ , inSequence+ [ expect $ CopyFile "/a/b/python.mp4" "/z/python.mp4" |->+ Right ()+ , expect $ Sync |-> ()+ ]+ ]+ ]+ assertSuccess <=< Error.run $ run sourcesOptions+ { optFirst = FirstDirs+ , optOrder = OrderRandom+ }++------------------------------------------------------------------------------++testSourcesOrderRandomScript :: TestTree+testSourcesOrderRandomScript =+ testCase "SourcesOrderRandomScript" . runMockT $ do+ inSequence+ [ expect $ MakeAbsolute "/z" |-> Right "/z"+ , expect $ GetFileStatus "/z" |-> Right (FileStatus 10 True 90000)+ , expect $ MakeAbsolute "python.mp4" |-> Right "/a/b/python.mp4"+ , expect $ GetFileStatus "/a/b/python.mp4" |->+ Right (FileStatus 11 False 30000)+ , expect $ DoesPathExist "/z/python.mp4" |-> Right False+ , expect $ MakeAbsolute "/a/b/two" |-> Right "/a/b/two"+ , expect $ GetFileStatus "/a/b/two" |->+ Right (FileStatus 11 True 20000)+ , expect $ DoesPathExist "/z/two" |-> Right False+ , expect $ MakeAbsolute "one" |-> Right "/a/b/one"+ , expect $ GetFileStatus "/a/b/one" |->+ Right (FileStatus 11 True 10000)+ , expect $ DoesPathExist "/z/one" |-> Right False+ , expect $ MakeAbsolute "c/Haskell.mp4" |-> Right "/a/b/c/Haskell.mp4"+ , expect $ GetFileStatus "/a/b/c/Haskell.mp4" |->+ Right (FileStatus 11 False 40000)+ , expect $ DoesPathExist "/z/Haskell.mp4" |-> Right False+ , inAnyOrder+ [ inSequence+ [ expect $ PutStrLn "mkdir /z/one" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ ListDirectory "/a/b/one" |->+ Right ["dos.mp3", "Uno.mp3", "tres.mp3"]+ , expect $ GetFileStatus "/a/b/one/dos.mp3" |->+ Right (FileStatus 11 False 2000)+ , expect $ GetFileStatus "/a/b/one/Uno.mp3" |->+ Right (FileStatus 11 False 1000)+ , expect $ GetFileStatus "/a/b/one/tres.mp3" |->+ Right (FileStatus 11 False 3000)+ , inAnyOrder+ [ inSequence+ [ expect $+ PutStrLn "cp /a/b/one/Uno.mp3 /z/one/Uno.mp3" |->+ ()+ , expect $ PutStrLn "sync" |-> ()+ ]+ , inSequence+ [ expect $+ PutStrLn "cp /a/b/one/dos.mp3 /z/one/dos.mp3" |->+ ()+ , expect $ PutStrLn "sync" |-> ()+ ]+ , inSequence+ [ expect $+ PutStrLn "cp /a/b/one/tres.mp3 /z/one/tres.mp3"+ |-> ()+ , expect $ PutStrLn "sync" |-> ()+ ]+ ]+ ]+ , inSequence+ [ expect $ PutStrLn "mkdir /z/two" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ ListDirectory "/a/b/two" |-> Right []+ ]+ ]+ , inAnyOrder+ [ inSequence+ [ expect $ PutStrLn "cp /a/b/c/Haskell.mp4 /z/Haskell.mp4" |->+ ()+ , expect $ PutStrLn "sync" |-> ()+ ]+ , inSequence+ [ expect $ PutStrLn "cp /a/b/python.mp4 /z/python.mp4" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ ]+ ]+ ]+ assertSuccess <=< Error.run $ run sourcesOptions+ { optFirst = FirstDirs+ , optOrder = OrderRandom+ , optScript = True+ }++------------------------------------------------------------------------------++testVerbose :: TestTree+testVerbose = testCase "Verbose" . runMockT $ do+ inSequence+ [ expect $ MakeAbsolute "/z" |-> Right "/z"+ , expect $ GetFileStatus "/z" |-> Right (FileStatus 10 True 90000)+ , expect $ MakeAbsolute "one" |-> Right "/a/b/one"+ , expect $ GetFileStatus "/a/b/one" |-> Right (FileStatus 11 True 10000)+ , expect $ DoesPathExist "/z/one" |-> Right False+ , expect $ PutStrLn "one" |-> ()+ , expect $ CreateDirectory "/z/one" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ ListDirectory "/a/b/one" |->+ Right ["dos.mp3", "Uno.mp3", "tres.mp3"]+ , expect $ GetFileStatus "/a/b/one/dos.mp3" |->+ Right (FileStatus 11 False 2000)+ , expect $ GetFileStatus "/a/b/one/Uno.mp3" |->+ Right (FileStatus 11 False 1000)+ , expect $ GetFileStatus "/a/b/one/tres.mp3" |->+ Right (FileStatus 11 False 3000)+ , expect $ PutStrLn "one/Uno.mp3" |-> ()+ , expect $ CopyFile "/a/b/one/Uno.mp3" "/z/one/Uno.mp3" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ PutStrLn "one/dos.mp3" |-> ()+ , expect $ CopyFile "/a/b/one/dos.mp3" "/z/one/dos.mp3" |-> Right ()+ , expect $ Sync |-> ()+ , expect $ PutStrLn "one/tres.mp3" |-> ()+ , expect $ CopyFile "/a/b/one/tres.mp3" "/z/one/tres.mp3" |-> Right ()+ , expect $ Sync |-> ()+ ]+ assertSuccess <=< Error.run $ run defaultOptions+ { optVerbose = True+ }++------------------------------------------------------------------------------++testVerboseScript :: TestTree+testVerboseScript = testCase "VerboseScript" . runMockT $ do+ inSequence+ [ expect $ MakeAbsolute "/z" |-> Right "/z"+ , expect $ GetFileStatus "/z" |-> Right (FileStatus 10 True 90000)+ , expect $ MakeAbsolute "one" |-> Right "/a/b/one"+ , expect $ GetFileStatus "/a/b/one" |-> Right (FileStatus 11 True 10000)+ , expect $ DoesPathExist "/z/one" |-> Right False+ , expect $ PutStrLn "echo one" |-> ()+ , expect $ PutStrLn "mkdir /z/one" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ ListDirectory "/a/b/one" |->+ Right ["dos.mp3", "Uno.mp3", "tres.mp3"]+ , expect $ GetFileStatus "/a/b/one/dos.mp3" |->+ Right (FileStatus 11 False 2000)+ , expect $ GetFileStatus "/a/b/one/Uno.mp3" |->+ Right (FileStatus 11 False 1000)+ , expect $ GetFileStatus "/a/b/one/tres.mp3" |->+ Right (FileStatus 11 False 3000)+ , expect $ PutStrLn "echo one/Uno.mp3" |-> ()+ , expect $ PutStrLn "cp /a/b/one/Uno.mp3 /z/one/Uno.mp3" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "echo one/dos.mp3" |-> ()+ , expect $ PutStrLn "cp /a/b/one/dos.mp3 /z/one/dos.mp3" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ , expect $ PutStrLn "echo one/tres.mp3" |-> ()+ , expect $ PutStrLn "cp /a/b/one/tres.mp3 /z/one/tres.mp3" |-> ()+ , expect $ PutStrLn "sync" |-> ()+ ]+ assertSuccess <=< Error.run $ run defaultOptions+ { optScript = True+ , optVerbose = True+ }++------------------------------------------------------------------------------++testDestinationNotFound :: TestTree+testDestinationNotFound = testCase "DestinationNotFound" . runMockT $ do+ inSequence+ [ expect $ MakeAbsolute "/z" |-> Right "/z"+ , expect $ GetFileStatus "/z" |-> Left (userError "file not found: /z")+ ]+ assertError "user error (file not found: /z)" <=< Error.run $+ run defaultOptions++------------------------------------------------------------------------------++testDestinationNotDirectory :: TestTree+testDestinationNotDirectory =+ testCase "DestinationNotDirectory" . runMockT $ do+ inSequence+ [ expect $ MakeAbsolute "/z" |-> Right "/z"+ , expect $ GetFileStatus "/z" |-> Right (FileStatus 10 False 90000)+ ]+ assertError "not a directory: /z" <=< Error.run $ run defaultOptions++------------------------------------------------------------------------------++testSourceNotFound :: TestTree+testSourceNotFound = testCase "SourceNotFound" . runMockT $ do+ inSequence+ [ expect $ MakeAbsolute "/z" |-> Right "/z"+ , expect $ GetFileStatus "/z" |-> Right (FileStatus 10 True 90000)+ , expect $ MakeAbsolute "one" |-> Right "/a/b/one"+ , expect $ GetFileStatus "/a/b/one" |->+ Left (userError "file not found: /a/b/one")+ ]+ assertError "user error (file not found: /a/b/one)" <=< Error.run $+ run defaultOptions++------------------------------------------------------------------------------++testSourceAbove :: TestTree+testSourceAbove = testCase "SourceAbove" . runMockT $ do+ inSequence+ [ expect $ MakeAbsolute "b/c" |-> Right "/a/b/c"+ , expect $ GetFileStatus "/a/b/c" |-> Right (FileStatus 11 True 1000)+ , expect $ MakeAbsolute "e" |-> Right "/a/e"+ , expect $ GetFileStatus "/a/e" |-> Right (FileStatus 11 True 2000)+ , expect $ DoesPathExist "/a/b/c/e" |-> Right False+ , expect $ MakeAbsolute "b/d" |-> Right "/a/b/d"+ , expect $ GetFileStatus "/a/b/d" |-> Right (FileStatus 11 True 3000)+ , expect $ DoesPathExist "/a/b/c/d" |-> Right False+ , expect $ MakeAbsolute "b0" |-> Right "/a/b0"+ , expect $ GetFileStatus "/a/b0" |-> Right (FileStatus 11 True 4000)+ , expect $ DoesPathExist "/a/b/c/b0" |-> Right False+ , expect $ MakeAbsolute "b" |-> Right "/a/b"+ , expect $ GetFileStatus "/a/b" |-> Right (FileStatus 11 True 5000)+ ]+ assertError "source directory above target directory: b" <=<+ Error.run $ run defaultOptions+ { optSources = NonEmpty.fromList ["e", "b/d", "b0", "b", "f"]+ , optDestination = "b/c"+ }++------------------------------------------------------------------------------++testSourceExists :: TestTree+testSourceExists = testCase "SourceExists" . runMockT $ do+ inSequence+ [ expect $ MakeAbsolute "/z" |-> Right "/z"+ , expect $ GetFileStatus "/z" |-> Right (FileStatus 10 True 90000)+ , expect $ MakeAbsolute "one" |-> Right "/a/b/one"+ , expect $ GetFileStatus "/a/b/one" |-> Right (FileStatus 11 True 10000)+ , expect $ DoesPathExist "/z/one" |-> Right True+ ]+ assertError "already exists: /z/one" <=< Error.run $ run defaultOptions++------------------------------------------------------------------------------++testLarge :: TestTree+testLarge = testCase "Large" . runMockT $ do+ inSequence+ [ expect $ MakeAbsolute "/z" |-> Right "/z"+ , expect $ GetFileStatus "/z" |-> Right (FileStatus 10 True 90000)+ , expect $ MakeAbsolute "d" |-> Right "/a/b/d"+ , expect $ GetFileStatus "/a/b/d" |-> Right (FileStatus 11 True 2000)+ , expect $ DoesPathExist "/z/d" |-> Right False+ , expect $ MakeAbsolute "c" |-> Right "/a/b/c"+ , expect $ GetFileStatus "/a/b/c" |-> Right (FileStatus 11 True 1000)+ , expect $ DoesPathExist "/z/c" |-> Right False+ , expect $ CreateDirectory "/z/c" |-> Right ()+ , expect $ ListDirectory "/a/b/c" |-> Right ["two", "one"]+ , expect $ GetFileStatus "/a/b/c/two" |->+ Right (FileStatus 11 True 2000)+ , expect $ GetFileStatus "/a/b/c/one" |->+ Right (FileStatus 11 True 1000)+ , expect $ CreateDirectory "/z/c/one" |-> Right ()+ , expect $ ListDirectory "/a/b/c/one" |->+ Right ["11.mp3", "six", "13.mp3", "eight", "12.mp3", "14.mp3"]+ , expect $ GetFileStatus "/a/b/c/one/11.mp3" |->+ Right (FileStatus 11 False 1100)+ , expect $ GetFileStatus "/a/b/c/one/six" |->+ Right (FileStatus 11 True 6000)+ , expect $ GetFileStatus "/a/b/c/one/13.mp3" |->+ Right (FileStatus 11 False 1300)+ , expect $ GetFileStatus "/a/b/c/one/eight" |->+ Right (FileStatus 11 True 8000)+ , expect $ GetFileStatus "/a/b/c/one/12.mp3" |->+ Right (FileStatus 11 False 1200)+ , expect $ GetFileStatus "/a/b/c/one/14.mp3" |->+ Right (FileStatus 11 False 1400)+ , expect $ CopyFile "/a/b/c/one/11.mp3" "/z/c/one/11.mp3" |-> Right ()+ , expect $ CopyFile "/a/b/c/one/12.mp3" "/z/c/one/12.mp3" |-> Right ()+ , expect $ CopyFile "/a/b/c/one/13.mp3" "/z/c/one/13.mp3" |-> Right ()+ , expect $ CopyFile "/a/b/c/one/14.mp3" "/z/c/one/14.mp3" |-> Right ()+ , expect $ CreateDirectory "/z/c/one/eight" |-> Right ()+ , expect $ ListDirectory "/a/b/c/one/eight" |->+ Right ["83.mp3", "81.mp3", "82.mp3"]+ , expect $ GetFileStatus "/a/b/c/one/eight/83.mp3" |->+ Right (FileStatus 11 False 8300)+ , expect $ GetFileStatus "/a/b/c/one/eight/81.mp3" |->+ Right (FileStatus 11 False 8100)+ , expect $ GetFileStatus "/a/b/c/one/eight/82.mp3" |->+ Right (FileStatus 11 False 8200)+ , expect $+ CopyFile "/a/b/c/one/eight/81.mp3" "/z/c/one/eight/81.mp3" |->+ Right ()+ , expect $+ CopyFile "/a/b/c/one/eight/82.mp3" "/z/c/one/eight/82.mp3" |->+ Right ()+ , expect $+ CopyFile "/a/b/c/one/eight/83.mp3" "/z/c/one/eight/83.mp3" |->+ Right ()+ , expect $ CreateDirectory "/z/c/one/six" |-> Right ()+ , expect $ ListDirectory "/a/b/c/one/six" |-> Right []+ , expect $ CreateDirectory "/z/c/two" |-> Right ()+ , expect $ ListDirectory "/a/b/c/two" |->+ Right ["23.mp3", "22.mp3", "seven", "five", "21.mp3"]+ , expect $ GetFileStatus "/a/b/c/two/23.mp3" |->+ Right (FileStatus 11 False 2300)+ , expect $ GetFileStatus "/a/b/c/two/22.mp3" |->+ Right (FileStatus 11 False 2200)+ , expect $ GetFileStatus "/a/b/c/two/seven" |->+ Right (FileStatus 11 True 7000)+ , expect $ GetFileStatus "/a/b/c/two/five" |->+ Right (FileStatus 11 True 5000)+ , expect $ GetFileStatus "/a/b/c/two/21.mp3" |->+ Right (FileStatus 11 False 2100)+ , expect $ CopyFile "/a/b/c/two/21.mp3" "/z/c/two/21.mp3" |-> Right ()+ , expect $ CopyFile "/a/b/c/two/22.mp3" "/z/c/two/22.mp3" |-> Right ()+ , expect $ CopyFile "/a/b/c/two/23.mp3" "/z/c/two/23.mp3" |-> Right ()+ , expect $ CreateDirectory "/z/c/two/five" |-> Right ()+ , expect $ ListDirectory "/a/b/c/two/five" |-> Right ["51.mp3"]+ , expect $ GetFileStatus "/a/b/c/two/five/51.mp3" |->+ Right (FileStatus 11 False 5100)+ , expect $ CopyFile "/a/b/c/two/five/51.mp3" "/z/c/two/five/51.mp3" |->+ Right ()+ , expect $ CreateDirectory "/z/c/two/seven" |-> Right ()+ , expect $ ListDirectory "/a/b/c/two/seven" |->+ Right ["72.mp3", "71.mp3"]+ , expect $ GetFileStatus "/a/b/c/two/seven/72.mp3" |->+ Right (FileStatus 11 False 7200)+ , expect $ GetFileStatus "/a/b/c/two/seven/71.mp3" |->+ Right (FileStatus 11 False 7100)+ , expect $+ CopyFile "/a/b/c/two/seven/71.mp3" "/z/c/two/seven/71.mp3" |->+ Right ()+ , expect $+ CopyFile "/a/b/c/two/seven/72.mp3" "/z/c/two/seven/72.mp3" |->+ Right ()+ , expect $ CreateDirectory "/z/d" |-> Right ()+ , expect $ ListDirectory "/a/b/d" |-> Right ["three"]+ , expect $ GetFileStatus "/a/b/d/three" |->+ Right (FileStatus 11 True 3000)+ , expect $ CreateDirectory "/z/d/three" |-> Right ()+ , expect $ ListDirectory "/a/b/d/three" |->+ Right ["32.mp3", "31.mp3", "four", "34.mp3", "nine", "33.mp3"]+ , expect $ GetFileStatus "/a/b/d/three/32.mp3" |->+ Right (FileStatus 11 False 3200)+ , expect $ GetFileStatus "/a/b/d/three/31.mp3" |->+ Right (FileStatus 11 False 3100)+ , expect $ GetFileStatus "/a/b/d/three/four" |->+ Right (FileStatus 11 True 4000)+ , expect $ GetFileStatus "/a/b/d/three/34.mp3" |->+ Right (FileStatus 11 False 3400)+ , expect $ GetFileStatus "/a/b/d/three/nine" |->+ Right (FileStatus 11 True 9000)+ , expect $ GetFileStatus "/a/b/d/three/33.mp3" |->+ Right (FileStatus 11 False 3300)+ , expect $ CopyFile "/a/b/d/three/31.mp3" "/z/d/three/31.mp3" |->+ Right ()+ , expect $ CopyFile "/a/b/d/three/32.mp3" "/z/d/three/32.mp3" |->+ Right ()+ , expect $ CopyFile "/a/b/d/three/33.mp3" "/z/d/three/33.mp3" |->+ Right ()+ , expect $ CopyFile "/a/b/d/three/34.mp3" "/z/d/three/34.mp3" |->+ Right ()+ , expect $ CreateDirectory "/z/d/three/four" |-> Right ()+ , expect $ ListDirectory "/a/b/d/three/four" |->+ Right ["41.mp3", "42.mp3"]+ , expect $ GetFileStatus "/a/b/d/three/four/41.mp3" |->+ Right (FileStatus 11 False 4100)+ , expect $ GetFileStatus "/a/b/d/three/four/42.mp3" |->+ Right (FileStatus 11 False 4200)+ , expect $+ CopyFile "/a/b/d/three/four/41.mp3" "/z/d/three/four/41.mp3" |->+ Right ()+ , expect $+ CopyFile "/a/b/d/three/four/42.mp3" "/z/d/three/four/42.mp3" |->+ Right ()+ , expect $ CreateDirectory "/z/d/three/nine" |-> Right ()+ , expect $ ListDirectory "/a/b/d/three/nine" |-> Right ["ten"]+ , expect $ GetFileStatus "/a/b/d/three/nine/ten" |->+ Right (FileStatus 11 True 10000)+ , expect $ CreateDirectory "/z/d/three/nine/ten" |-> Right ()+ , expect $ ListDirectory "/a/b/d/three/nine/ten" |->+ Right ["102.mp3", "101.mp3"]+ , expect $ GetFileStatus "/a/b/d/three/nine/ten/102.mp3" |->+ Right (FileStatus 11 False 10200)+ , expect $ GetFileStatus "/a/b/d/three/nine/ten/101.mp3" |->+ Right (FileStatus 11 False 10100)+ , expect $+ CopyFile+ "/a/b/d/three/nine/ten/101.mp3"+ "/z/d/three/nine/ten/101.mp3"+ |-> Right ()+ , expect $+ CopyFile+ "/a/b/d/three/nine/ten/102.mp3"+ "/z/d/three/nine/ten/102.mp3"+ |-> Right ()+ ]+ assertSuccess <=< Error.run $ run defaultOptions+ { optSync = False+ , optSources = NonEmpty.fromList ["d", "c"]+ }++------------------------------------------------------------------------------++testLargeScript :: TestTree+testLargeScript = testCase "LargeScript" . runMockT $ do+ inSequence+ [ expect $ MakeAbsolute "/z" |-> Right "/z"+ , expect $ GetFileStatus "/z" |-> Right (FileStatus 10 True 90000)+ , expect $ MakeAbsolute "d" |-> Right "/a/b/d"+ , expect $ GetFileStatus "/a/b/d" |-> Right (FileStatus 11 True 2000)+ , expect $ DoesPathExist "/z/d" |-> Right False+ , expect $ MakeAbsolute "c" |-> Right "/a/b/c"+ , expect $ GetFileStatus "/a/b/c" |-> Right (FileStatus 11 True 1000)+ , expect $ DoesPathExist "/z/c" |-> Right False+ , expect $ PutStrLn "mkdir /z/c" |-> ()+ , expect $ ListDirectory "/a/b/c" |-> Right ["two", "one"]+ , expect $ GetFileStatus "/a/b/c/two" |->+ Right (FileStatus 11 True 2000)+ , expect $ GetFileStatus "/a/b/c/one" |->+ Right (FileStatus 11 True 1000)+ , expect $ PutStrLn "mkdir /z/c/one" |-> ()+ , expect $ ListDirectory "/a/b/c/one" |->+ Right ["11.mp3", "six", "13.mp3", "eight", "12.mp3", "14.mp3"]+ , expect $ GetFileStatus "/a/b/c/one/11.mp3" |->+ Right (FileStatus 11 False 1100)+ , expect $ GetFileStatus "/a/b/c/one/six" |->+ Right (FileStatus 11 True 6000)+ , expect $ GetFileStatus "/a/b/c/one/13.mp3" |->+ Right (FileStatus 11 False 1300)+ , expect $ GetFileStatus "/a/b/c/one/eight" |->+ Right (FileStatus 11 True 8000)+ , expect $ GetFileStatus "/a/b/c/one/12.mp3" |->+ Right (FileStatus 11 False 1200)+ , expect $ GetFileStatus "/a/b/c/one/14.mp3" |->+ Right (FileStatus 11 False 1400)+ , expect $ PutStrLn "cp /a/b/c/one/11.mp3 /z/c/one/11.mp3" |-> ()+ , expect $ PutStrLn "cp /a/b/c/one/12.mp3 /z/c/one/12.mp3" |-> ()+ , expect $ PutStrLn "cp /a/b/c/one/13.mp3 /z/c/one/13.mp3" |-> ()+ , expect $ PutStrLn "cp /a/b/c/one/14.mp3 /z/c/one/14.mp3" |-> ()+ , expect $ PutStrLn "mkdir /z/c/one/eight" |-> ()+ , expect $ ListDirectory "/a/b/c/one/eight" |->+ Right ["83.mp3", "81.mp3", "82.mp3"]+ , expect $ GetFileStatus "/a/b/c/one/eight/83.mp3" |->+ Right (FileStatus 11 False 8300)+ , expect $ GetFileStatus "/a/b/c/one/eight/81.mp3" |->+ Right (FileStatus 11 False 8100)+ , expect $ GetFileStatus "/a/b/c/one/eight/82.mp3" |->+ Right (FileStatus 11 False 8200)+ , expect $+ PutStrLn "cp /a/b/c/one/eight/81.mp3 /z/c/one/eight/81.mp3" |-> ()+ , expect $+ PutStrLn "cp /a/b/c/one/eight/82.mp3 /z/c/one/eight/82.mp3" |-> ()+ , expect $+ PutStrLn "cp /a/b/c/one/eight/83.mp3 /z/c/one/eight/83.mp3" |-> ()+ , expect $ PutStrLn "mkdir /z/c/one/six" |-> ()+ , expect $ ListDirectory "/a/b/c/one/six" |-> Right []+ , expect $ PutStrLn "mkdir /z/c/two" |-> ()+ , expect $ ListDirectory "/a/b/c/two" |->+ Right ["23.mp3", "22.mp3", "seven", "five", "21.mp3"]+ , expect $ GetFileStatus "/a/b/c/two/23.mp3" |->+ Right (FileStatus 11 False 2300)+ , expect $ GetFileStatus "/a/b/c/two/22.mp3" |->+ Right (FileStatus 11 False 2200)+ , expect $ GetFileStatus "/a/b/c/two/seven" |->+ Right (FileStatus 11 True 7000)+ , expect $ GetFileStatus "/a/b/c/two/five" |->+ Right (FileStatus 11 True 5000)+ , expect $ GetFileStatus "/a/b/c/two/21.mp3" |->+ Right (FileStatus 11 False 2100)+ , expect $ PutStrLn "cp /a/b/c/two/21.mp3 /z/c/two/21.mp3" |-> ()+ , expect $ PutStrLn "cp /a/b/c/two/22.mp3 /z/c/two/22.mp3" |-> ()+ , expect $ PutStrLn "cp /a/b/c/two/23.mp3 /z/c/two/23.mp3" |-> ()+ , expect $ PutStrLn "mkdir /z/c/two/five" |-> ()+ , expect $ ListDirectory "/a/b/c/two/five" |-> Right ["51.mp3"]+ , expect $ GetFileStatus "/a/b/c/two/five/51.mp3" |->+ Right (FileStatus 11 False 5100)+ , expect $ PutStrLn "cp /a/b/c/two/five/51.mp3 /z/c/two/five/51.mp3" |->+ ()+ , expect $ PutStrLn "mkdir /z/c/two/seven" |-> ()+ , expect $ ListDirectory "/a/b/c/two/seven" |->+ Right ["72.mp3", "71.mp3"]+ , expect $ GetFileStatus "/a/b/c/two/seven/72.mp3" |->+ Right (FileStatus 11 False 7200)+ , expect $ GetFileStatus "/a/b/c/two/seven/71.mp3" |->+ Right (FileStatus 11 False 7100)+ , expect $+ PutStrLn "cp /a/b/c/two/seven/71.mp3 /z/c/two/seven/71.mp3" |-> ()+ , expect $+ PutStrLn "cp /a/b/c/two/seven/72.mp3 /z/c/two/seven/72.mp3" |-> ()+ , expect $ PutStrLn "mkdir /z/d" |-> ()+ , expect $ ListDirectory "/a/b/d" |-> Right ["three"]+ , expect $ GetFileStatus "/a/b/d/three" |->+ Right (FileStatus 11 True 3000)+ , expect $ PutStrLn "mkdir /z/d/three" |-> ()+ , expect $ ListDirectory "/a/b/d/three" |->+ Right ["32.mp3", "31.mp3", "four", "34.mp3", "nine", "33.mp3"]+ , expect $ GetFileStatus "/a/b/d/three/32.mp3" |->+ Right (FileStatus 11 False 3200)+ , expect $ GetFileStatus "/a/b/d/three/31.mp3" |->+ Right (FileStatus 11 False 3100)+ , expect $ GetFileStatus "/a/b/d/three/four" |->+ Right (FileStatus 11 True 4000)+ , expect $ GetFileStatus "/a/b/d/three/34.mp3" |->+ Right (FileStatus 11 False 3400)+ , expect $ GetFileStatus "/a/b/d/three/nine" |->+ Right (FileStatus 11 True 9000)+ , expect $ GetFileStatus "/a/b/d/three/33.mp3" |->+ Right (FileStatus 11 False 3300)+ , expect $ PutStrLn "cp /a/b/d/three/31.mp3 /z/d/three/31.mp3" |-> ()+ , expect $ PutStrLn "cp /a/b/d/three/32.mp3 /z/d/three/32.mp3" |-> ()+ , expect $ PutStrLn "cp /a/b/d/three/33.mp3 /z/d/three/33.mp3" |-> ()+ , expect $ PutStrLn "cp /a/b/d/three/34.mp3 /z/d/three/34.mp3" |-> ()+ , expect $ PutStrLn "mkdir /z/d/three/four" |-> ()+ , expect $ ListDirectory "/a/b/d/three/four" |->+ Right ["41.mp3", "42.mp3"]+ , expect $ GetFileStatus "/a/b/d/three/four/41.mp3" |->+ Right (FileStatus 11 False 4100)+ , expect $ GetFileStatus "/a/b/d/three/four/42.mp3" |->+ Right (FileStatus 11 False 4200)+ , expect $+ PutStrLn "cp /a/b/d/three/four/41.mp3 /z/d/three/four/41.mp3" |-> ()+ , expect $+ PutStrLn "cp /a/b/d/three/four/42.mp3 /z/d/three/four/42.mp3" |-> ()+ , expect $ PutStrLn "mkdir /z/d/three/nine" |-> ()+ , expect $ ListDirectory "/a/b/d/three/nine" |-> Right ["ten"]+ , expect $ GetFileStatus "/a/b/d/three/nine/ten" |->+ Right (FileStatus 11 True 10000)+ , expect $ PutStrLn "mkdir /z/d/three/nine/ten" |-> ()+ , expect $ ListDirectory "/a/b/d/three/nine/ten" |->+ Right ["102.mp3", "101.mp3"]+ , expect $ GetFileStatus "/a/b/d/three/nine/ten/102.mp3" |->+ Right (FileStatus 11 False 10200)+ , expect $ GetFileStatus "/a/b/d/three/nine/ten/101.mp3" |->+ Right (FileStatus 11 False 10100)+ , expect $+ PutStrLn+ "cp /a/b/d/three/nine/ten/101.mp3 /z/d/three/nine/ten/101.mp3"+ |-> ()+ , expect $+ PutStrLn+ "cp /a/b/d/three/nine/ten/102.mp3 /z/d/three/nine/ten/102.mp3"+ |-> ()+ ]+ assertSuccess <=< Error.run $ run defaultOptions+ { optSync = False+ , optScript = True+ , optSources = NonEmpty.fromList ["d", "c"]+ }++------------------------------------------------------------------------------++tests :: TestTree+tests = testGroup "seqcp:Mock"+ [ testCaseSensitive+ , testCaseSensitiveScript+ , testSourcesCaseSensitive+ , testSourcesCaseSensitiveScript+ , testCaseInsensitive+ , testCaseInsensitiveScript+ , testSourcesCaseInsensitive+ , testSourcesCaseInsensitiveScript+ , testFirstNone+ , testFirstNoneScript+ , testFirstDirs+ , testFirstDirsScript+ , testSourcesFirstDirs+ , testSourcesFirstDirsScript+ , testFirstFiles+ , testFirstFilesScript+ , testSourcesFirstFiles+ , testSourcesFirstFilesScript+ , testNoSync+ , testNoSyncScript+ , testOrderNameReverse+ , testOrderNameReverseScript+ , testSourcesOrderNameReverse+ , testSourcesOrderNameReverseScript+ , testOrderTime+ , testOrderTimeScript+ , testSourcesOrderTime+ , testSourcesOrderTimeScript+ , testOrderTimeReverse+ , testOrderTimeReverseScript+ , testSourcesOrderTimeReverse+ , testSourcesOrderTimeReverseScript+ , testOrderRandom+ , testOrderRandomScript+ , testSourcesOrderRandom+ , testSourcesOrderRandomScript+ , testVerbose+ , testVerboseScript+ , testDestinationNotFound+ , testDestinationNotDirectory+ , testSourceNotFound+ , testSourceAbove+ , testSourceExists+ , testLarge+ , testLargeScript+ ]
+ test/Spec.hs view
@@ -0,0 +1,23 @@+{-# LANGUAGE CPP #-}++module Main (main) where++-- https://hackage.haskell.org/package/tasty+import Test.Tasty (defaultMain, testGroup)++-- (phatsort:test)+#if __GLASGOW_HASKELL__ >= 806+import qualified PhatSort.Cmd.PhatSort.Mock+import qualified PhatSort.Cmd.SeqCp.Mock+#endif++------------------------------------------------------------------------------++main :: IO ()+main = defaultMain $ testGroup "test"+ [+#if __GLASGOW_HASKELL__ >= 806+ PhatSort.Cmd.PhatSort.Mock.tests+ , PhatSort.Cmd.SeqCp.Mock.tests+#endif+ ]
+ test/TestLib.hs view
@@ -0,0 +1,80 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE TypeFamilies #-}++{-# OPTIONS_GHC -fno-warn-orphans #-}++module TestLib where++-- https://hackage.haskell.org/package/base+import Control.Monad.IO.Class (MonadIO(liftIO))+import GHC.Stack (HasCallStack)++-- https://hackage.haskell.org/package/HMock+import Test.HMock (MockT, makeMockable)++-- https://hackage.haskell.org/package/MonadRandom+import qualified Control.Monad.Random.Lazy as Rand+import Control.Monad.Random.Lazy (MonadRandom)++-- https://hackage.haskell.org/package/tasty-hunit+import qualified Test.Tasty.HUnit as HUnit++-- https://hackage.haskell.org/package/transformers+import Control.Monad.Trans.Class (lift)++-- (phatsort)+import PhatSort.Monad.FileSystem (MonadFileSystem)+import PhatSort.Monad.Stdio (MonadStdio)+import PhatSort.Monad.Sync (MonadSync)++------------------------------------------------------------------------------++makeMockable [t|MonadFileSystem|]++makeMockable [t|MonadStdio|]++makeMockable [t|MonadSync|]++------------------------------------------------------------------------------++instance MonadRandom m => MonadRandom (MockT m) where+ getRandomR = lift . Rand.getRandomR+ getRandom = lift Rand.getRandom+ getRandomRs = lift . Rand.getRandomRs+ getRandoms = lift Rand.getRandoms++------------------------------------------------------------------------------++assertSuccess+ :: (HasCallStack, MonadIO m)+ => Either String ()+ -> m ()+assertSuccess = \case+ Right () -> pure ()+ Left err -> liftIO . HUnit.assertFailure $ "unexpected error: " ++ err++------------------------------------------------------------------------------++assertError+ :: (HasCallStack, MonadIO m)+ => String -- ^ expected error message+ -> Either String ()+ -> m ()+assertError expected = \case+ Left err+ | err == expected -> pure ()+ | otherwise -> liftIO . HUnit.assertFailure $ unlines+ [ "unexpected error message"+ , " expected: " ++ expected+ , " but got: " ++ err+ ]+ Right () -> liftIO . HUnit.assertFailure $ unlines+ [ "unexpected success"+ , " expected error message: " ++ expected+ ]