module Darcs.Test.UI.Commands.Test.Simple ( testSuite ) where import Darcs.Prelude import qualified Darcs.Util.IndexedMonad as Indexed import Darcs.Patch.Commute ( Commute(..) ) import Darcs.Patch.Invert ( Invert(..) ) import Darcs.Patch.Set ( Origin ) import Darcs.Patch.Witnesses.Ordered ( RL(..), FL(..), consGapFL, reverseFL, mapFL, (:>)(..) ) import Darcs.Patch.Witnesses.Sealed ( Sealed(..), unseal, mapSeal, Sealed2(..) , FreeLeft, unFreeLeft, Gap(..) ) import Darcs.Patch.Witnesses.Show ( Show2(..) ) import qualified Darcs.UI.Options.All as O import Darcs.UI.Commands.Test.Impl ( TestRunner(..), TestResult(..), TestResultValid(..), TestFailure(..) , runStrategy, StrategyResultRaw(..) , PatchSeq(..), patchTreeToFL ) import Darcs.Test.UI.Commands.Test.IndexedApply ( IndexedApply(..) ) import Data.Constraint ( Dict(..) ) import Test.Framework.Providers.HUnit ( testCase ) import Test.Framework.Providers.QuickCheck2 ( testProperty ) import Test.Framework ( Test, testGroup ) import Test.HUnit ( assertEqual ) import Test.QuickCheck ( Arbitrary(..), Gen, Property, property, Discard(..), forAll, forAllShrink ) import Test.QuickCheck.Gen ( listOf, listOf1, frequency, elements ) testSuite :: Test testSuite = testGroup "Darcs.UI.Commands.Test.Simple" [ testGroup "Generic test cases" $ map genericTestCases [O.Linear, O.Bisect, O.Backoff] , testGroup "Randomised tests of linear" [linearRandomised] , testGroup "Randomised tests against linear" $ map genericRandomised [O.Bisect, O.Backoff] ] genericTestCases :: O.TestStrategy -> Test genericTestCases testStrategy = testGroup (show testStrategy) $ map (expectedResult testStrategy) [ ("Sequence ending in success", ((U, [S]), NoFailureOnHead)) , ("Sequence with no passes (1)", ((U, [F]), NoPasses)) , ("Sequence with no passes (2)", ((F, [F]), NoPasses)) , ("Sequence with simple failure (1)", ((S, [F]), Blame [0])) , ("Sequence with simple failure (2)", ((S, [S,S,F,F]), Blame [2])) , ("Sequence with untestable states", ((S, [S,S,U,U,F,F]), Blame [2,3,4])) , ("Sequence with initial untestable states", ((U, [U,U,U,U,U,U,U,S,S,U,U,F,F]), Blame [9,10,11])) ] type ExpectedResult = ((TestingState, [TestingState]), StrategyResultRaw [Int]) expectedResult :: O.TestStrategy -> (String, ExpectedResult) -> Test expectedResult testStrategy (testName, (testDetails, expectedTestResult)) = testCase testName $ do -- whether we try to shrink or not is irrelevant as nothing will commute let result = runStrategyOn testStrategy O.NoShrinkFailure testDetails assertEqual "Unexpected result" expectedTestResult result genericRandomised :: O.TestStrategy -> Test genericRandomised testStrategy = testGroup (show testStrategy) [ testProperty "simple sequence" (simpleSequence testStrategy) , testProperty "multi sequence" (multiSequence testStrategy) ] linearRandomised :: Test linearRandomised = testGroup (show O.Linear) [ testProperty "blame is found when possible" (findBlame O.Linear) ] simpleSequence :: O.TestStrategy -> SimpleSequenceDefinition -> Bool simpleSequence testStrategy sequenceDef = let s = ssdToTestDetails sequenceDef in runStrategyOn O.Linear O.NoShrinkFailure s == runStrategyOn testStrategy O.NoShrinkFailure s -- tests that if we stick multiple sequences each with a single "blame sequence" -- together, the strategy finds one of those sequences multiSequence :: O.TestStrategy -> Property multiSequence testStrategy = forAllShrink (listOf1 arbitraryBlameSSD) shrink $ \sequenceDefs -> let allTestDetails = map ssdToTestDetails sequenceDefs mergeDetails (i1, s1) (i2, s2) = (i1, s1 ++ [i2] ++ s2) adjustedResults _ [] = Just [] adjustedResults n (x:xs) = case (runStrategyOn O.Linear O.NoShrinkFailure x, adjustedResults (n + 1 + length (snd x)) xs) of (Blame ps, Just ys) -> Just (Blame (map (+n) ps) : ys) _ -> Nothing in case adjustedResults 0 allTestDetails of Nothing -> property Discard Just [] -> property Discard Just res -> property $ runStrategyOn testStrategy O.NoShrinkFailure (foldr1 mergeDetails allTestDetails) `elem` res findBlame :: O.TestStrategy -> Property findBlame testStrategy = forAll arbitraryBlameSSD $ \s -> case runStrategyOn testStrategy O.NoShrinkFailure (ssdToTestDetails s) of Blame _ -> True _ -> False -- a sequence of test results guaranteed to be monotonic in success/failure data SimpleSequenceDefinition = SimpleSequenceDefinition { successPart :: [TestingState] -- contains either S or U , middlePart :: TestingState -- either S, F or U - exists to guarantee the joined list is non-empty , failurePart :: [TestingState] -- contains either F or U } deriving Show ssdToTestDetails :: SimpleSequenceDefinition -> (TestingState, [TestingState]) ssdToTestDetails ssd = case successPart ssd ++ [middlePart ssd] ++ failurePart ssd of x:xs -> (x, xs) _ -> error "internal error: impossible empty list in ssdToTestDetails" instance Arbitrary SimpleSequenceDefinition where arbitrary = do s <- listOf (frequency [(1, r S), (3, r U)]) m <- frequency [(1, r S), (5, r U), (1, r F)] f <- listOf (frequency [(1, r F), (3, r U)]) return $ SimpleSequenceDefinition s m f where r = return shrink (SimpleSequenceDefinition s m f) = map (SimpleSequenceDefinition s m) (shrink f) ++ map (\s' -> SimpleSequenceDefinition s' m f) (shrink s) -- an arbitrary that's guaranteed to produce a sequence that results in Blame xxx arbitraryBlameSSD :: Gen SimpleSequenceDefinition arbitraryBlameSSD = do s1 <- listOf (frequency [(1, r S), (3, r U)]) s2 <- listOf (frequency [(1, r S), (3, r U)]) m <- frequency [(1, r S), (5, r U), (1, r F)] f <- listOf (frequency [(1, r F), (3, r U)]) return $ SimpleSequenceDefinition (s1 ++ [S] ++ s2) m (f ++ [F]) where r = return instance Arbitrary TestingState where arbitrary = elements [S, U, F] shrink _ = [] data TestingState = S | U | F deriving (Eq, Show) data TrivialPatch wX wY = TrivialPatch Int TestingState TestingState deriving Show instance Show2 TrivialPatch where showDict2 = Dict runStrategyOn :: O.TestStrategy -> O.ShrinkFailure -> (TestingState, [TestingState]) -> StrategyResultRaw [Int] runStrategyOn testStrategy shrinkFailure (initialState, patchStates) = let finalState = last (initialState:patchStates) in unseal (fmap toPatchNums . fst . flip runTestingMonad finalState . runStrategy testStrategy shrinkFailure) (genPatchSequence initialState patchStates) toPatchNums :: (Sealed2 (PatchSeq TrivialPatch)) -> [Int] toPatchNums (Sealed2 ps) = mapFL (\(TrivialPatch n _ _) -> n) (patchTreeToFL ps) genPatchSequence :: TestingState -> [TestingState] -> Sealed (RL TrivialPatch Origin) genPatchSequence initialState patchStates = mapSeal reverseFL $ unFreeLeft $ doGen 0 initialState patchStates where doGen :: Int -> TestingState -> [TestingState] -> FreeLeft (FL TrivialPatch) doGen _ _ [] = emptyGap NilFL doGen n startingState (nextState:states) = consGapFL (TrivialPatch n startingState nextState) (doGen (n+1) nextState states) instance Invert TrivialPatch where invert (TrivialPatch num ov nv) = TrivialPatch num nv ov instance Commute TrivialPatch where commute (_ :> _) = Nothing data TestingMonad wX wY a = TestingMonad { runTestingMonad :: TestingState -> (a, TestingState) } instance Indexed.Monad TestingMonad where return v = TestingMonad (\n -> (v, n)) m >>= f = TestingMonad (\n1 -> let (a, n2) = runTestingMonad m n1 in runTestingMonad (f a) n2) m1 >> m2 = TestingMonad (\n1 -> let (_, n2) = runTestingMonad m1 n1 in runTestingMonad m2 n2) toTestResult :: TestingState -> TestResult wX toTestResult S = Testable Success toTestResult U = Untestable toTestResult F = Testable (Failure (TestFailure 1)) instance IndexedApply TrivialPatch where type ApplyState TrivialPatch = TestingMonad apply (TrivialPatch num old new) = TestingMonad $ \st -> if st == old then ((), new) else error $ "state mismatch for patch " ++ show num ++ ", expected " ++ show old ++ ", got " ++ show st unapply (TrivialPatch num old new) = TestingMonad $ \st -> if st == new then ((), old) else error $ "state mismatch for patch " ++ show num ++ ", expected " ++ show new ++ ", got " ++ show st instance TestRunner TestingMonad where type ApplyPatchReqs TestingMonad p = (IndexedApply p, ApplyState p ~ TestingMonad) type DisplayPatchReqs TestingMonad p = p ~ TrivialPatch writeMsg _ = Indexed.return () mentionPatch _ = Indexed.return () finishedTesting v = TestingMonad (\_ -> (v, error "something tried to read final testing state")) getCurrentTestResult = TestingMonad (\n -> (toTestResult n, n)) applyPatch = apply unapplyPatch = unapply