summaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/Properties.hs813
1 files changed, 537 insertions, 276 deletions
diff --git a/tests/Properties.hs b/tests/Properties.hs
index 01adc7d..170bc36 100644
--- a/tests/Properties.hs
+++ b/tests/Properties.hs
@@ -13,9 +13,10 @@ import Control.Exception (assert)
import Control.Monad
import Test.QuickCheck hiding (promote)
import System.IO
-import System.Random
+import System.Random hiding (next)
import Text.Printf
-import Data.List (nub,sort,group,sort,intersperse,genericLength)
+import Data.List (nub,sort,sortBy,group,sort,intersperse,genericLength)
+import qualified Data.List as L
import Data.Char (ord)
import Data.Map (keys,elems)
import qualified Data.Map as M
@@ -23,12 +24,42 @@ import qualified Data.Map as M
-- ---------------------------------------------------------------------
-- QuickCheck properties for the StackSet
+-- Some general hints for creating StackSet properties:
+--
+-- * ops that mutate the StackSet are usually local
+-- * most ops on StackSet should either be trivially reversible, or
+-- idempotent, or both.
+
+--
+-- The all important Arbitrary instance for StackSet.
+--
+instance (Integral i, Integral s, Eq a, Arbitrary a) => Arbitrary (StackSet i a s) where
+ arbitrary = do
+ sz <- choose (1,10) -- number of workspaces
+ n <- choose (0,sz-1) -- pick one to be in focus
+ sc <- choose (1,sz) -- a number of physical screens
+ ls <- vector sz -- a vector of sz workspaces
+
+ -- pick a random item in each stack to focus
+ fs <- sequence [ if null s then return Nothing
+ else liftM Just (choose ((-1),length s-1))
+ | s <- ls ]
+
+ return $ fromList (fromIntegral n, fromIntegral sc,fs,ls)
+ coarbitrary = error "no coarbitrary for StackSet"
+
-- | fromList. Build a new StackSet from a list of list of elements,
-- keeping track of the currently focused workspace, and the total
-- number of workspaces. If there are duplicates in the list, the last
-- occurence wins.
-fromList :: (Integral i, Integral j, Ord a) => (i, Int, [Maybe a], [[a]]) -> StackSet i j a
+--
+-- 'o' random workspace
+-- 'm' number of physical screens
+-- 'fs' random focused window on each workspace
+-- 'xs' list of list of windows
+--
+fromList :: (Integral i, Integral s, Eq a) => (i, s, [Maybe Int], [[a]]) -> StackSet i a s
fromList (_,_,_,[]) = error "Cannot build a StackSet from an empty list"
fromList (n,m,fs,xs) | n < 0 || n >= genericLength xs
@@ -36,235 +67,392 @@ fromList (n,m,fs,xs) | n < 0 || n >= genericLength xs
| m < 1 || m > genericLength xs
= error $ "Can't have more screens than workspaces: " ++ show (m, length xs)
--- 'o' random workspace
--- 'fs' random focused window on each workspace
---
fromList (o,m,fs,xs) =
let s = view o $
foldr (\(i,ys) s ->
- foldr (\a t -> insert a i t) s ys)
- (empty (length xs) m) (zip [0..] xs)
-
- in foldr (\f s -> case f of
- Nothing -> s
- Just w -> raiseFocus w s) s fs
-
--- ---------------------------------------------------------------------
+ foldr insertLeft (view i s) ys)
+ (new (genericLength xs) m) (zip [0..] xs)
+ in foldr (\f t -> case f of
+ Nothing -> t
+ Just i -> foldr (const focusLeft) t [0..i] ) s fs
--- | /O(n)/. Number of stacks
-size :: T -> Int
-size = M.size . stacks
-
--- | Height of stack 'n'
-height :: Int -> T -> Int
-height i w = maybe 0 length (index i w)
+------------------------------------------------------------------------
--- build (non-empty) StackSets with between 1 and 100 stacks
--
--- StackSet
--- { current :: i
--- , screen2ws:: !(M.Map j i) -- ^ screen -> workspace
--- , ws2screen:: !(M.Map i j) -- ^ workspace -> screen map
--- , stacks :: !(M.Map i ([a], [a])) -- ^ screen -> (floating, normal)
--- , cache :: !(M.Map a i) -- ^ a cache of windows back to their stacks
--- }
+-- Just generate StackSets with Char elements.
--
--- Use 'raiseFocus' to bring focus to the front'
---
-instance (Integral i, Integral j, Ord a, Arbitrary a) => Arbitrary (StackSet i j a) where
- arbitrary = do
- sz <- choose (1,20)
- n <- choose (0,sz-1)
- sc <- choose (1,sz)
- ls <- vector sz
+type T = StackSet Int Char Int
- -- pick a random element of each stack to focus.
- fs <- sequence [ if null s then return Nothing
- else liftM Just (elements s)
- | s <- ls ]
-
- return $ fromList (fromIntegral n,sc,fs,ls)
- coarbitrary = error "no coarbitrary for StackSet"
+-- Useful operation, the non-local workspaces
+hidden x = [ w | w <- prev x ++ next x ] -- the hidden workspaces
--- Invariants:
+-- Basic data invariants of the StackSet
--
--- * no element should ever appear more than once in a StackSet
--- * the current index should always be valid
+-- With the new zipper-based StackSet, tracking focus is no longer an
+-- issue: the data structure enforces focus by construction.
--
--- All operations must preserve this.
+-- But we still need to ensure there are no duplicates, and master/and
+-- the xinerama mapping aren't checked by the data structure at all.
--
-invariant (w :: T) = inBounds w && noDuplicates allWindows
- where
- allWindows = concatMap (uncurry (++)) . M.elems . stacks $ w
- noDuplicates ws = nub ws == ws
- inBounds x = current x >= 0 && current x < sz where sz = M.size (stacks x)
+-- * no element should ever appear more than once in a StackSet
+-- * the xinerama screen map should be:
+-- -- keys should always index valid workspaces
+-- -- monotonically ascending in the elements
+-- * the current workspace should be a member of the xinerama screens
+--
+invariant (s :: T) = and
+ -- no duplicates
+ [ noDuplicates
--- test generator
-prop_invariant = invariant
+ -- all this xinerama stuff says we don't have the right structure
+ , currentIsVisible
+ , validScreens
+ , validWorkspaces
+ , inBounds
+ ]
+ where
+ ws = [ focus t : left t ++ right t
+ | w <- current s : prev s ++ next s, let t = stack w, t /= Empty ]
+ noDuplicates = nub ws == ws
--- empty StackSets have no windows in them
-prop_empty n m = n > 0 && m > 0 ==> all (null . uncurry (++)) (M.elems (stacks x))
- where x = empty n m :: T
+ -- xinerama invariants:
--- empty StackSets always have focus on workspace 0
-prop_empty_current n m = n > 0 && m > 0 ==> current x == 0
- where x = empty n m :: T
+ currentIsVisible = M.member (tag (current s)) (screens s)
-prop_member1 i n m = n > 0 && m > 0 ==> member i (push i x)
- where x = empty n m :: T
+ validScreens = monotonic . sort . M.elems . screens $ s
-prop_member2 i x = not (member i (delete i x))
- where _ = x :: T
+ validWorkspaces = and [ w `elem` allworkspaces | w <- (M.keys . screens) s ]
+ where allworkspaces = map tag $ current s : prev s ++ next s
-prop_member3 i n m = member i (empty n m :: T) == False
+ inBounds = and [ w >=0 && w < size s | (w,sc) <- M.assocs (screens s) ]
-prop_sizepush is n m = n > 0 ==> size (foldr push x is ) == n
- where x = empty n m :: T
+monotonic [] = True
+monotonic (x:[]) = True
+monotonic (x:y:zs) | x == y-1 = monotonic (y:zs)
+ | otherwise = False
-prop_currentpush is n m = n > 0 ==>
- height (current x) (foldr push x js) == length js
- where
- js = nub is
- x = empty n m :: T
+prop_invariant = invariant
-prop_push_idem i (x :: T) = push i x == push i (push i x)
+-- and check other ops preserve invariants
+prop_empty_I (n :: Positive Int) = forAll (choose (1,fromIntegral n)) $ \m ->
+ invariant $ new (fromIntegral n) m
-prop_pushpeek x is = not (null is) ==> fromJust (peek (foldr push x is)) == head is
- where _ = x :: T
+prop_view_I (n :: NonNegative Int) (x :: T) =
+ fromIntegral n < size x ==> invariant $ view (fromIntegral n) x
-prop_peekmember x = case peek x of
- Just w -> member w x
- Nothing -> True {- then we don't know anything -}
- where _ = x :: T
+prop_focusLeft_I (n :: NonNegative Int) (x :: T) =
+ invariant $ foldr (const focusLeft) x [1..n]
+prop_focusRight_I (n :: NonNegative Int) (x :: T) =
+ invariant $ foldr (const focusRight) x [1..n]
-prop_peek_peekStack n x =
- if current x == n then peekStack n x == peek x
- else True -- so we don't exhaust
- where _ = x :: T
+prop_focus_I (n :: NonNegative Int) (x :: T) =
+ case peek x of
+ Nothing -> True
+ Just _ -> let w = focus . stack . current $ foldr (const focusLeft) x [1..n]
+ in invariant $ focusWindow w x
-prop_notpeek_peekStack n x = current x /= n && isJust (peek x) ==> peekStack n x /= peek x
- where _ = x :: T
+prop_insertLeft_I n (x :: T) = invariant $ insertLeft n x
-------------------------------------------------------------------------
+prop_delete_I (x :: T) = invariant $
+ case peek x of
+ Nothing -> x
+ Just i -> delete i x
-type T = StackSet Int Int Char
+prop_swap_I (x :: T) = invariant $ swap x
-prop_delete_uniq i x = not (member i x) ==> delete i x == x
- where _ = x :: T
+prop_shift_I (n :: NonNegative Int) (x :: T) =
+ fromIntegral n < size x ==> invariant $ shift (fromIntegral n) x
-{-
-TODO: enable this property when we have a better story about focus.
-prop_delete_push i x = not (member i x) ==> delete i (push i x) == x
- where _ = x :: T
--}
+-- ---------------------------------------------------------------------
+-- 'new'
-prop_delete_push i x = not (member i x) ==> delete i (push i x) == x
- where _ = x :: T
+-- empty StackSets have no windows in them
+prop_empty (n :: Positive Int)
+ (m :: Positive Int) =
+ all (== Empty) [ stack w | w <- current x : prev x ++ next x ]
-prop_delete2 i x =
- delete i x == delete i (delete i x)
- where _ = x :: T
+ where x = new (fromIntegral n) (fromIntegral m) :: T
-prop_focus1 i x = member i x ==> peek (raiseFocus i x) == Just i
- where _ = x :: T
+-- empty StackSets always have focus on workspace 0
+prop_empty_current (n :: Positive Int)
+ (m :: Positive Int) = tag (current x) == 0
+ where x = new (fromIntegral n) (fromIntegral m) :: T
--- rotation is reversible in two directions
-prop_rotaterotate1 (x :: T) = rotate LT (rotate GT x') == x'
- where x' = rotate LT x
-prop_rotaterotate2 (x :: T) = rotate GT (rotate LT x') == x'
- where x' = rotate GT x
+-- no windows will be a member of an empty workspace
+prop_member_empty i (n :: Positive Int) (m :: Positive Int)
+ = member i (new (fromIntegral n) (fromIntegral m) :: T) == False
--- rotation through the height of a stack gets us back to the start
-prop_rotate_all (x :: T) = f (f x) == f x
- where
- n = height (current x) x
- f x' = foldr (\_ y -> rotate GT y) x' [1..n]
+-- ---------------------------------------------------------------------
+-- viewing workspaces
+-- view sets the current workspace to 'n'
+prop_view_current (x :: T) (n :: NonNegative Int) = i < size x ==>
+ tag (current (view i x)) == i
+ where
+ i = fromIntegral n
-prop_viewview r x =
- let n = current x
- sz = size x
- i = r `mod` sz
- in view n (view (fromIntegral i) x) == x
+-- view *only* sets the current workspace, and touches Xinerama.
+-- no workspace contents will be changed.
+prop_view_local (x :: T) (n :: NonNegative Int) = i < size x ==>
+ workspaces x == workspaces (view i x)
+ where
+ workspaces a = sortBy (\s t -> tag s `compare` tag t) $
+ current a : prev a ++ next a
+ i = fromIntegral n
- where _ = x :: T
+-- view should result in a visible xinerama screen
+prop_view_xinerama (x :: T) (n :: NonNegative Int) = i < size x ==>
+ M.member i (screens (view i x))
+ where
+ i = fromIntegral n
+-- view is idempotent
prop_view_idem (x :: T) r =
let i = fromIntegral $ r `mod` sz
sz = size x
in view i (view i x) == (view i x)
-{-
-TODO: enable this property when we have a better story for focus.
+-- view is reversible
+prop_view_reversible r (x :: T) = view n (view i x) == x
+ where n = tag (current x)
+ sz = size x
+ i = fromIntegral $ r `mod` sz
-prop_shift_reversible r (x :: T) =
- let i = fromIntegral $ r `mod` sz
- sz = size x
- n = current x
- in height n x > 0 ==> (view n . shift n . view i . shift i) x == x
--}
+-- ---------------------------------------------------------------------
+-- Xinerama
+-- every screen should yield a valid workspace
+prop_lookupWorkspace (n :: NonNegative Int) (x :: T) =
+ s < M.size (screens x) ==>
+ fromJust (lookupWorkspace s x) `elem` (map tag $ current x : prev x ++ next x)
+ where
+ s = fromIntegral n
-prop_fullcache x = cached == allvals where
- cached = sort . keys $ cache x
- allvals = sort . concat . map (uncurry (++)) . elems $ stacks x
- _ = x :: T
+-- ---------------------------------------------------------------------
+-- peek/index
-prop_currentwsvisible x = (current x) `elem` (visibleWorkspaces x)
- where _ = x :: T
+-- peek either yields nothing on the Empty workspace, or Just a valid window
+prop_member_peek (x :: T) =
+ case peek x of
+ Nothing -> True {- then we don't know anything -}
+ Just i -> member i x
-prop_ws2screen_screen2ws x = (ws == ws') && (sc == sc')
- where ws = sort . keys $ ws2screen x
- ws' = sort . elems $ screen2ws x
- sc = sort . keys $ screen2ws x
- sc' = sort . elems $ ws2screen x
- _ = x :: T
+-- ---------------------------------------------------------------------
+-- index
+
+-- the list returned by index should be the same length as the actual
+-- windows kept in the zipper
+prop_index_length (x :: T) =
+ case it of
+ Empty -> length (index x) == 0
+ Node {} -> length (index x) == length list
+ where
+ it = stack . current $ x
+ list = focus it : left it ++ right it
-prop_screenworkspace x = all test [0..((fromIntegral $ size x)-1)]
- where test ws = case screen ws x of
- Nothing -> True
- Just sc -> workspace sc x == Just ws
- _ = x :: T
+-- ---------------------------------------------------------------------
+-- rotating focus
+--
+-- Unfortunately, in the presence of wrapping of focus, we don't have a
+-- simple identity where focusLeft . focusRight == id, as the focus
+-- operations repartition the structure on wrapping.
+--
+-- Note the issue with equality on Stacks given the wrapping semantics.
+--
+-- [1,2,3] ++ [4] ++ [5]
+--
+-- should be equivalent to:
+--
+-- [] ++ [4] ++ [5,1,2,3]
+--
+-- However, we can simply normalise the list, taking focus as the head,
+-- and the items should be the same.
+
+-- So we normalise the stack on the current workspace.
+-- We normalise by moving everything to the 'left' of the focused item,
+-- to the right.
+-- normal (x :: T) = modify Empty (\c -> case c of
+-- Node t ls rs -> Node t [] (rs ++ reverse ls)) x
+normal = id
+
+-- master/focus
+--
+-- The tiling order, and master window, of a stack is unaffected by focus changes.
+--
+prop_focus_left_master (n :: NonNegative Int) (x::T) =
+ index (foldr (const focusLeft) x [1..n]) == index x
+prop_focus_right_master (n :: NonNegative Int) (x::T) =
+ index (foldr (const focusRight) x [1..n]) == index x
+prop_focusWindow_master (n :: NonNegative Int) (x :: T) =
+ case peek x of
+ Nothing -> True
+ Just _ -> let s = index x
+ i = fromIntegral n `mod` length s
+ in index (focusWindow (s !! i) x) == index x
+
+-- shifting focus is trivially reversible
+prop_focus_left (x :: T) = normal (focusLeft (focusRight x)) == normal x
+prop_focus_right (x :: T) = normal (focusRight (focusLeft x)) == normal x
+
+-- focusWindow actually leaves the window focused...
+prop_focusWindow_works (n :: NonNegative Int) (x :: T) =
+ case peek x of
+ Nothing -> True
+ Just _ -> let s = index x
+ i = fromIntegral n `mod` length s
+ in (focus . stack . current) (focusWindow (s !! i) x) == (s !! i)
-prop_swap a b xs = swap a b (swap a b ys) == ys
- where ys = nub xs :: [Int]
+-- rotation through the height of a stack gets us back to the start
+prop_focus_all_l (x :: T) = normal (foldr (const focusLeft) x [1..n]) == normal x
+ where n = length (index x)
+prop_focus_all_r (x :: T) = normal (foldr (const focusRight) x [1..n]) == normal x
+ where n = length (index x)
-------------------------------------------------------------------------
+-- prop_rotate_all (x :: T) = f (f x) == f x
+-- f x' = foldr (\_ y -> rotate GT y) x' [1..n]
--- promote is idempotent
-prop_promote2 x = promote (promote x) == (promote x)
- where _ = x :: T
+-- focus is local to the current workspace
+prop_focus_local (x :: T) = hidden (focusRight x) == hidden x
--- focus doesn't change
-prop_promotefocus x = focus (promote x) == focus x
- where _ = x :: T
+prop_focusWindow_local (n :: NonNegative Int) (x::T ) =
+ case peek x of
+ Nothing -> True
+ Just _ -> let s = index x
+ i = fromIntegral n `mod` length s
+ in hidden (focusWindow (s !! i) x) == hidden x
--- screen certainly should't change
-prop_promotecurrent x = current (promote x) == current x
- where _ = x :: T
+-- ---------------------------------------------------------------------
+-- member/findIndex
--- the physical screen doesn't change
-prop_promotescreen n x = screen n (promote x) == screen n x
- where _ = x :: T
+--
+-- For all windows in the stackSet, findIndex should identify the
+-- correct workspace
+--
+prop_findIndex (x :: T) =
+ and [ tag w == fromJust (findIndex i x)
+ | w <- current x : prev x ++ next x
+ , let t = stack w
+ , t /= Empty
+ , i <- focus (stack w) : left (stack w) ++ right (stack w)
+ ]
--- promote doesn't mess with other windows
-prop_promote_raise_id x = (not . null . fromMaybe [] . flip index x . current $ x) ==>
- (promote . promote . promote) x == promote x
- where _ = x :: T
+-- ---------------------------------------------------------------------
+-- 'insert'
+
+-- inserting a item into an empty stackset means that item is now a member
+prop_insert_empty i (n :: Positive Int) (m :: Positive Int) = member i (insertLeft i x)
+ where x = new (fromIntegral n) (fromIntegral m) :: T
+
+-- insert should be idempotent
+prop_insert_idem i (x :: T) = insertLeft i x == insertLeft i (insertLeft i x)
+
+-- insert when an item is a member should leave the stackset unchanged
+prop_insert_duplicate i (x :: T) = member i x ==> insertLeft i x == x
-- push shouldn't change anything but the current workspace
-prop_push_local (x :: T) i = not (member i x) ==> hidden x == hidden (push i x)
+prop_insert_local (x :: T) i = not (member i x) ==> hidden x == hidden (insertLeft i x)
+
+-- Inserting a (unique) list of items into an empty stackset should
+-- result in the last inserted element having focus.
+prop_insert_peek (n :: Positive Int) (m :: Positive Int) (NonEmptyNubList is) =
+ peek (foldr insertLeft x is) == Just (head is)
+ where
+ x = new (fromIntegral n) (fromIntegral m) :: T
+
+-- insert >> delete is the identity, when i `notElem` .
+-- Except for the 'master', which is reset on insert and delete.
+--
+prop_insert_delete n x = not (member n x) ==> delete n (insertLeft n y) == (y :: T)
+ where
+ y = swap x -- sets the master window to the current focus.
+ -- otherwise, we don't have a rule for where master goes.
+
+-- inserting n elements increases current stack size by n
+prop_size_insert is (n :: Positive Int) (m :: Positive Int) =
+ size (foldr insertLeft x ws ) == (length ws)
where
- hidden w = [ index n w | n <- [0 ..sz-1], n /= current w ]
- sz = M.size (stacks x)
+ ws = nub is
+ x = new (fromIntegral n) (fromIntegral m) :: T
+ size = length . index
+
+
+-- ---------------------------------------------------------------------
+-- 'delete'
+
+-- deleting the current item removes it.
+prop_delete x =
+ case peek x of
+ Nothing -> True
+ Just i -> not (member i (delete i x))
+ where _ = x :: T
+
+-- delete is reversible with 'insert'.
+-- It is the identiy, except for the 'master', which is reset on insert and delete.
+--
+prop_delete_insert (x :: T) =
+ case peek x of
+ Nothing -> True
+ Just n -> insertLeft n (delete n y) == y
+ where
+ y = swap x
+
+-- delete should be local
+prop_delete_local (x :: T) =
+ case peek x of
+ Nothing -> True
+ Just i -> hidden x == hidden (delete i x)
+
+-- ---------------------------------------------------------------------
+-- swap: setting the master window
+-- prop_swap_reversible a b xs = swap a b (swap a b ys) == ys
+-- where ys = nub xs :: [Int]
+
+-- swap doesn't change focus
+prop_swap_focus (x :: T)
+ = case peek x of
+ Nothing -> True
+ Just f -> focus (stack (current (swap x))) == f
+
+-- swap is local
+prop_swap_local (x :: T) = hidden x == hidden (swap x)
+
+-- TODO swap is reversible
+-- swap is reversible, but involves moving focus back the window with
+-- master on it. easy to do with a mouse...
+{-
+prop_promote_reversible x b = (not . null . fromMaybe [] . flip index x . current $ x) ==>
+ (raiseFocus y . promote . raiseFocus z . promote) x == x
+ where _ = x :: T
+ dir = if b then LT else GT
+ (Just y) = peek x
+ (Just (z:_)) = flip index x . current $ x
+-}
+
+prop_swap_idempotent (x :: T) = swap (swap x) == swap x
+
+-- ---------------------------------------------------------------------
+-- shift
+
+-- shift is fully reversible on current window, when focus and master
+-- are the same. otherwise, master may move.
+prop_shift_reversible (r :: Int) (x :: T) =
+ let i = fromIntegral $ r `mod` sz
+ sz = size y
+ n = tag (current y)
+ in case peek y of
+ Nothing -> True
+ Just _ -> (view n . shift n . view i . shift i) y == y
+ where
+ y = swap x
------------------------------------------------------------------------
-- some properties for layouts:
-- 1 window should always be tiled fullscreen
+{-
prop_tile_fullscreen rect = tile pct rect 1 1 == [rect]
-- multiple windows
@@ -287,61 +475,7 @@ noOverlaps xs = and [ verts a `notOverlap` verts b
= (top1 < bottom2 || top2 < bottom1)
|| (right1 < left2 || right2 < left1)
-
-------------------------------------------------------------------------
-
-instance Arbitrary Char where
- arbitrary = choose ('a','z')
- coarbitrary n = coarbitrary (ord n)
-
-instance Random Word8 where
- randomR = integralRandomR
- random = randomR (minBound,maxBound)
-
-instance Arbitrary Word8 where
- arbitrary = choose (minBound,maxBound)
- coarbitrary n = variant (fromIntegral ((fromIntegral n) `rem` 4))
-
-instance Random Word64 where
- randomR = integralRandomR
- random = randomR (minBound,maxBound)
-
-instance Arbitrary Word64 where
- arbitrary = choose (minBound,maxBound)
- coarbitrary n = variant (fromIntegral ((fromIntegral n) `rem` 4))
-
-integralRandomR :: (Integral a, RandomGen g) => (a,a) -> g -> (a,g)
-integralRandomR (a,b) g = case randomR (fromIntegral a :: Integer,
- fromIntegral b :: Integer) g of
- (x,g) -> (fromIntegral x, g)
-
-instance Arbitrary Position where
- arbitrary = do n <- arbitrary :: Gen Word8
- return (fromIntegral n)
- coarbitrary = undefined
-
-instance Arbitrary Dimension where
- arbitrary = do n <- arbitrary :: Gen Word8
- return (fromIntegral n)
- coarbitrary = undefined
-
-instance Arbitrary Rectangle where
- arbitrary = do
- sx <- arbitrary
- sy <- arbitrary
- sw <- arbitrary
- sh <- arbitrary
- return $ Rectangle sx sy sw sh
- coarbitrary = undefined
-
-
-instance Arbitrary Rational where
- arbitrary = do
- n <- arbitrary
- d' <- arbitrary
- let d = if d' == 0 then 1 else d'
- return (n % d)
- coarbitrary = undefined
+-}
------------------------------------------------------------------------
@@ -349,81 +483,100 @@ main :: IO ()
main = do
args <- getArgs
let n = if null args then 100 else read (head args)
- results <- mapM (\(s,a) -> printf "%-25s: " s >> a n) tests
+ (results, passed) <- liftM unzip $ mapM (\(s,a) -> printf "%-25s: " s >> a n) tests
+ printf "Passed %d tests!\n" (sum passed)
when (not . and $ results) $ fail "Not all tests passed!"
where
- n = 100
tests =
- [("StackSet invariants", mytest prop_invariant)
- ,("empty is empty" , mytest prop_empty)
- ,("empty / current" , mytest prop_empty_current)
-
- ,("member/push ", mytest prop_member1)
- ,("member/peek ", mytest prop_peekmember)
- ,("member/delete ", mytest prop_member2)
- ,("member/empty ", mytest prop_member3)
-
- ,("size/push ", mytest prop_sizepush)
- ,("height/push ", mytest prop_currentpush)
- ,("push/peek ", mytest prop_pushpeek)
- ,("push is local" , mytest prop_push_local)
- ,("idempotent push" , mytest prop_push_idem)
-
- ,("peek/peekStack" , mytest prop_peek_peekStack)
- ,("not . peek/peekStack", mytest prop_notpeek_peekStack)
-
- ,("delete/not.member", mytest prop_delete_uniq)
- ,("delete idempotent", mytest prop_delete2)
- ,("delete.push identity" , mytest prop_delete_push)
-
- ,("focus", mytest prop_focus1)
-
- ,("rotate l >> rotate r", mytest prop_rotaterotate1)
- ,("rotate r >> rotate l", mytest prop_rotaterotate2)
- ,("rotate all", mytest prop_rotate_all)
-
- ,("view/view ", mytest prop_viewview)
- ,("view idem ", mytest prop_view_idem)
-
- -- disabled, for now ,("shift reversible ", mytest prop_shift_reversible)
-
- ,("fullcache ", mytest prop_fullcache)
- ,("currentwsvisible ", mytest prop_currentwsvisible)
- ,("ws screen mapping", mytest prop_ws2screen_screen2ws)
- ,("screen/workspace ", mytest prop_screenworkspace)
-
- ,("promote idempotent", mytest prop_promote2)
- ,("promote focus", mytest prop_promotefocus)
- ,("promote current", mytest prop_promotecurrent)
- ,("promote only swaps", mytest prop_promote_raise_id)
- ,("promote/screen" , mytest prop_promotescreen)
-
- ,("swap", mytest prop_swap)
-
-------------------------------------------------------------------------
+ [("StackSet invariants" , mytest prop_invariant)
+
+ ,("empty: invariant" , mytest prop_empty_I)
+ ,("empty is empty" , mytest prop_empty)
+ ,("empty / current" , mytest prop_empty_current)
+ ,("empty / member" , mytest prop_member_empty)
+
+ ,("view : invariant" , mytest prop_view_I)
+ ,("view sets current" , mytest prop_view_current)
+ ,("view idempotent" , mytest prop_view_idem)
+ ,("view reviersible" , mytest prop_view_reversible)
+ ,("view / xinerama" , mytest prop_view_xinerama)
+ ,("view is local" , mytest prop_view_local)
+
+ ,("valid workspace xinerama", mytest prop_lookupWorkspace)
+
+ ,("peek/member " , mytest prop_member_peek)
+
+ ,("index/length" , mytest prop_index_length)
+
+ ,("focus left : invariant", mytest prop_focusLeft_I)
+ ,("focus right: invariant", mytest prop_focusRight_I)
+ ,("focusWindow: invariant", mytest prop_focus_I)
+ ,("focus left/master" , mytest prop_focus_left_master)
+ ,("focus right/master" , mytest prop_focus_right_master)
+ ,("focusWindow master" , mytest prop_focusWindow_master)
+ ,("focus left/right" , mytest prop_focus_left)
+ ,("focus right/left" , mytest prop_focus_right)
+ ,("focus all left " , mytest prop_focus_all_l)
+ ,("focus all right " , mytest prop_focus_all_r)
+ ,("focus is local" , mytest prop_focus_local)
+ ,("focusWindow is local", mytest prop_focusWindow_local)
+ ,("focusWindow works" , mytest prop_focusWindow_works)
+
+ ,("findIndex" , mytest prop_findIndex)
+
+ ,("insert: invariant" , mytest prop_insertLeft_I)
+ ,("insert/new" , mytest prop_insert_empty)
+ ,("insert is idempotent", mytest prop_insert_idem)
+ ,("insert is reversible", mytest prop_insert_delete)
+ ,("insert is local" , mytest prop_insert_local)
+ ,("insert duplicates" , mytest prop_insert_duplicate)
+ ,("insert/peek " , mytest prop_insert_peek)
+ ,("insert/size" , mytest prop_size_insert)
+
+ ,("delete: invariant" , mytest prop_delete_I)
+ ,("delete/empty" , mytest prop_empty)
+ ,("delete/member" , mytest prop_delete)
+ ,("delete is reversible", mytest prop_delete_insert)
+ ,("delete is local" , mytest prop_delete_local)
+
+ ,("swap: invariant " , mytest prop_swap_I)
+ ,("swap id on focus" , mytest prop_swap_focus)
+ ,("swap is idempotent" , mytest prop_swap_idempotent)
+ ,("swap is local" , mytest prop_swap_local)
+
+ ,("shift: invariant" , mytest prop_shift_I)
+ ,("shift is reversible" , mytest prop_shift_reversible)
+{-
,("tile 1 window fullsize", mytest prop_tile_fullscreen)
,("tiles never overlap", mytest prop_tile_non_overlap)
+-}
]
+------------------------------------------------------------------------
+--
+-- QC driver
+--
+
debug = False
-mytest :: Testable a => a -> Int -> IO Bool
+mytest :: Testable a => a -> Int -> IO (Bool, Int)
mytest a n = mycheck defaultConfig
{ configMaxTest=n
- , configEvery= \n args -> if debug then show n ++ ":\n" ++ unlines args else [] } a
+ , configEvery = \n args -> let s = show n in s ++ [ '\b' | _ <- s ] } a
+ -- , configEvery= \n args -> if debug then show n ++ ":\n" ++ unlines args else [] } a
-mycheck :: Testable a => Config -> a -> IO Bool
+mycheck :: Testable a => Config -> a -> IO (Bool, Int)
mycheck config a = do
rnd <- newStdGen
mytests config (evaluate a) rnd 0 0 []
-mytests :: Config -> Gen Result -> StdGen -> Int -> Int -> [[String]] -> IO Bool
+mytests :: Config -> Gen Result -> StdGen -> Int -> Int -> [[String]] -> IO (Bool, Int)
mytests config gen rnd0 ntest nfail stamps
- | ntest == configMaxTest config = done "OK," ntest stamps >> return True
- | nfail == configMaxFail config = done "Arguments exhausted after" ntest stamps >> return True
+ | ntest == configMaxTest config = done "OK," ntest stamps >> return (True, ntest)
+ | nfail == configMaxFail config = done "Arguments exhausted after" ntest stamps >> return (True, ntest)
| otherwise =
do putStr (configEvery config ntest (arguments result)) >> hFlush stdout
case ok result of
@@ -436,7 +589,7 @@ mytests config gen rnd0 ntest nfail stamps
++ show ntest
++ " tests:\n"
++ unlines (arguments result)
- ) >> hFlush stdout >> return False
+ ) >> hFlush stdout >> return (False, ntest)
where
result = generate (configSize config ntest) rnd2 gen
(rnd1,rnd2) = split rnd0
@@ -466,3 +619,111 @@ done mesg ntest stamps = putStr ( mesg ++ " " ++ show ntest ++ " tests" ++ table
percentage n m = show ((100 * n) `div` m) ++ "%"
------------------------------------------------------------------------
+
+instance Arbitrary Char where
+ arbitrary = choose ('a','z')
+ coarbitrary n = coarbitrary (ord n)
+
+instance Random Word8 where
+ randomR = integralRandomR
+ random = randomR (minBound,maxBound)
+
+instance Arbitrary Word8 where
+ arbitrary = choose (minBound,maxBound)
+ coarbitrary n = variant (fromIntegral ((fromIntegral n) `rem` 4))
+
+instance Random Word64 where
+ randomR = integralRandomR
+ random = randomR (minBound,maxBound)
+
+instance Arbitrary Word64 where
+ arbitrary = choose (minBound,maxBound)
+ coarbitrary n = variant (fromIntegral ((fromIntegral n) `rem` 4))
+
+integralRandomR :: (Integral a, RandomGen g) => (a,a) -> g -> (a,g)
+integralRandomR (a,b) g = case randomR (fromIntegral a :: Integer,
+ fromIntegral b :: Integer) g of
+ (x,g) -> (fromIntegral x, g)
+
+instance Arbitrary Position where
+ arbitrary = do n <- arbitrary :: Gen Word8
+ return (fromIntegral n)
+ coarbitrary = undefined
+
+instance Arbitrary Dimension where
+ arbitrary = do n <- arbitrary :: Gen Word8
+ return (fromIntegral n)
+ coarbitrary = undefined
+
+instance Arbitrary Rectangle where
+ arbitrary = do
+ sx <- arbitrary
+ sy <- arbitrary
+ sw <- arbitrary
+ sh <- arbitrary
+ return $ Rectangle sx sy sw sh
+ coarbitrary = undefined
+
+instance Arbitrary Rational where
+ arbitrary = do
+ n <- arbitrary
+ d' <- arbitrary
+ let d = if d' == 0 then 1 else d'
+ return (n % d)
+ coarbitrary = undefined
+
+------------------------------------------------------------------------
+-- QC 2
+
+-- from QC2
+-- | NonEmpty xs: guarantees that xs is non-empty.
+newtype NonEmptyList a = NonEmpty [a]
+ deriving ( Eq, Ord, Show, Read )
+
+instance Arbitrary a => Arbitrary (NonEmptyList a) where
+ arbitrary = NonEmpty `fmap` (arbitrary `suchThat` (not . null))
+ coarbitrary = undefined
+
+newtype NonEmptyNubList a = NonEmptyNubList [a]
+ deriving ( Eq, Ord, Show, Read )
+
+instance (Eq a, Arbitrary a) => Arbitrary (NonEmptyNubList a) where
+ arbitrary = NonEmptyNubList `fmap` ((liftM nub arbitrary) `suchThat` (not . null))
+ coarbitrary = undefined
+
+
+type Positive a = NonZero (NonNegative a)
+
+newtype NonZero a = NonZero a
+ deriving ( Eq, Ord, Num, Integral, Real, Enum, Show, Read )
+
+instance (Num a, Ord a, Arbitrary a) => Arbitrary (NonZero a) where
+ arbitrary = fmap NonZero $ arbitrary `suchThat` (/= 0)
+ coarbitrary = undefined
+
+newtype NonNegative a = NonNegative a
+ deriving ( Eq, Ord, Num, Integral, Real, Enum, Show, Read )
+
+instance (Num a, Ord a, Arbitrary a) => Arbitrary (NonNegative a) where
+ arbitrary =
+ frequency
+ [ (5, (NonNegative . abs) `fmap` arbitrary)
+ , (1, return 0)
+ ]
+ coarbitrary = undefined
+
+-- | Generates a value that satisfies a predicate.
+suchThat :: Gen a -> (a -> Bool) -> Gen a
+gen `suchThat` p =
+ do mx <- gen `suchThatMaybe` p
+ case mx of
+ Just x -> return x
+ Nothing -> sized (\n -> resize (n+1) (gen `suchThat` p))
+
+-- | Tries to generate a value that satisfies a predicate.
+suchThatMaybe :: Gen a -> (a -> Bool) -> Gen (Maybe a)
+gen `suchThatMaybe` p = sized (try 0 . max 1)
+ where
+ try _ 0 = return Nothing
+ try k n = do x <- resize (2*k+n) gen
+ if p x then return (Just x) else try (k+1) (n-1)