mirror of
https://github.com/xmonad/xmonad.git
synced 2025-05-19 08:30:21 -07:00
Changes include: - compatibility with base-4 or 3 (base-2 untested) by using extensible-exceptions. This adds an additional dependency for users of ghc<6.10) - list all dependencies again when -ftesting (change in Cabal-1.8.0.2) - remove unnecessary imports - suppress -fwarn-unused-do-bind, with appropriate Cabal-1.8 workaround, described here: http://www.haskell.org/pipermail/xmonad/2010-January/009554.html
1192 lines
43 KiB
Haskell
1192 lines
43 KiB
Haskell
{-# OPTIONS -fglasgow-exts -w #-}
|
|
module Properties where
|
|
|
|
import XMonad.StackSet hiding (filter)
|
|
import XMonad.Layout
|
|
import XMonad.Core hiding (workspaces,trace)
|
|
import XMonad.Operations ( applyResizeIncHint, applyMaxSizeHint )
|
|
import qualified XMonad.StackSet as S (filter)
|
|
|
|
import Debug.Trace
|
|
import Data.Word
|
|
import Graphics.X11.Xlib.Types (Rectangle(..),Position,Dimension)
|
|
import Data.Ratio
|
|
import Data.Maybe
|
|
import System.Environment
|
|
import Control.Exception (assert)
|
|
import qualified Control.Exception.Extensible as C
|
|
import Control.Monad
|
|
import Test.QuickCheck hiding (promote)
|
|
import System.IO.Unsafe
|
|
import System.IO
|
|
import System.Random hiding (next)
|
|
import Text.Printf
|
|
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
|
|
|
|
-- ---------------------------------------------------------------------
|
|
-- 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 l, Arbitrary sd)
|
|
=> Arbitrary (StackSet i l a s sd) 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
|
|
lay <- arbitrary -- pick any layout
|
|
sds <- replicateM sc arbitrary
|
|
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, sds,fs,ls,lay)
|
|
|
|
|
|
-- | 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.
|
|
--
|
|
-- '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, [sd], [Maybe Int], [[a]], l) -> StackSet i l a s sd
|
|
fromList (_,_,_,[],_) = error "Cannot build a StackSet from an empty list"
|
|
|
|
fromList (o,m,fs,xs,l) =
|
|
let s = view o $
|
|
foldr (\(i,ys) s ->
|
|
foldr insertUp (view i s) ys)
|
|
(new l [0..genericLength xs-1] m) (zip [0..] xs)
|
|
in foldr (\f t -> case f of
|
|
Nothing -> t
|
|
Just i -> foldr (const focusUp) t [0..i] ) s fs
|
|
|
|
------------------------------------------------------------------------
|
|
|
|
--
|
|
-- Just generate StackSets with Char elements.
|
|
--
|
|
type T = StackSet (NonNegative Int) Int Char Int Int
|
|
|
|
-- Useful operation, the non-local workspaces
|
|
hidden_spaces x = map workspace (visible x) ++ hidden x
|
|
|
|
-- Basic data invariants of the StackSet
|
|
--
|
|
-- With the new zipper-based StackSet, tracking focus is no longer an
|
|
-- issue: the data structure enforces focus by construction.
|
|
--
|
|
-- 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.
|
|
--
|
|
-- * 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
|
|
|
|
-- all this xinerama stuff says we don't have the right structure
|
|
-- , validScreens
|
|
-- , validWorkspaces
|
|
-- , inBounds
|
|
]
|
|
|
|
where
|
|
ws = concat [ focus t : up t ++ down t
|
|
| w <- workspace (current s) : map workspace (visible s) ++ hidden s
|
|
, t <- maybeToList (stack w)] :: [Char]
|
|
noDuplicates = nub ws == ws
|
|
|
|
-- validScreens = monotonic . sort . M. . (W.current s : W.visible : W$ s
|
|
|
|
-- validWorkspaces = and [ w `elem` allworkspaces | w <- (M.keys . screens) s ]
|
|
-- where allworkspaces = map tag $ current s : prev s ++ next s
|
|
|
|
-- inBounds = and [ w >=0 && w < size s | (w,sc) <- M.assocs (screens s) ]
|
|
|
|
monotonic [] = True
|
|
monotonic (x:[]) = True
|
|
monotonic (x:y:zs) | x == y-1 = monotonic (y:zs)
|
|
| otherwise = False
|
|
|
|
prop_invariant = invariant
|
|
|
|
-- and check other ops preserve invariants
|
|
prop_empty_I (n :: Positive Int) l = forAll (choose (1,fromIntegral n)) $ \m ->
|
|
forAll (vector m) $ \ms ->
|
|
invariant $ new l [0..fromIntegral n-1] ms
|
|
|
|
prop_view_I (n :: NonNegative Int) (x :: T) =
|
|
invariant $ view (fromIntegral n) x
|
|
|
|
prop_greedyView_I (n :: NonNegative Int) (x :: T) =
|
|
invariant $ greedyView (fromIntegral n) x
|
|
|
|
prop_focusUp_I (n :: NonNegative Int) (x :: T) =
|
|
invariant $ foldr (const focusUp) x [1..n]
|
|
prop_focusMaster_I (n :: NonNegative Int) (x :: T) =
|
|
invariant $ foldr (const focusMaster) x [1..n]
|
|
prop_focusDown_I (n :: NonNegative Int) (x :: T) =
|
|
invariant $ foldr (const focusDown) x [1..n]
|
|
|
|
prop_focus_I (n :: NonNegative Int) (x :: T) =
|
|
case peek x of
|
|
Nothing -> True
|
|
Just _ -> let w = focus . fromJust . stack . workspace . current $ foldr (const focusUp) x [1..n]
|
|
in invariant $ focusWindow w x
|
|
|
|
prop_insertUp_I n (x :: T) = invariant $ insertUp n x
|
|
|
|
prop_delete_I (x :: T) = invariant $
|
|
case peek x of
|
|
Nothing -> x
|
|
Just i -> delete i x
|
|
|
|
prop_swap_master_I (x :: T) = invariant $ swapMaster x
|
|
|
|
prop_swap_left_I (n :: NonNegative Int) (x :: T) =
|
|
invariant $ foldr (const swapUp ) x [1..n]
|
|
prop_swap_right_I (n :: NonNegative Int) (x :: T) =
|
|
invariant $ foldr (const swapDown) x [1..n]
|
|
|
|
prop_shift_I (n :: NonNegative Int) (x :: T) =
|
|
n `tagMember` x ==> invariant $ shift (fromIntegral n) x
|
|
|
|
prop_shift_win_I (n :: NonNegative Int) (w :: Char) (x :: T) =
|
|
n `tagMember` x && w `member` x ==> invariant $ shiftWin (fromIntegral n) w x
|
|
|
|
|
|
-- ---------------------------------------------------------------------
|
|
-- 'new'
|
|
|
|
-- empty StackSets have no windows in them
|
|
prop_empty (EmptyStackSet x) =
|
|
all (== Nothing) [ stack w | w <- workspace (current x)
|
|
: map workspace (visible x) ++ hidden x ]
|
|
|
|
-- empty StackSets always have focus on first workspace
|
|
prop_empty_current (NonEmptyNubList ns) (NonEmptyNubList sds) l =
|
|
-- TODO, this is ugly
|
|
length sds <= length ns ==>
|
|
tag (workspace $ current x) == head ns
|
|
where x = new l ns sds :: T
|
|
|
|
-- no windows will be a member of an empty workspace
|
|
prop_member_empty i (EmptyStackSet x)
|
|
= member i x == False
|
|
|
|
-- ---------------------------------------------------------------------
|
|
-- viewing workspaces
|
|
|
|
-- view sets the current workspace to 'n'
|
|
prop_view_current (x :: T) (n :: NonNegative Int) = i `tagMember` x ==>
|
|
tag (workspace $ current (view i x)) == i
|
|
where
|
|
i = fromIntegral n
|
|
|
|
-- view *only* sets the current workspace, and touches Xinerama.
|
|
-- no workspace contents will be changed.
|
|
prop_view_local (x :: T) (n :: NonNegative Int) = i `tagMember` x ==>
|
|
workspaces x == workspaces (view i x)
|
|
where
|
|
workspaces a = sortBy (\s t -> tag s `compare` tag t) $
|
|
workspace (current a)
|
|
: map workspace (visible a) ++ hidden a
|
|
i = fromIntegral n
|
|
|
|
-- view should result in a visible xinerama screen
|
|
-- prop_view_xinerama (x :: T) (n :: NonNegative Int) = i `tagMember` x ==>
|
|
-- M.member i (screens (view i x))
|
|
-- where
|
|
-- i = fromIntegral n
|
|
|
|
-- view is idempotent
|
|
prop_view_idem (x :: T) (i :: NonNegative Int) = i `tagMember` x ==> view i (view i x) == (view i x)
|
|
|
|
-- view is reversible, though shuffles the order of hidden/visible
|
|
prop_view_reversible (i :: NonNegative Int) (x :: T) =
|
|
i `tagMember` x ==> normal (view n (view i x)) == normal x
|
|
where n = tag (workspace $ current x)
|
|
|
|
-- ---------------------------------------------------------------------
|
|
-- greedyViewing workspaces
|
|
|
|
-- greedyView sets the current workspace to 'n'
|
|
prop_greedyView_current (x :: T) (n :: NonNegative Int) = i `tagMember` x ==>
|
|
tag (workspace $ current (greedyView i x)) == i
|
|
where
|
|
i = fromIntegral n
|
|
|
|
-- greedyView leaves things unchanged for invalid workspaces
|
|
prop_greedyView_current_id (x :: T) (n :: NonNegative Int) = not (i `tagMember` x) ==>
|
|
tag (workspace $ current (greedyView i x)) == j
|
|
where
|
|
i = fromIntegral n
|
|
j = tag (workspace (current x))
|
|
|
|
-- greedyView *only* sets the current workspace, and touches Xinerama.
|
|
-- no workspace contents will be changed.
|
|
prop_greedyView_local (x :: T) (n :: NonNegative Int) = i `tagMember` x ==>
|
|
workspaces x == workspaces (greedyView i x)
|
|
where
|
|
workspaces a = sortBy (\s t -> tag s `compare` tag t) $
|
|
workspace (current a)
|
|
: map workspace (visible a) ++ hidden a
|
|
i = fromIntegral n
|
|
|
|
-- greedyView is idempotent
|
|
prop_greedyView_idem (x :: T) (i :: NonNegative Int) = i `tagMember` x ==> greedyView i (greedyView i x) == (greedyView i x)
|
|
|
|
-- greedyView is reversible, though shuffles the order of hidden/visible
|
|
prop_greedyView_reversible (i :: NonNegative Int) (x :: T) =
|
|
i `tagMember` x ==> normal (greedyView n (greedyView i x)) == normal x
|
|
where n = tag (workspace $ current x)
|
|
|
|
-- normalise workspace list
|
|
normal s = s { hidden = sortBy g (hidden s), visible = sortBy f (visible s) }
|
|
where
|
|
f = \a b -> tag (workspace a) `compare` tag (workspace b)
|
|
g = \a b -> tag a `compare` tag b
|
|
|
|
-- ---------------------------------------------------------------------
|
|
-- 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
|
|
|
|
-- ---------------------------------------------------------------------
|
|
-- peek/index
|
|
|
|
-- 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
|
|
|
|
-- ---------------------------------------------------------------------
|
|
-- 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 stack . workspace . current $ x of
|
|
Nothing -> length (index x) == 0
|
|
Just it -> length (index x) == length (focus it : up it ++ down it)
|
|
|
|
-- ---------------------------------------------------------------------
|
|
-- rotating focus
|
|
--
|
|
|
|
-- 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 focusUp) x [1..n]) == index x
|
|
prop_focus_right_master (n :: NonNegative Int) (x::T) =
|
|
index (foldr (const focusDown) x [1..n]) == index x
|
|
prop_focus_master_master (n :: NonNegative Int) (x::T) =
|
|
index (foldr (const focusMaster) 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) = (focusUp (focusDown x)) == x
|
|
prop_focus_right (x :: T) = (focusDown (focusUp x)) == x
|
|
|
|
-- focus master is idempotent
|
|
prop_focusMaster_idem (x :: T) = focusMaster x == focusMaster (focusMaster 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 . fromJust . stack . workspace . current) (focusWindow (s !! i) x) == (s !! i)
|
|
|
|
-- rotation through the height of a stack gets us back to the start
|
|
prop_focus_all_l (x :: T) = (foldr (const focusUp) x [1..n]) == x
|
|
where n = length (index x)
|
|
prop_focus_all_r (x :: T) = (foldr (const focusDown) x [1..n]) == 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]
|
|
|
|
-- focus is local to the current workspace
|
|
prop_focus_down_local (x :: T) = hidden_spaces (focusDown x) == hidden_spaces x
|
|
prop_focus_up_local (x :: T) = hidden_spaces (focusUp x) == hidden_spaces x
|
|
|
|
prop_focus_master_local (x :: T) = hidden_spaces (focusMaster x) == hidden_spaces x
|
|
|
|
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_spaces (focusWindow (s !! i) x) == hidden_spaces x
|
|
|
|
-- On an invalid window, the stackset is unmodified
|
|
prop_focusWindow_identity (n :: Char) (x::T ) =
|
|
not (n `member` x) ==> focusWindow n x == x
|
|
|
|
-- ---------------------------------------------------------------------
|
|
-- member/findTag
|
|
|
|
--
|
|
-- For all windows in the stackSet, findTag should identify the
|
|
-- correct workspace
|
|
--
|
|
prop_findIndex (x :: T) =
|
|
and [ tag w == fromJust (findTag i x)
|
|
| w <- workspace (current x) : map workspace (visible x) ++ hidden x
|
|
, t <- maybeToList (stack w)
|
|
, i <- focus t : up t ++ down t
|
|
]
|
|
|
|
prop_allWindowsMember w (x :: T) = (w `elem` allWindows x) ==> member w x
|
|
|
|
prop_currentTag (x :: T) =
|
|
currentTag x == tag (workspace (current x))
|
|
|
|
-- ---------------------------------------------------------------------
|
|
-- 'insert'
|
|
|
|
-- inserting a item into an empty stackset means that item is now a member
|
|
prop_insert_empty i (EmptyStackSet x)= member i (insertUp i x)
|
|
|
|
-- insert should be idempotent
|
|
prop_insert_idem i (x :: T) = insertUp i x == insertUp i (insertUp i x)
|
|
|
|
-- insert when an item is a member should leave the stackset unchanged
|
|
prop_insert_duplicate i (x :: T) = member i x ==> insertUp i x == x
|
|
|
|
-- push shouldn't change anything but the current workspace
|
|
prop_insert_local (x :: T) i = not (member i x) ==> hidden_spaces x == hidden_spaces (insertUp i x)
|
|
|
|
-- Inserting a (unique) list of items into an empty stackset should
|
|
-- result in the last inserted element having focus.
|
|
prop_insert_peek (EmptyStackSet x) (NonEmptyNubList is) =
|
|
peek (foldr insertUp x is) == Just (head is)
|
|
|
|
-- 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 (insertUp n y) == (y :: T)
|
|
where
|
|
y = swapMaster 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 (EmptyStackSet x) =
|
|
size (foldr insertUp x ws ) == (length ws)
|
|
where
|
|
ws = nub is
|
|
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 -> insertUp n (delete n y) == y
|
|
where
|
|
y = swapMaster x
|
|
|
|
-- delete should be local
|
|
prop_delete_local (x :: T) =
|
|
case peek x of
|
|
Nothing -> True
|
|
Just i -> hidden_spaces x == hidden_spaces (delete i x)
|
|
|
|
-- delete should not affect focus unless the focused element is what is being deleted
|
|
prop_delete_focus n (x :: T) = member n x && Just n /= peek x ==> peek (delete n x) == peek x
|
|
|
|
-- focus movement in the presence of delete:
|
|
-- when the last window in the stack set is focused, focus moves `up'.
|
|
-- usual case is that it moves 'down'.
|
|
prop_delete_focus_end (x :: T) =
|
|
length (index x) > 1
|
|
==>
|
|
peek (delete n y) == peek (focusUp y)
|
|
where
|
|
n = last (index x)
|
|
y = focusWindow n x -- focus last window in stack
|
|
|
|
-- focus movement in the presence of delete:
|
|
-- when not in the last item in the stack, focus moves down
|
|
prop_delete_focus_not_end (x :: T) =
|
|
length (index x) > 1 &&
|
|
n /= last (index x)
|
|
==>
|
|
peek (delete n x) == peek (focusDown x)
|
|
where
|
|
Just n = peek x
|
|
|
|
-- ---------------------------------------------------------------------
|
|
-- filter
|
|
|
|
-- preserve order
|
|
prop_filter_order (x :: T) =
|
|
case stack $ workspace $ current x of
|
|
Nothing -> True
|
|
Just s@(Stack i _ _) -> integrate' (S.filter (/= i) s) == filter (/= i) (integrate' (Just s))
|
|
|
|
-- ---------------------------------------------------------------------
|
|
-- swapUp, swapDown, swapMaster: reordiring windows
|
|
|
|
-- swap is trivially reversible
|
|
prop_swap_left (x :: T) = (swapUp (swapDown x)) == x
|
|
prop_swap_right (x :: T) = (swapDown (swapUp x)) == 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
|
|
-}
|
|
|
|
-- swap doesn't change focus
|
|
prop_swap_master_focus (x :: T) = peek x == (peek $ swapMaster x)
|
|
-- = case peek x of
|
|
-- Nothing -> True
|
|
-- Just f -> focus (stack (workspace $ current (swap x))) == f
|
|
prop_swap_left_focus (x :: T) = peek x == (peek $ swapUp x)
|
|
prop_swap_right_focus (x :: T) = peek x == (peek $ swapDown x)
|
|
|
|
-- swap is local
|
|
prop_swap_master_local (x :: T) = hidden_spaces x == hidden_spaces (swapMaster x)
|
|
prop_swap_left_local (x :: T) = hidden_spaces x == hidden_spaces (swapUp x)
|
|
prop_swap_right_local (x :: T) = hidden_spaces x == hidden_spaces (swapDown x)
|
|
|
|
-- rotation through the height of a stack gets us back to the start
|
|
prop_swap_all_l (x :: T) = (foldr (const swapUp) x [1..n]) == x
|
|
where n = length (index x)
|
|
prop_swap_all_r (x :: T) = (foldr (const swapDown) x [1..n]) == x
|
|
where n = length (index x)
|
|
|
|
prop_swap_master_idempotent (x :: T) = swapMaster (swapMaster x) == swapMaster x
|
|
|
|
-- ---------------------------------------------------------------------
|
|
-- shift
|
|
|
|
-- shift is fully reversible on current window, when focus and master
|
|
-- are the same. otherwise, master may move.
|
|
prop_shift_reversible i (x :: T) =
|
|
i `tagMember` x ==> case peek y of
|
|
Nothing -> True
|
|
Just _ -> normal ((view n . shift n . view i . shift i) y) == normal y
|
|
where
|
|
y = swapMaster x
|
|
n = tag (workspace $ current y)
|
|
|
|
------------------------------------------------------------------------
|
|
-- shiftMaster
|
|
|
|
-- focus/local/idempotent same as swapMaster:
|
|
prop_shift_master_focus (x :: T) = peek x == (peek $ shiftMaster x)
|
|
prop_shift_master_local (x :: T) = hidden_spaces x == hidden_spaces (shiftMaster x)
|
|
prop_shift_master_idempotent (x :: T) = shiftMaster (shiftMaster x) == shiftMaster x
|
|
-- ordering is constant modulo the focused window:
|
|
prop_shift_master_ordering (x :: T) = case peek x of
|
|
Nothing -> True
|
|
Just m -> L.delete m (index x) == L.delete m (index $ shiftMaster x)
|
|
|
|
-- ---------------------------------------------------------------------
|
|
-- shiftWin
|
|
|
|
-- shiftWin on current window is the same as shift
|
|
prop_shift_win_focus i (x :: T) =
|
|
i `tagMember` x ==> case peek x of
|
|
Nothing -> True
|
|
Just w -> shiftWin i w x == shift i x
|
|
|
|
-- shiftWin on a non-existant window is identity
|
|
prop_shift_win_indentity i w (x :: T) =
|
|
i `tagMember` x && not (w `member` x) ==> shiftWin i w x == x
|
|
|
|
-- shiftWin leaves the current screen as it is, if neither i is the tag
|
|
-- of the current workspace nor w on the current workspace
|
|
prop_shift_win_fix_current i w (x :: T) =
|
|
i `tagMember` x && w `member` x && i /= n && findTag w x /= Just n
|
|
==> (current $ x) == (current $ shiftWin i w x)
|
|
where
|
|
n = tag (workspace $ current x)
|
|
|
|
------------------------------------------------------------------------
|
|
-- properties for the floating layer:
|
|
|
|
prop_float_reversible n (x :: T) =
|
|
n `member` x ==> sink n (float n geom x) == x
|
|
where
|
|
geom = RationalRect 100 100 100 100
|
|
|
|
prop_float_geometry n (x :: T) =
|
|
n `member` x ==> let s = float n geom x
|
|
in M.lookup n (floating s) == Just geom
|
|
where
|
|
geom = RationalRect 100 100 100 100
|
|
|
|
prop_float_delete n (x :: T) =
|
|
n `member` x ==> let s = float n geom x
|
|
t = delete n s
|
|
in not (n `member` t)
|
|
where
|
|
geom = RationalRect 100 100 100 100
|
|
|
|
|
|
------------------------------------------------------------------------
|
|
|
|
prop_screens (x :: T) = n `elem` screens x
|
|
where
|
|
n = current x
|
|
|
|
prop_differentiate xs =
|
|
if null xs then differentiate xs == Nothing
|
|
else (differentiate xs) == Just (Stack (head xs) [] (tail xs))
|
|
where _ = xs :: [Int]
|
|
|
|
-- looking up the tag of the current workspace should always produce a tag.
|
|
prop_lookup_current (x :: T) = lookupWorkspace scr x == Just tg
|
|
where
|
|
(Screen (Workspace tg _ _) scr _) = current x
|
|
|
|
-- looking at a visible tag
|
|
prop_lookup_visible (x :: T) =
|
|
visible x /= [] ==>
|
|
fromJust (lookupWorkspace scr x) `elem` tags
|
|
where
|
|
tags = [ tag (workspace y) | y <- visible x ]
|
|
scr = last [ screen y | y <- visible x ]
|
|
|
|
|
|
-- ---------------------------------------------------------------------
|
|
-- testing for failure
|
|
|
|
-- and help out hpc
|
|
prop_abort x = unsafePerformIO $ C.catch (abort "fail")
|
|
(\(C.SomeException e) -> return $ show e == "xmonad: StackSet: fail" )
|
|
where
|
|
_ = x :: Int
|
|
|
|
-- new should fail with an abort
|
|
prop_new_abort x = unsafePerformIO $ C.catch f
|
|
(\(C.SomeException e) -> return $ show e == "xmonad: StackSet: non-positive argument to StackSet.new" )
|
|
where
|
|
f = new undefined{-layout-} [] [] `seq` return False
|
|
|
|
_ = x :: Int
|
|
|
|
-- prop_view_should_fail = view {- with some bogus data -}
|
|
|
|
-- screens makes sense
|
|
prop_screens_works (x :: T) = screens x == current x : visible x
|
|
|
|
------------------------------------------------------------------------
|
|
-- renaming tags
|
|
|
|
-- | Rename a given tag if present in the StackSet.
|
|
-- 408 renameTag :: Eq i => i -> i -> StackSet i l a s sd -> StackSet i l a s sd
|
|
|
|
prop_rename1 (x::T) o n = o `tagMember` x && not (n `tagMember` x) ==>
|
|
let y = renameTag o n x
|
|
in n `tagMember` y
|
|
|
|
-- |
|
|
-- Ensure that a given set of workspace tags is present by renaming
|
|
-- existing workspaces and\/or creating new hidden workspaces as
|
|
-- necessary.
|
|
--
|
|
prop_ensure (x :: T) l xs = let y = ensureTags l xs x
|
|
in and [ n `tagMember` y | n <- xs ]
|
|
|
|
-- adding a tag should create a new hidden workspace
|
|
prop_ensure_append (x :: T) l n =
|
|
not (n `tagMember` x)
|
|
==>
|
|
(hidden y /= hidden x -- doesn't append, renames
|
|
&&
|
|
and [ isNothing (stack z) && layout z == l | z <- hidden y, tag z == n ]
|
|
)
|
|
where
|
|
y = ensureTags l (n:ts) x
|
|
ts = [ tag z | z <- workspaces x ]
|
|
|
|
prop_mapWorkspaceId (x::T) = x == mapWorkspace id x
|
|
|
|
prop_mapWorkspaceInverse (x::T) = x == mapWorkspace predTag (mapWorkspace succTag x)
|
|
where predTag w = w { tag = pred $ tag w }
|
|
succTag w = w { tag = succ $ tag w }
|
|
|
|
prop_mapLayoutId (x::T) = x == mapLayout id x
|
|
|
|
prop_mapLayoutInverse (x::T) = x == mapLayout pred (mapLayout succ x)
|
|
|
|
------------------------------------------------------------------------
|
|
-- The Tall layout
|
|
|
|
-- 1 window should always be tiled fullscreen
|
|
prop_tile_fullscreen rect = tile pct rect 1 1 == [rect]
|
|
where pct = 1/2
|
|
|
|
-- multiple windows
|
|
prop_tile_non_overlap rect windows nmaster = noOverlaps (tile pct rect nmaster windows)
|
|
where _ = rect :: Rectangle
|
|
pct = 3 % 100
|
|
|
|
-- splitting horizontally yields sensible results
|
|
prop_split_hoziontal (NonNegative n) x =
|
|
{-
|
|
trace (show (rect_x x
|
|
,rect_width x
|
|
,rect_x x + fromIntegral (rect_width x)
|
|
,map rect_x xs))
|
|
$
|
|
-}
|
|
|
|
sum (map rect_width xs) == rect_width x
|
|
&&
|
|
all (== rect_height x) (map rect_height xs)
|
|
&&
|
|
(map rect_x xs) == (sort $ map rect_x xs)
|
|
|
|
where
|
|
xs = splitHorizontally n x
|
|
|
|
-- splitting horizontally yields sensible results
|
|
prop_splitVertically (r :: Rational) x =
|
|
|
|
rect_x x == rect_x a && rect_x x == rect_x b
|
|
&&
|
|
rect_width x == rect_width a && rect_width x == rect_width b
|
|
|
|
{-
|
|
trace (show (rect_x x
|
|
,rect_width x
|
|
,rect_x x + fromIntegral (rect_width x)
|
|
,map rect_x xs))
|
|
$
|
|
-}
|
|
|
|
where
|
|
(a,b) = splitVerticallyBy r x
|
|
|
|
|
|
-- pureLayout works.
|
|
prop_purelayout_tall n r1 r2 rect (t :: T) =
|
|
isJust (peek t) ==>
|
|
length ts == length (index t)
|
|
&&
|
|
noOverlaps (map snd ts)
|
|
&&
|
|
description layoot == "Tall"
|
|
where layoot = Tall n r1 r2
|
|
st = fromJust . stack . workspace . current $ t
|
|
ts = pureLayout layoot rect st
|
|
|
|
-- Test message handling of Tall
|
|
|
|
-- what happens when we send a Shrink message to Tall
|
|
prop_shrink_tall (NonNegative n) (NonZero (NonNegative delta)) (NonNegative frac) =
|
|
n == n' && delta == delta' -- these state components are unchanged
|
|
&& frac' <= frac && (if frac' < frac then frac' == 0 || frac' == frac - delta
|
|
else frac == 0 )
|
|
-- remaining fraction should shrink
|
|
where
|
|
l1 = Tall n delta frac
|
|
Just l2@(Tall n' delta' frac') = l1 `pureMessage` (SomeMessage Shrink)
|
|
-- pureMessage :: layout a -> SomeMessage -> Maybe (layout a)
|
|
|
|
|
|
-- what happens when we send a Shrink message to Tall
|
|
prop_expand_tall (NonNegative n)
|
|
(NonZero (NonNegative delta))
|
|
(NonNegative n1)
|
|
(NonZero (NonNegative d1)) =
|
|
|
|
n == n'
|
|
&& delta == delta' -- these state components are unchanged
|
|
&& frac' >= frac
|
|
&& (if frac' > frac
|
|
then frac' == 1 || frac' == frac + delta
|
|
else frac == 1 )
|
|
|
|
-- remaining fraction should shrink
|
|
where
|
|
frac = min 1 (n1 % d1)
|
|
l1 = Tall n delta frac
|
|
Just l2@(Tall n' delta' frac') = l1 `pureMessage` (SomeMessage Expand)
|
|
-- pureMessage :: layout a -> SomeMessage -> Maybe (layout a)
|
|
|
|
-- what happens when we send an IncMaster message to Tall
|
|
prop_incmaster_tall (NonNegative n) (NonZero (NonNegative delta)) (NonNegative frac)
|
|
(NonNegative k) =
|
|
delta == delta' && frac == frac' && n' == n + k
|
|
where
|
|
l1 = Tall n delta frac
|
|
Just l2@(Tall n' delta' frac') = l1 `pureMessage` (SomeMessage (IncMasterN k))
|
|
-- pureMessage :: layout a -> SomeMessage -> Maybe (layout a)
|
|
|
|
|
|
|
|
-- toMessage LT = SomeMessage Shrink
|
|
-- toMessage EQ = SomeMessage Expand
|
|
-- toMessage GT = SomeMessage (IncMasterN 1)
|
|
|
|
|
|
------------------------------------------------------------------------
|
|
-- Full layout
|
|
|
|
-- pureLayout works for Full
|
|
prop_purelayout_full rect (t :: T) =
|
|
isJust (peek t) ==>
|
|
length ts == 1 -- only one window to view
|
|
&&
|
|
snd (head ts) == rect -- and sets fullscreen
|
|
&&
|
|
fst (head ts) == fromJust (peek t) -- and the focused window is shown
|
|
|
|
where layoot = Full
|
|
st = fromJust . stack . workspace . current $ t
|
|
ts = pureLayout layoot rect st
|
|
|
|
-- what happens when we send an IncMaster message to Full --- Nothing
|
|
prop_sendmsg_full (NonNegative k) =
|
|
isNothing (Full `pureMessage` (SomeMessage (IncMasterN k)))
|
|
|
|
prop_desc_full = description Full == show Full
|
|
|
|
------------------------------------------------------------------------
|
|
|
|
prop_desc_mirror n r1 r2 = description (Mirror $! t) == "Mirror Tall"
|
|
where t = Tall n r1 r2
|
|
|
|
------------------------------------------------------------------------
|
|
|
|
noOverlaps [] = True
|
|
noOverlaps [_] = True
|
|
noOverlaps xs = and [ verts a `notOverlap` verts b
|
|
| a <- xs
|
|
, b <- filter (a /=) xs
|
|
]
|
|
where
|
|
verts (Rectangle a b w h) = (a,b,a + fromIntegral w - 1, b + fromIntegral h - 1)
|
|
|
|
notOverlap (left1,bottom1,right1,top1)
|
|
(left2,bottom2,right2,top2)
|
|
= (top1 < bottom2 || top2 < bottom1)
|
|
|| (right1 < left2 || right2 < left1)
|
|
|
|
------------------------------------------------------------------------
|
|
-- Aspect ratios
|
|
|
|
prop_resize_inc (NonZero (NonNegative inc_w),NonZero (NonNegative inc_h)) b@(w,h) =
|
|
w' `mod` inc_w == 0 && h' `mod` inc_h == 0
|
|
where (w',h') = applyResizeIncHint a b
|
|
a = (inc_w,inc_h)
|
|
|
|
prop_resize_inc_extra ((NonNegative inc_w)) b@(w,h) =
|
|
(w,h) == (w',h')
|
|
where (w',h') = applyResizeIncHint a b
|
|
a = (-inc_w,0::Dimension)-- inc_h)
|
|
|
|
prop_resize_max (NonZero (NonNegative inc_w),NonZero (NonNegative inc_h)) b@(w,h) =
|
|
w' <= inc_w && h' <= inc_h
|
|
where (w',h') = applyMaxSizeHint a b
|
|
a = (inc_w,inc_h)
|
|
|
|
prop_resize_max_extra ((NonNegative inc_w)) b@(w,h) =
|
|
(w,h) == (w',h')
|
|
where (w',h') = applyMaxSizeHint a b
|
|
a = (-inc_w,0::Dimension)-- inc_h)
|
|
|
|
------------------------------------------------------------------------
|
|
|
|
main :: IO ()
|
|
main = do
|
|
args <- fmap (drop 1) getArgs
|
|
let n = if null args then 100 else read (head args)
|
|
(results, passed) <- liftM unzip $ mapM (\(s,a) -> printf "%-40s: " s >> a n) tests
|
|
printf "Passed %d tests!\n" (sum passed)
|
|
when (not . and $ results) $ fail "Not all tests passed!"
|
|
where
|
|
|
|
tests =
|
|
[("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 reversible" , mytest prop_view_reversible)
|
|
-- ,("view / xinerama" , mytest prop_view_xinerama)
|
|
,("view is local" , mytest prop_view_local)
|
|
|
|
,("greedyView : invariant" , mytest prop_greedyView_I)
|
|
,("greedyView sets current" , mytest prop_greedyView_current)
|
|
,("greedyView is safe " , mytest prop_greedyView_current_id)
|
|
,("greedyView idempotent" , mytest prop_greedyView_idem)
|
|
,("greedyView reversible" , mytest prop_greedyView_reversible)
|
|
,("greedyView is local" , mytest prop_greedyView_local)
|
|
--
|
|
-- ,("valid workspace xinerama", mytest prop_lookupWorkspace)
|
|
|
|
,("peek/member " , mytest prop_member_peek)
|
|
|
|
,("index/length" , mytest prop_index_length)
|
|
|
|
,("focus left : invariant", mytest prop_focusUp_I)
|
|
,("focus master : invariant", mytest prop_focusMaster_I)
|
|
,("focus right: invariant", mytest prop_focusDown_I)
|
|
,("focusWindow: invariant", mytest prop_focus_I)
|
|
,("focus left/master" , mytest prop_focus_left_master)
|
|
,("focus right/master" , mytest prop_focus_right_master)
|
|
,("focus master/master" , mytest prop_focus_master_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 down is local" , mytest prop_focus_down_local)
|
|
,("focus up is local" , mytest prop_focus_up_local)
|
|
,("focus master is local" , mytest prop_focus_master_local)
|
|
,("focus master idemp" , mytest prop_focusMaster_idem)
|
|
|
|
,("focusWindow is local", mytest prop_focusWindow_local)
|
|
,("focusWindow works" , mytest prop_focusWindow_works)
|
|
,("focusWindow identity", mytest prop_focusWindow_identity)
|
|
|
|
,("findTag" , mytest prop_findIndex)
|
|
,("allWindows/member" , mytest prop_allWindowsMember)
|
|
,("currentTag" , mytest prop_currentTag)
|
|
|
|
,("insert: invariant" , mytest prop_insertUp_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)
|
|
,("delete/focus" , mytest prop_delete_focus)
|
|
,("delete last/focus up", mytest prop_delete_focus_end)
|
|
,("delete ~last/focus down", mytest prop_delete_focus_not_end)
|
|
|
|
,("filter preserves order", mytest prop_filter_order)
|
|
|
|
,("swapMaster: invariant", mytest prop_swap_master_I)
|
|
,("swapUp: invariant" , mytest prop_swap_left_I)
|
|
,("swapDown: invariant", mytest prop_swap_right_I)
|
|
,("swapMaster id on focus", mytest prop_swap_master_focus)
|
|
,("swapUp id on focus", mytest prop_swap_left_focus)
|
|
,("swapDown id on focus", mytest prop_swap_right_focus)
|
|
,("swapMaster is idempotent", mytest prop_swap_master_idempotent)
|
|
,("swap all left " , mytest prop_swap_all_l)
|
|
,("swap all right " , mytest prop_swap_all_r)
|
|
,("swapMaster is local" , mytest prop_swap_master_local)
|
|
,("swapUp is local" , mytest prop_swap_left_local)
|
|
,("swapDown is local" , mytest prop_swap_right_local)
|
|
|
|
,("shiftMaster id on focus", mytest prop_shift_master_focus)
|
|
,("shiftMaster is local", mytest prop_shift_master_local)
|
|
,("shiftMaster is idempotent", mytest prop_shift_master_idempotent)
|
|
,("shiftMaster preserves ordering", mytest prop_shift_master_ordering)
|
|
|
|
,("shift: invariant" , mytest prop_shift_I)
|
|
,("shift is reversible" , mytest prop_shift_reversible)
|
|
,("shiftWin: invariant" , mytest prop_shift_win_I)
|
|
,("shiftWin is shift on focus" , mytest prop_shift_win_focus)
|
|
,("shiftWin fix current" , mytest prop_shift_win_fix_current)
|
|
|
|
,("floating is reversible" , mytest prop_float_reversible)
|
|
,("floating sets geometry" , mytest prop_float_geometry)
|
|
,("floats can be deleted", mytest prop_float_delete)
|
|
,("screens includes current", mytest prop_screens)
|
|
|
|
,("differentiate works", mytest prop_differentiate)
|
|
,("lookupTagOnScreen", mytest prop_lookup_current)
|
|
,("lookupTagOnVisbleScreen", mytest prop_lookup_visible)
|
|
,("screens works", mytest prop_screens_works)
|
|
,("renaming works", mytest prop_rename1)
|
|
,("ensure works", mytest prop_ensure)
|
|
,("ensure hidden semantics", mytest prop_ensure_append)
|
|
|
|
,("mapWorkspace id", mytest prop_mapWorkspaceId)
|
|
,("mapWorkspace inverse", mytest prop_mapWorkspaceInverse)
|
|
,("mapLayout id", mytest prop_mapLayoutId)
|
|
,("mapLayout inverse", mytest prop_mapLayoutInverse)
|
|
|
|
-- testing for failure:
|
|
,("abort fails", mytest prop_abort)
|
|
,("new fails with abort", mytest prop_new_abort)
|
|
,("shiftWin identity", mytest prop_shift_win_indentity)
|
|
|
|
-- tall layout
|
|
|
|
,("tile 1 window fullsize", mytest prop_tile_fullscreen)
|
|
,("tiles never overlap", mytest prop_tile_non_overlap)
|
|
,("split hozizontally", mytest prop_split_hoziontal)
|
|
,("split verticalBy", mytest prop_splitVertically)
|
|
|
|
,("pure layout tall", mytest prop_purelayout_tall)
|
|
,("send shrink tall", mytest prop_shrink_tall)
|
|
,("send expand tall", mytest prop_expand_tall)
|
|
,("send incmaster tall", mytest prop_incmaster_tall)
|
|
|
|
-- full layout
|
|
|
|
,("pure layout full", mytest prop_purelayout_full)
|
|
,("send message full", mytest prop_sendmsg_full)
|
|
,("describe full", mytest prop_desc_full)
|
|
|
|
,("describe mirror", mytest prop_desc_mirror)
|
|
|
|
-- resize hints
|
|
,("window hints: inc", mytest prop_resize_inc)
|
|
,("window hints: inc all", mytest prop_resize_inc_extra)
|
|
,("window hints: max", mytest prop_resize_max)
|
|
,("window hints: max all ", mytest prop_resize_max_extra)
|
|
|
|
]
|
|
|
|
------------------------------------------------------------------------
|
|
--
|
|
-- QC driver
|
|
--
|
|
|
|
debug = False
|
|
|
|
mytest :: Testable a => a -> Int -> IO (Bool, Int)
|
|
mytest a n = mycheck defaultConfig
|
|
{ configMaxTest=n
|
|
, 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, Int)
|
|
mycheck config a = do
|
|
rnd <- newStdGen
|
|
mytests config (evaluate a) rnd 0 0 []
|
|
|
|
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, 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
|
|
Nothing ->
|
|
mytests config gen rnd1 ntest (nfail+1) stamps
|
|
Just True ->
|
|
mytests config gen rnd1 (ntest+1) nfail (stamp result:stamps)
|
|
Just False ->
|
|
putStr ( "Falsifiable after "
|
|
++ show ntest
|
|
++ " tests:\n"
|
|
++ unlines (arguments result)
|
|
) >> hFlush stdout >> return (False, ntest)
|
|
where
|
|
result = generate (configSize config ntest) rnd2 gen
|
|
(rnd1,rnd2) = split rnd0
|
|
|
|
done :: String -> Int -> [[String]] -> IO ()
|
|
done mesg ntest stamps = putStr ( mesg ++ " " ++ show ntest ++ " tests" ++ table )
|
|
where
|
|
table = display
|
|
. map entry
|
|
. reverse
|
|
. sort
|
|
. map pairLength
|
|
. group
|
|
. sort
|
|
. filter (not . null)
|
|
$ stamps
|
|
|
|
display [] = ".\n"
|
|
display [x] = " (" ++ x ++ ").\n"
|
|
display xs = ".\n" ++ unlines (map (++ ".") xs)
|
|
|
|
pairLength xss@(xs:_) = (length xss, xs)
|
|
entry (n, xs) = percentage n ntest
|
|
++ " "
|
|
++ concat (intersperse ", " xs)
|
|
|
|
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
|
|
|
|
newtype EmptyStackSet = EmptyStackSet T deriving Show
|
|
|
|
instance Arbitrary EmptyStackSet where
|
|
arbitrary = do
|
|
(NonEmptyNubList ns) <- arbitrary
|
|
(NonEmptyNubList sds) <- arbitrary
|
|
l <- arbitrary
|
|
-- there cannot be more screens than workspaces:
|
|
return . EmptyStackSet . new l ns $ take (min (length ns) (length sds)) sds
|
|
coarbitrary = error "coarbitrary EmptyStackSet"
|
|
|
|
-- | 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)
|