{-# LANGUAGE DataKinds #-} {-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DeriveGeneric #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE OverloadedLists #-} {-# LANGUAGE OverloadedStrings #-} module Main where import Control.Concurrent import qualified Control.Exception as Ex (evaluate) import qualified Control.Exception.Safe as Ex import Control.Monad (when) import Control.Monad.IO.Class (liftIO) import Data.Either (isRight) import Data.Foldable (toList) import Data.Function (fix) import Data.Maybe (isNothing) import Data.String (fromString) import qualified Data.Map.Strict as Map import qualified Data.Set as Set import Data.Word (Word64) import qualified Di import qualified Di.Core import qualified System.Directory as Dir import qualified System.Environment (getEnv) import System.FilePath (()) import qualified System.IO.Error as IO import qualified System.Random import qualified Test.Tasty as Tasty import Test.Tasty.HUnit ((@?=), (@=?)) import qualified Test.Tasty.HUnit as HU import qualified Test.Tasty.QuickCheck as QC import Test.Tasty.QuickCheck ((===)) import qualified Test.Tasty.Runners as Tasty import qualified Moto.Internal as U import qualified Moto.File -------------------------------------------------------------------------------- data Fail = Fail String deriving (Show) instance Ex.Exception Fail fail_sync :: String -> IO a fail_sync s = Ex.throwM (Fail s) -------------------------------------------------------------------------------- backup_noop :: x -> U.Backup x backup_noop x = U.Backup (\_ k -> k x) backup_fail_sync :: String -> U.Backup x backup_fail_sync s = U.Backup (\_ _ -> fail_sync s) -------------------------------------------------------------------------------- store_noop :: x -> U.Store x store_noop x = U.Store (\_ _ _ -> pure ()) (\_ _ k -> k x) (\_ _ -> pure ()) store_fail_sync_save :: x -> String -> U.Store x store_fail_sync_save x s = U.Store (\_ _ _ -> fail_sync s) (\_ _ k -> k x) (\_ _ -> pure ()) store_fail_sync_load :: String -> U.Store x store_fail_sync_load s = U.Store (\_ _ _ -> pure ()) (\_ _ k -> fail_sync s) (\_ _ -> pure ()) store_fail_sync_delete :: x -> String -> U.Store x store_fail_sync_delete x s = U.Store (\_ _ _ -> pure ()) (\_ _ k -> k x) (\_ _ -> fail_sync s) -------------------------------------------------------------------------------- change_noop :: U.Change x change_noop = U.Change (\_ _ _ _ -> pure ()) change_fail_sync :: String -> U.Change x change_fail_sync s = U.Change (\_ _ _ _ -> fail_sync s) -------------------------------------------------------------------------------- mig_noop :: x -> U.Mig id deps mig_noop x = U.Mig (store_noop x) (backup_noop x) change_noop -------------------------------------------------------------------------------- -- Here we just write some things that we expect to compile over time. _migs_0 :: U.Migs '[] _migs_0 = U.migs _migs_1 :: U.Migs '[ '("a",'[])] _migs_1 = U.migs U.* mig_noop () -- Type inferred _migs_1' = U.migs U.* (mig_noop () :: U.Mig "a" '[]) _migs_2 :: U.Migs '[ '("b",'[]), '("a",'[])] _migs_2 = U.migs U.* mig_noop () U.* mig_noop () _migs_2' :: U.Migs '[ '("b",'["a"]), '("a",'[])] _migs_2' = U.migs U.* mig_noop () U.* mig_noop () _migs_3 :: U.Migs '[ '("c",'[]), '("b",'[]), '("a",'[])] _migs_3 = U.migs U.* mig_noop () U.* mig_noop () U.* mig_noop () _migs_3' :: U.Migs '[ '("c",'["a"]), '("b",'["a"]), '("a",'[])] _migs_3' = U.migs U.* mig_noop () U.* mig_noop () U.* mig_noop () _migs_3'' :: U.Migs '[ '("c",'["b"]), '("b",'["a"]), '("a",'[])] _migs_3'' = U.migs U.* mig_noop () U.* mig_noop () U.* mig_noop () _migs_3''' :: U.Migs '[ '("c",'["b","a"]), '("b",'["a"]), '("a",'[])] _migs_3''' = U.migs U.* mig_noop () U.* mig_noop () U.* mig_noop () -------------------------------------------------------------------------------- main :: IO () main = Di.new $ \di -> do Tasty.defaultMainWithIngredients [ Tasty.consoleTestReporter , Tasty.listingTests ] (tt di) -------------------------------------------------------------------------------- tt :: Di.Df1 -> Tasty.TestTree tt di = Tasty.testGroup "main" [ ttPlan , ttRun ] ttPlan :: Tasty.TestTree ttPlan = Tasty.testGroup "plan" [ HU.testCase "empty migs, empty ran, forwards implicit" $ do True @=? isRight (U.mkPlan U.migs [] (U.Target U.Forwards [])) , QC.testProperty "empty migs, empty ran, explicit targets" $ do let g = (,) <$> genDirection <*> fmap Set.fromList (QC.listOf1 genMigId) QC.forAll g $ \(d, mIds) -> do qcEqLeft (U.Err_Plan_TargetsNotFound mIds) (U.mkPlan U.migs [] (U.Target d mIds)) , HU.testCase "empty migs, empty ran, backwards implicit" $ do True @=? isRight (U.mkPlan U.migs [] (U.Target U.Backwards [])) , QC.testProperty "empty migs, some ran, forwards implicit" $ do let g = (,) <$> genDirection <*> QC.listOf1 genMigId QC.forAll g $ \(d, mIds) -> do qcEqLeft U.Err_Plan_HistoryUnknown (U.mkPlan U.migs mIds (U.Target d [])) , QC.testProperty "one mig, different ran" $ do let g = (,) <$> genDirection <*> QC.suchThat (QC.listOf1 genMigId) (/= ["a"]) QC.forAll g $ \(d, mIds) -> do qcEqLeft U.Err_Plan_HistoryUnknown (U.mkPlan U.migs mIds (U.Target d [])) , HU.testCase "one mig, empty ran, forwards implicit" $ do let migs = U.migs U.* (mig_noop () :: U.Mig "a" '[]) Right (U.Forwards, ["a"]) @=? fmap plan_bits (U.mkPlan migs [] (U.Target U.Forwards [])) , HU.testCase "one mig, empty ran, forwards explicit" $ do let migs = U.migs U.* (mig_noop () :: U.Mig "a" '[]) Right (U.Forwards, ["a"]) @=? fmap plan_bits (U.mkPlan migs [] (U.Target U.Forwards ["a"])) , HU.testCase "one mig, same ran, forwards implicit" $ do let migs = U.migs U.* (mig_noop () :: U.Mig "a" '[]) Right (U.Forwards, []) @=? fmap plan_bits (U.mkPlan migs ["a"] (U.Target U.Forwards [])) , HU.testCase "one mig, same ran, forwards explicit" $ do let migs = U.migs U.* (mig_noop () :: U.Mig "a" '[]) Right (U.Forwards, []) @=? fmap plan_bits (U.mkPlan migs ["a"] (U.Target U.Forwards ["a"])) , HU.testCase "one mig, empty ran, backwards implicit" $ do let migs = U.migs U.* (mig_noop () :: U.Mig "a" '[]) Right (U.Backwards, []) @=? fmap plan_bits (U.mkPlan migs [] (U.Target U.Backwards [])) , HU.testCase "one mig, empty ran, backwards explicit" $ do let migs = U.migs U.* (mig_noop () :: U.Mig "a" '[]) Right (U.Backwards, []) @=? fmap plan_bits (U.mkPlan migs [] (U.Target U.Backwards ["a"])) , HU.testCase "one mig, same ran, backwards implicit" $ do let migs = U.migs U.* (mig_noop () :: U.Mig "a" '[]) Right (U.Backwards, ["a"]) @=? fmap plan_bits (U.mkPlan migs ["a"] (U.Target U.Backwards [])) , HU.testCase "one mig, same ran, backwards explicit" $ do let migs = U.migs U.* (mig_noop () :: U.Mig "a" '[]) Right (U.Backwards, ["a"]) @=? fmap plan_bits (U.mkPlan migs ["a"] (U.Target U.Backwards ["a"])) , HU.testCase "five migs, empty ran, forwards implicit" $ do let migs = U.migs U.* (mig_noop () :: U.Mig "a" '[]) U.* (mig_noop () :: U.Mig "b" '[]) U.* (mig_noop () :: U.Mig "c" '["b"]) U.* (mig_noop () :: U.Mig "d" '["a","c"]) U.* (mig_noop () :: U.Mig "e" '["c"]) Right (U.Forwards, ["a","b","c","d","e"]) @=? fmap plan_bits (U.mkPlan migs [] (U.Target U.Forwards [])) , HU.testCase "five migs, empty ran, forwards explicit" $ do let migs = U.migs U.* (mig_noop () :: U.Mig "a" '[]) U.* (mig_noop () :: U.Mig "b" '[]) U.* (mig_noop () :: U.Mig "c" '["b"]) U.* (mig_noop () :: U.Mig "d" '["a","c"]) U.* (mig_noop () :: U.Mig "e" '["c"]) Right (U.Forwards, ["b","c","e"]) @=? fmap plan_bits (U.mkPlan migs [] (U.Target U.Forwards ["e","b","c"])) , HU.testCase "five migs, empty ran, forwards impossible" $ do let migs = U.migs U.* (mig_noop () :: U.Mig "a" '[]) U.* (mig_noop () :: U.Mig "b" '[]) U.* (mig_noop () :: U.Mig "c" '["b"]) U.* (mig_noop () :: U.Mig "d" '["a","c"]) U.* (mig_noop () :: U.Mig "e" '["c"]) huEqLeft U.Err_Plan_TargetImpossible (U.mkPlan migs [] (U.Target U.Forwards ["e","c"])) , HU.testCase "five migs, empty ran, backwards implicit" $ do let migs = U.migs U.* (mig_noop () :: U.Mig "a" '[]) U.* (mig_noop () :: U.Mig "b" '[]) U.* (mig_noop () :: U.Mig "c" '["b"]) U.* (mig_noop () :: U.Mig "d" '["a","c"]) U.* (mig_noop () :: U.Mig "e" '["c"]) Right (U.Backwards, []) @=? fmap plan_bits (U.mkPlan migs [] (U.Target U.Backwards [])) , HU.testCase "five migs, empty ran, backwards explicit" $ do let migs = U.migs U.* (mig_noop () :: U.Mig "a" '[]) U.* (mig_noop () :: U.Mig "b" '[]) U.* (mig_noop () :: U.Mig "c" '["b"]) U.* (mig_noop () :: U.Mig "d" '["a","c"]) U.* (mig_noop () :: U.Mig "e" '["c"]) Right (U.Backwards, []) @=? fmap plan_bits (U.mkPlan migs [] (U.Target U.Backwards ["e","b","c"])) , HU.testCase "five migs, empty ran, backwards for impossible forwards" $ do let migs = U.migs U.* (mig_noop () :: U.Mig "a" '[]) U.* (mig_noop () :: U.Mig "b" '[]) U.* (mig_noop () :: U.Mig "c" '["b"]) U.* (mig_noop () :: U.Mig "d" '["a","c"]) U.* (mig_noop () :: U.Mig "e" '["c"]) Right (U.Backwards, []) @=? fmap plan_bits (U.mkPlan migs [] (U.Target U.Backwards ["d","a"])) , QC.testProperty "five migs, bad ran all" $ do let migs = U.migs U.* (mig_noop () :: U.Mig "a" '[]) U.* (mig_noop () :: U.Mig "b" '[]) U.* (mig_noop () :: U.Mig "c" '["b"]) U.* (mig_noop () :: U.Mig "d" '["a","c"]) U.* (mig_noop () :: U.Mig "e" '["c"]) let g = (,) <$> genDirection <*> fmap Set.fromList (QC.sublistOf ["a","b","c","d","e"]) QC.forAll g $ \(d, mIds) -> do qcEqLeft U.Err_Plan_HistoryUnknown (U.mkPlan migs ["a","e","c","b","d"] (U.Target d mIds)) , QC.testProperty "five migs, bad ran some" $ do let migs = U.migs U.* (mig_noop () :: U.Mig "a" '[]) U.* (mig_noop () :: U.Mig "b" '[]) U.* (mig_noop () :: U.Mig "c" '["b"]) U.* (mig_noop () :: U.Mig "d" '["a","c"]) U.* (mig_noop () :: U.Mig "e" '["c"]) let g = (,) <$> genDirection <*> fmap Set.fromList (QC.sublistOf ["a","b","c","d","e"]) QC.forAll g $ \(d, mIds) -> do qcEqLeft U.Err_Plan_HistoryUnknown (U.mkPlan migs ["a","d"] (U.Target d mIds)) , HU.testCase "five migs, same ran, forwards implicit" $ do let migs = U.migs U.* (mig_noop () :: U.Mig "a" '[]) U.* (mig_noop () :: U.Mig "b" '[]) U.* (mig_noop () :: U.Mig "c" '["b"]) U.* (mig_noop () :: U.Mig "d" '["a","c"]) U.* (mig_noop () :: U.Mig "e" '["c"]) Right (U.Forwards, []) @=? fmap plan_bits (U.mkPlan migs ["b","a","c","e","d"] (U.Target U.Forwards [])) , HU.testCase "five migs, same ran, forwards explicit" $ do let migs = U.migs U.* (mig_noop () :: U.Mig "a" '[]) U.* (mig_noop () :: U.Mig "b" '[]) U.* (mig_noop () :: U.Mig "c" '["b"]) U.* (mig_noop () :: U.Mig "d" '["a","c"]) U.* (mig_noop () :: U.Mig "e" '["c"]) Right (U.Forwards, []) @=? fmap plan_bits (U.mkPlan migs ["a","b","c","e","d"] (U.Target U.Forwards ["e","b","c"])) , HU.testCase "five migs, same ran, backwards implicit" $ do let migs = U.migs U.* (mig_noop () :: U.Mig "a" '[]) U.* (mig_noop () :: U.Mig "b" '[]) U.* (mig_noop () :: U.Mig "c" '["b"]) U.* (mig_noop () :: U.Mig "d" '["a","c"]) U.* (mig_noop () :: U.Mig "e" '["c"]) Right (U.Backwards, ["b","a","c","e","d"]) @=? fmap plan_bits (U.mkPlan migs ["b","a","c","e","d"] (U.Target U.Backwards [])) , HU.testCase "five migs, same ran, some backwards explicit" $ do let migs = U.migs U.* (mig_noop () :: U.Mig "a" '[]) U.* (mig_noop () :: U.Mig "b" '[]) U.* (mig_noop () :: U.Mig "c" '["b"]) U.* (mig_noop () :: U.Mig "d" '["a","c"]) U.* (mig_noop () :: U.Mig "e" '["c"]) Right (U.Backwards, ["a","d"]) @=? fmap plan_bits (U.mkPlan migs ["a","b","c","e","d"] (U.Target U.Backwards ["a","d"])) , HU.testCase "five migs, same ran, backwards impossible" $ do let migs = U.migs U.* (mig_noop () :: U.Mig "a" '[]) U.* (mig_noop () :: U.Mig "b" '[]) U.* (mig_noop () :: U.Mig "c" '["b"]) U.* (mig_noop () :: U.Mig "d" '["a","c"]) U.* (mig_noop () :: U.Mig "e" '["c"]) huEqLeft U.Err_Plan_TargetImpossible (U.mkPlan migs ["a","b","c","d","e"] (U.Target U.Backwards ["b"])) , HU.testCase "five migs, some ran, one backwards explicit" $ do let migs = U.migs U.* (mig_noop () :: U.Mig "a" '[]) U.* (mig_noop () :: U.Mig "b" '[]) U.* (mig_noop () :: U.Mig "c" '["b"]) U.* (mig_noop () :: U.Mig "d" '["a","c"]) U.* (mig_noop () :: U.Mig "e" '["c"]) Right (U.Backwards, ["c"]) @=? fmap plan_bits (U.mkPlan migs ["a","b","c"] (U.Target U.Backwards ["c"])) , HU.testCase "five migs, some ran, forwards impossible" $ do let migs = U.migs U.* (mig_noop () :: U.Mig "a" '[]) U.* (mig_noop () :: U.Mig "b" '[]) U.* (mig_noop () :: U.Mig "c" '["b"]) U.* (mig_noop () :: U.Mig "d" '["a","c"]) U.* (mig_noop () :: U.Mig "e" '["c"]) huEqLeft U.Err_Plan_TargetImpossible (U.mkPlan migs ["a","b"] (U.Target U.Forwards ["e"])) , HU.testCase "five migs, some ran, backwards impossible" $ do let migs = U.migs U.* (mig_noop () :: U.Mig "a" '[]) U.* (mig_noop () :: U.Mig "b" '[]) U.* (mig_noop () :: U.Mig "c" '["b"]) U.* (mig_noop () :: U.Mig "d" '["a","c"]) U.* (mig_noop () :: U.Mig "e" '["c"]) huEqLeft U.Err_Plan_TargetImpossible (U.mkPlan migs ["a","b","c"] (U.Target U.Backwards ["b"])) , HU.testCase "five migs, some ran, one backwards explicit gone" $ do let migs = U.migs U.* (mig_noop () :: U.Mig "a" '[]) U.* (mig_noop () :: U.Mig "b" '[]) U.* (U.Gone :: U.Mig "c" '["b"]) U.* (mig_noop () :: U.Mig "d" '["a","c"]) U.* (mig_noop () :: U.Mig "e" '["c"]) huEqLeft (U.Err_Plan_TargetsGone ["c"]) (U.mkPlan migs ["a","b","c"] (U.Target U.Backwards ["c"])) , HU.testCase "five migs, some ran, some backwards explicit gone" $ do let migs = U.migs U.* (U.Gone :: U.Mig "a" '[]) U.* (mig_noop () :: U.Mig "b" '[]) U.* (U.Gone :: U.Mig "c" '["b"]) U.* (mig_noop () :: U.Mig "d" '["a","c"]) U.* (mig_noop () :: U.Mig "e" '["c"]) huEqLeft (U.Err_Plan_TargetsGone ["c"]) (U.mkPlan migs ["a","b","c"] (U.Target U.Backwards ["c","b"])) , HU.testCase "five migs, some ran, one backwards explicit one path gone" $ do let migs = U.migs U.* (mig_noop () :: U.Mig "a" '[]) U.* (mig_noop () :: U.Mig "b" '[]) U.* (U.Gone :: U.Mig "c" '["b"]) U.* (mig_noop () :: U.Mig "d" '["a","c"]) U.* (mig_noop () :: U.Mig "e" '["c"]) Right (U.Backwards, ["a"]) @=? fmap plan_bits (U.mkPlan migs ["a","b","c","d"] (U.Target U.Backwards ["a"])) , HU.testCase "five migs, some ran, two backwards explicit gone" $ do let migs = U.migs U.* (mig_noop () :: U.Mig "a" '[]) U.* (mig_noop () :: U.Mig "b" '[]) U.* (U.Gone :: U.Mig "c" '["b"]) U.* (mig_noop () :: U.Mig "d" '["a","c"]) U.* (mig_noop () :: U.Mig "e" '["c"]) huEqLeft (U.Err_Plan_TargetsGone ["c"]) (U.mkPlan migs ["a","b","c"] (U.Target U.Backwards ["c","b"])) , HU.testCase "five migs, some ran, backwards implicit gone" $ do let migs = U.migs U.* (U.Gone :: U.Mig "a" '[]) U.* (mig_noop () :: U.Mig "b" '[]) U.* (U.Gone :: U.Mig "c" '["b"]) U.* (mig_noop () :: U.Mig "d" '["a","c"]) U.* (mig_noop () :: U.Mig "e" '["c"]) huEqLeft (U.Err_Plan_TargetsGone ["c","a"]) (U.mkPlan migs ["a","b","c"] (U.Target U.Backwards [])) , HU.testCase "five migs, some ran, backwards diamond implicit gone" $ do let migs = U.migs U.* (mig_noop () :: U.Mig "a" '[]) U.* (U.Gone :: U.Mig "b" '["a"]) U.* (U.Gone :: U.Mig "c" '["a"]) U.* (mig_noop () :: U.Mig "d" '["a","c"]) U.* (mig_noop () :: U.Mig "e" '["c"]) huEqLeft (U.Err_Plan_TargetsGone ["b","c"]) (U.mkPlan migs ["a","b","c"] (U.Target U.Backwards [])) , HU.testCase "five migs, some ran, one forwards explicit gone" $ do let migs = U.migs U.* (mig_noop () :: U.Mig "a" '[]) U.* (mig_noop () :: U.Mig "b" '[]) U.* (U.Gone :: U.Mig "c" '["b"]) U.* (mig_noop () :: U.Mig "d" '["a","c"]) U.* (mig_noop () :: U.Mig "e" '["c"]) huEqLeft (U.Err_Plan_TargetsGone ["c"]) (U.mkPlan migs ["a","b"] (U.Target U.Forwards ["c"])) , HU.testCase "five migs, some ran, some forwards explicit gone" $ do let migs = U.migs U.* (U.Gone :: U.Mig "a" '[]) U.* (mig_noop () :: U.Mig "b" '[]) U.* (U.Gone :: U.Mig "c" '["b"]) U.* (mig_noop () :: U.Mig "d" '["a","c"]) U.* (mig_noop () :: U.Mig "e" '["c"]) huEqLeft (U.Err_Plan_TargetsGone ["c","a"]) (U.mkPlan migs ["b"] (U.Target U.Forwards ["c","b","a"])) , HU.testCase "five migs, some ran, one forwards explicit one path gone" $ do let migs = U.migs U.* (mig_noop () :: U.Mig "a" '[]) U.* (mig_noop () :: U.Mig "b" '[]) U.* (U.Gone :: U.Mig "c" '["b"]) U.* (mig_noop () :: U.Mig "d" '["a","b"]) U.* (mig_noop () :: U.Mig "e" '["d"]) Right (U.Forwards, ["a","b","d","e"]) @=? fmap plan_bits (U.mkPlan migs [] (U.Target U.Forwards ["a","b","d","e"])) , HU.testCase "five migs, some ran, forwards implicit gone" $ do let migs = U.migs U.* (U.Gone :: U.Mig "a" '[]) U.* (mig_noop () :: U.Mig "b" '[]) U.* (U.Gone :: U.Mig "c" '["b"]) U.* (mig_noop () :: U.Mig "d" '["a","c"]) U.* (mig_noop () :: U.Mig "e" '["c"]) huEqLeft (U.Err_Plan_TargetsGone ["c","a"]) (U.mkPlan migs ["b"] (U.Target U.Forwards [])) ] ttRun :: Tasty.TestTree ttRun = Tasty.testGroup "run" [ HU.testCase "empty registry, empty migs, forwards implicit" $ withTestDi $ \di -> withExpectedTestRegistry di U.Clean [] $ \reg -> huExpect [] $ \f -> do Right plan <- U.getPlan di U.migs reg (U.Target U.Forwards []) U.run di reg plan , HU.testCase "empty registry, two migs, forwards implicit" $ withTestDi $ \di -> withExpectedTestRegistry di U.Clean ["a","b"] $ \reg -> huExpect [4,1,2,5] $ \f -> do let migs = U.migs U.* (mig_noop () :: U.Mig "a" '[]) U.* (U.Mig (U.Store (\_ _ () -> f 1) (\_ _ k -> f 2 >> k ()) (\_ _ -> f 3)) (U.Backup (\_ k -> f 4 >> k ())) (U.Change (\_ _ _ _ -> f 5)) :: U.Mig "b" '["a"]) Right plan <- U.getPlan di migs reg (U.Target U.Forwards []) U.run di reg plan , HU.testCase "empty registry, two migs, fail sync backup, forwards implicit" $ withTestDi $ \di -> withExpectedTestRegistry di U.Clean ["a"] $ \reg -> huExpectFailSync "x" [] $ \f -> do let migs = U.migs U.* (mig_noop () :: U.Mig "a" '[]) U.* (U.Mig (U.Store (\_ _ () -> f 1) (\_ _ k -> f 2 >> k ()) (\_ _ -> f 3)) (U.Backup (\_ k -> fail_sync "x" >> f 4 >> k ())) (U.Change (\_ _ _ _ -> f 5)) :: U.Mig "b" '[]) Right plan <- U.getPlan di migs reg (U.Target U.Forwards []) U.run di reg plan , HU.testCase "empty registry, two migs, fail sync save, forwards implicit" $ withTestDi $ \di -> withExpectedTestRegistry di U.Clean ["a"] $ \reg -> huExpectFailSync "x" [4] $ \f -> do let migs = U.migs U.* (mig_noop () :: U.Mig "a" '[]) U.* (U.Mig (U.Store (\_ _ () -> fail_sync "x" >> f 1) (\_ _ k -> f 2 >> k ()) (\_ _ -> f 3)) (U.Backup (\_ k -> f 4 >> k ())) (U.Change (\_ _ _ _ -> f 5)) :: U.Mig "b" '[]) Right plan <- U.getPlan di migs reg (U.Target U.Forwards []) U.run di reg plan , HU.testCase "empty registry, two migs, fail sync load, forwards implicit" $ withTestDi $ \di -> withExpectedTestRegistry di U.Clean ["a"] $ \reg -> huExpectFailSync "x" [4,1] $ \f -> do let migs = U.migs U.* (mig_noop () :: U.Mig "a" '[]) U.* (U.Mig (U.Store (\_ _ () -> f 1) (\_ _ k -> fail_sync "x" >> f 2 >> k ()) (\_ _ -> f 3)) (U.Backup (\_ k -> f 4 >> k ())) (U.Change (\_ _ _ _ -> f 5)) :: U.Mig "b" '[]) Right plan <- U.getPlan di migs reg (U.Target U.Forwards []) U.run di reg plan , HU.testCase "empty registry, two migs, fail sync change normal, forwards implicit" $ withTestDi $ \di -> withExpectedTestRegistry di U.Clean ["a"] $ \reg -> huExpectFailSync "x" [4,1,2,6,3] $ \f -> do let migs = U.migs U.* (mig_noop () :: U.Mig "a" '[]) U.* (U.Mig (U.Store (\_ _ () -> f 1) (\_ _ k -> f 2 >> k ()) (\_ _ -> f 3)) (U.Backup (\_ k -> f 4 >> k ())) (U.Change (\_ d m _ -> case (d,m) of (U.Forwards, U.Normal) -> fail_sync "x" >> f 5 (U.Backwards, U.Recovery) -> f 6 _ -> error "impossible")) :: U.Mig "b" '[]) Right plan <- U.getPlan di migs reg (U.Target U.Forwards []) U.run di reg plan , HU.testCase "empty registry, two migs, fail sync change normal and recovery, forwards implicit" $ withTestDi $ \di -> withExpectedTestRegistry di (U.Dirty "b" U.Forwards) ["a"] $ \reg -> huExpectFailSync "x" [4,1,2] $ \f -> do let migs = U.migs U.* (mig_noop () :: U.Mig "a" '[]) U.* (U.Mig (U.Store (\_ _ () -> f 1) (\_ _ k -> f 2 >> k ()) (\_ _ -> f 3)) (U.Backup (\_ k -> f 4 >> k ())) (U.Change (\_ d m _ -> case (d,m) of (U.Forwards, U.Normal) -> fail_sync "x" >> f 5 (U.Backwards, U.Recovery) -> fail_sync "y" >> f 6 _ -> error "impossible")) :: U.Mig "b" '[]) Right plan <- U.getPlan di migs reg (U.Target U.Forwards []) U.run di reg plan ] -------------------------------------------------------------------------------- huExpect :: [Int] -> ((Int -> IO ()) -> IO x) -> IO x huExpect as0 k = do mv <- newMVar [] x <- k (\a -> modifyMVar_ mv (\as -> pure $! (a:as))) as <- takeMVar mv as0 @=? reverse as pure x huExpectFailSync :: String -> [Int] -> ((Int -> IO ()) -> IO ()) -> IO () huExpectFailSync s as0 k = do mv <- newMVar [] x <- Ex.catch (k (\a -> modifyMVar_ mv (\as -> pure $! (a:as)))) (\e@(Fail s') -> when (s' /= s) (Ex.throwM e)) as <- takeMVar mv as0 @=? reverse as pure x withTestDi :: (Di.Core.Di level path message -> IO a) -> IO a withTestDi = Di.Core.new (\_ -> pure ()) withExpectedTestRegistry :: forall a. Di.Df1 -> U.Status -> [U.MigId] -> (U.Registry -> IO a) -> IO a withExpectedTestRegistry di sex idsex k = do dir <- getTempDir Moto.File.withRegistry (dir ++ "/reg") $ \reg -> do Ex.finally (k reg) $ do s <- Ex.evaluate =<< U.registry_state reg di (sex, idsex) @=? (U.state_status s, map fst (U.state_committed s)) getTempDir :: IO FilePath getTempDir = do tmp_dir <- Dir.getTemporaryDirectory fix $ \k -> do w :: Word64 <- System.Random.randomIO let fn :: FilePath = tmp_dir ('d' : show w) Ex.try (Dir.createDirectory fn) >>= \case Right () -> pure fn Left e | IO.isAlreadyExistsError e -> k | otherwise -> Ex.throwM e plan_bits :: U.Plan -> (U.Direction, [U.MigId]) plan_bits (U.Plan d s) = (d, toList (fmap fst s)) state_bits :: U.State -> (U.Status, [U.MigId]) state_bits s = (U.state_status s, fst <$> U.state_committed s) genDirection :: QC.Gen U.Direction genDirection = QC.oneof [ pure U.Backwards, pure U.Forwards ] genMigId :: QC.Gen U.MigId genMigId = do c <- QC.arbitrary cs <- QC.arbitrary pure (fromString (c:cs)) -------------------------------------------------------------------------------- -- Compare some @'Left' a@ without requiring constraints on @b@. huEqLeft :: forall a b. (Eq a, Show a) => a -> Either a b -> HU.Assertion huEqLeft a (Left a') = Left a @=? (Left a' :: Either a ()) huEqLeft a _ = fail (show (Left a :: Either a ()) ++ " /= Right _") -- Compare some @'Left' a@ without requiring constraints on @b@. qcEqLeft :: forall a b. (Eq a, Show a) => a -> Either a b -> QC.Property qcEqLeft a (Left a') = Left a === (Left a' :: Either a ()) qcEqLeft a _ = show (Left a :: Either a ()) === "Right _"