darcs-2.18.1: harness/Darcs/Test/Patch/Arbitrary/PrimV1.hs
{-# OPTIONS_GHC -Wno-orphans #-}
module Darcs.Test.Patch.Arbitrary.PrimV1
( aPrim
, aPrimPair
) where
import Prelude ()
import Darcs.Prelude
import qualified Darcs.Test.Patch.Arbitrary.Generic as T
import Darcs.Test.Patch.Arbitrary.Generic
( NullPatch(..)
, MightBeEmptyHunk
, MightHaveDuplicate
, ArbitraryPrim
)
import Darcs.Test.Patch.RepoModel
import Darcs.Test.Patch.Arbitrary.Shrink
import Control.Applicative ( (<|>) )
import Test.QuickCheck
import Darcs.Test.Patch.WithState
import Darcs.Test.Patch.Types.Pair ( Pair(..) )
import Darcs.Patch.Witnesses.Sealed
import Darcs.Patch.Witnesses.Eq
import Darcs.Patch.Witnesses.Unsafe
import Darcs.Patch.Witnesses.Ordered
import Darcs.Patch.Prim ( isIdentity )
import Darcs.Patch.Prim.V1.Core ( FilePatchType( Hunk ) )
import qualified Darcs.Patch.Prim.V1.Core as Prim ( Prim( FP ) )
import qualified Darcs.Patch.V1.Prim as V1 ( Prim(..) )
import qualified Darcs.Patch.V2.Prim as V2 ( Prim(..) )
import Darcs.Test.Patch.V1Model
import Darcs.Util.Path
import Darcs.Test.Util.QuickCheck ( alpha, notIn, maybeOf )
import Darcs.UI.Commands.Replace ( defaultToks )
import Darcs.Patch.Prim ( PrimPatch, PrimConstruct(..) )
import Darcs.Patch.Apply ( ApplyState )
import Control.Monad ( guard )
import qualified Data.ByteString.Char8 as BC
import Data.Maybe ( fromJust, isJust )
type Prim1 = V1.Prim
type Prim2 = V2.Prim
type instance ModelOf Prim1 = V1Model
type instance ModelOf Prim2 = V1Model
instance ArbitraryPrim Prim1
instance ArbitraryPrim Prim2
instance NullPatch Prim.Prim where
nullPatch (Prim.FP _ fp) = nullPatch fp
nullPatch p | IsEq <- isIdentity p = IsEq
nullPatch _ = NotEq
deriving instance NullPatch Prim1
deriving instance NullPatch Prim2
instance NullPatch FilePatchType where
nullPatch (Hunk _ [] []) = unsafeCoerceP IsEq -- is this safe?
nullPatch _ = NotEq
instance MightBeEmptyHunk Prim.Prim where
isEmptyHunk (Prim.FP _ (Hunk _ [] [])) = True
isEmptyHunk _ = False
deriving instance MightBeEmptyHunk Prim1
deriving instance MightBeEmptyHunk Prim2
instance MightHaveDuplicate Prim1
instance MightHaveDuplicate Prim2
-- TODO add some useful shrinking, at least to
-- shrinkAtEnd/shrinkAtStart
instance Shrinkable Prim.Prim where
shrinkInternally _ = []
shrinkAtEnd _ = []
shrinkAtStart _ = []
deriving instance Shrinkable V1.Prim
deriving instance Shrinkable V2.Prim
----------------------------------------------------------------------
-- * QuickCheck generators
----------------------------------------------------------------------
-- ** FilePatchType generators
aHunk :: Content -> Gen (Int, [BC.ByteString], [BC.ByteString])
aHunk content
= (sized $ \n ->
do pos <- choose (1, contentLen+1)
let prefixLen = pos-1
restLen = contentLen-prefixLen
oldLen <- frequency
[ (75, choose (0, min restLen n))
-- produces small hunks common in real editing
, (25, choose (0, min 10 restLen))
]
-- newLen choice aims to cover all possibilities, that is,
-- remove less/the same/more than added and empty the file.
newLen <- frequency
[ ( 54
, choose (1,min 1 n)
)
, ( if oldLen /= 0 then 42 else 0
, choose (1,min 1 oldLen)
)
, ( if oldLen /= 0 then 2 else 0
, return oldLen
)
, ( if oldLen /= 0 then 2 else 0
, return 0
)
]
new <- vectorOf newLen aLine
let old = take oldLen $ drop prefixLen $ content
return (pos, old, new)) `suchThat` notEmptyHunk
where
contentLen = length content
notEmptyHunk (_,old,new) = not (null old && null new)
aTokReplace :: Content -> Gen (String, String, String)
aTokReplace []
= do w <- vectorOf 1 alpha
w' <- vectorOf 1 alpha
return (defaultToks, w, w')
aTokReplace content
= do let fileWords = concatMap BC.words content
wB <- elements fileWords
w' <- alphaBS `notIn` fileWords
return (defaultToks, BC.unpack wB, BC.unpack w')
where
alphaBS = do x <- alpha; return $ BC.pack [x]
----------------------------------------------------------------------
-- ** Prim generators
aHunkP :: PrimPatch prim => (AnchoredPath, File) -> Gen (prim wX wY)
aHunkP (path,file)
= do (pos, old, new) <- aHunk content
return $ hunk path pos old new
where
content = fileContent file
aTokReplaceP :: PrimPatch prim => (AnchoredPath,File) -> Gen (prim wX wY)
aTokReplaceP (path,file)
= do (tokchars, old, new) <- aTokReplace content
return $ tokreplace path tokchars old new
where
content = fileContent file
anAddFileP :: PrimPatch prim => (AnchoredPath,Dir) -> Gen (prim wX wY)
anAddFileP (path,dir)
= do newFilename <- aFilename `notIn` existing
let newPath = path `appendPath` newFilename
return $ addfile newPath
where
existing = map fst $ filterFiles $ dirContent dir
aRmFileP :: PrimPatch prim
=> AnchoredPath -- ^ Path of an empty file
-> prim wX wY
aRmFileP path = rmfile path
anAddDirP :: PrimPatch prim => (AnchoredPath,Dir) -> Gen (prim wX wY)
anAddDirP (path,dir)
= do newDirname <- aDirname `notIn` existing
let newPath = path `appendPath` newDirname
return $ adddir newPath
where
existing = map fst $ filterDirs $ dirContent dir
aRmDirP :: PrimPatch prim
=> AnchoredPath -- ^ Path of an empty directory
-> prim wX wY
aRmDirP path = rmdir path
aMoveP :: PrimPatch prim
=> Gen Name -> AnchoredPath -> (AnchoredPath,Dir) -> Gen (prim wX wY)
aMoveP nameGen oldPath (dirPath,dir)
= do newName <- nameGen `notIn` existing
let newPath = dirPath `appendPath` newName
return $ move oldPath newPath
where
existing = map fst $ dirContent dir
aModelShrink :: V1Model wX -> [Sealed (Prim.Prim wX)]
aModelShrink repo =
aModelShrinkName repo <|>
aModelDeleteFile repo <|>
aModelDeleteDir repo <|>
aModelShrinkFileContent repo
shrinkPath :: AnchoredPath -> [AnchoredPath]
shrinkPath (AnchoredPath ps) = do
ps' <- shrinkList shrinkName ps
guard (not $ null ps')
return $ AnchoredPath ps'
shrinkName :: Name -> [Name]
shrinkName n = do
n' <- shrink (BC.unpack . encodeWhiteName $ n)
guard (n' /= ".")
guard (not $ null n')
either (const []) (:[]) $ decodeWhiteName $ BC.pack n'
aModelShrinkName :: V1Model wX -> [Sealed (Prim.Prim wX)]
aModelShrinkName repo = do
(oldPath, _) <- list repo
newPath <- shrinkPath oldPath
guard (newPath `notElem` map fst (list repo))
return $ Sealed $ move oldPath newPath
aModelDeleteFile :: V1Model wX -> [Sealed (Prim.Prim wX)]
aModelDeleteFile repo = do
(path, _) <- filterFiles (list repo)
return $ Sealed $ rmfile path
aModelDeleteDir :: V1Model wX -> [Sealed (Prim.Prim wX)]
aModelDeleteDir repo = do
(path, _) <- filterDirs (list repo)
return $ Sealed $ rmdir path
aModelShrinkFileContent :: V1Model wX -> [Sealed (Prim.Prim wX)]
aModelShrinkFileContent repo = do
(path, file) <- filterFiles (list repo)
(pos, lineToRemove) <- zip [1..] $ fileContent file
(return (Sealed $ hunk path pos [lineToRemove] [])
<|>
do
smaller <- BC.pack <$> shrink (BC.unpack lineToRemove)
return $ Sealed $ hunk path pos [lineToRemove] [smaller])
-- | Generates any type of 'prim' patch, except binary and setpref patches.
aPrim :: forall prim wX . (PrimPatch prim, ApplyState prim ~ RepoState V1Model)
=> V1Model wX -> Gen (Sealed (WithEndState V1Model (prim wX)))
aPrim repo
= do mbFile <- maybeOf repoFiles
mbEmptyFile <- maybeOf $ filter (isEmpty . snd) repoFiles
dir <- elements (rootDir:repoDirs)
mbOldDir <- maybeOf repoDirs
mbEmptyDir <- maybeOf $ filter (isEmpty . snd) repoDirs
patch <- frequency
[ ( if isJust mbFile then 12 else 0
, aHunkP $ fromJust mbFile
)
, ( if isJust mbFile then 6 else 0
, aTokReplaceP $ fromJust mbFile
)
, ( 2
, anAddFileP dir
)
, ( if isJust mbEmptyFile then 12 else 0
, return $ aRmFileP $ fst $ fromJust mbEmptyFile
)
, ( 2
, anAddDirP dir
)
, ( if isJust mbEmptyDir then 10 else 0
, return $ aRmDirP $ fst $ fromJust mbEmptyDir
)
, ( if isJust mbFile then 3 else 0
, aMoveP aFilename (fst $ fromJust mbFile) dir
)
, let oldPath = fst $ fromJust mbOldDir in
( if isJust mbOldDir
&& not (oldPath `isPrefix` fst dir)
then 4 else 0
, aMoveP aDirname oldPath dir
)
]
let repo' = unFail $ repoApply repo patch
return $ seal $ WithEndState patch repo'
where
repoItems = list repo
repoFiles = filterFiles repoItems
repoDirs = filterDirs repoItems
rootDir = (anchoredRoot,root repo)
----------------------------------------------------------------------
-- *** Pairs of primitive patches
-- Try to generate commutable pairs of hunks
hunkPairP :: PrimPatch prim => (AnchoredPath, File) -> Gen ((prim :> prim) wX wY)
hunkPairP (path,file)
= do (l1, old1, new1) <- aHunk content
(delta, content') <- selectChunk (Hunk l1 old1 new1) content
(l2', old2, new2) <- aHunk content'
let l2 = l2'+delta
return (hunk path l1 old1 new1 :> hunk path l2 old2 new2)
where
content = fileContent file
selectChunk (Hunk l old new) content_
= elements [prefix, suffix]
where
start = l - 1
prefix = (0, take start content_)
suffix = (start + length new, drop (start + length old) content_)
selectChunk _ _ = error "impossible case"
aPrimPair :: ( PrimPatch prim
, ArbitraryState prim
, ApplyState prim ~ RepoState V1Model
, ModelOf prim ~ V1Model
)
=> V1Model wX
-> Gen (Sealed (WithEndState V1Model (Pair prim wX)))
aPrimPair repo
= do mbFile <- maybeOf repoFiles
frequency
[ ( if isJust mbFile then 1 else 0
, do p1 :> p2 <- hunkPairP $ fromJust mbFile
let repo' = unFail $ repoApply repo p1
repo'' = unFail $ repoApply repo' p2
return $ seal $ WithEndState (Pair (p1 :> p2)) repo''
)
, ( 1
, do
-- construct the underlying pair directly to avoid any
-- risk of indirectly calling arbitraryStatePair (which
-- would cause a loop).
Sealed (WithEndState pair repo') <- arbitraryState repo
return $ seal $ WithEndState (Pair pair) repo'
)
]
where
repoItems = list repo
repoFiles = filterFiles repoItems
----------------------------------------------------------------------
-- Arbitrary instances
type instance ModelOf Prim.Prim = V1Model
instance ShrinkModel Prim.Prim where
shrinkModelPatch s = aModelShrink s
-- Prim1
instance ArbitraryState Prim1 where
arbitraryState = aPrim
arbitraryStatePair = aPrimPair
instance ShrinkModel Prim1 where
shrinkModelPatch s = map (mapSeal V1.Prim) $ shrinkModelPatch s
instance PropagateShrink Prim1 Prim1 where
propagateShrink = propagatePrim
instance ArbitraryWS Prim1 where
arbitraryWS = makeWS2Gen aSmallRepo
-- Prim2
instance ArbitraryState Prim2 where
arbitraryState = aPrim
arbitraryStatePair = aPrimPair
instance ShrinkModel Prim2 where
shrinkModelPatch s = map (mapSeal V2.Prim) $ shrinkModelPatch s
instance PropagateShrink Prim2 Prim2 where
propagateShrink = propagatePrim
instance ArbitraryWS Prim2 where
arbitraryWS = makeWS2Gen aSmallRepo