1 {-# OPTIONS_GHC -fno-warn-orphans #-}
2 module QuickCheck.Merit where
3 import Control.Monad (Monad(..), replicateM)
5 import Data.Eq (Eq(..))
6 import Data.Foldable (Foldable(..))
7 import Data.Function (($), (.))
8 import Data.Functor ((<$>))
9 import Data.Hashable (Hashable)
11 import Data.List ((++), head, zip)
12 import Data.Ord (Ord(..))
13 import Data.Ratio (Rational)
14 import GHC.Exts (IsList(..))
16 import Prelude (Enum(..), Num(..), Integral(..), Bounded(..), fromIntegral, undefined)
17 import Test.QuickCheck
19 import Test.Tasty.QuickCheck
20 import Text.Show (Show(..))
21 import qualified Data.Map.Strict as Map
23 import QuickCheck.Utils
26 quickcheck :: TestTree
29 [ testProperty "arbitraryMerits" $ \(SameLength (Merit x::Merit SchoolGrade,Merit y::Merit SchoolGrade)) ->
30 Map.keys x == Map.keys y &&
34 -- | @arbitraryMerits n@ arbitrarily generates 'n' lists of 'Merit'
35 -- for the same arbitrary grades,
36 -- and with the same total 'Share' of individual judgments.
37 arbitraryMerits :: forall g. (Bounded g, Enum g, Ord g) => Int -> Gen [Merit g]
38 arbitraryMerits n = sized $ \shareSum -> do
39 minG <- choose (fromEnum(minBound::g), fromEnum(maxBound::g))
40 maxG <- choose (minG, fromEnum(maxBound::g))
41 let gs::[g] = toEnum minG`enumFromTo`toEnum maxG
42 let lenGrades = maxG - minG + 1
44 shares <- resize shareSum $ arbitrarySizedPositiveRationalSum lenGrades
45 shares' :: [Share] <- arbitraryPad (lenGrades - length shares) (return 0) shares
46 return $ Merit $ fromList $ zip gs shares'
48 -- | @arbitrarySizedPositiveRationalSum maxLen@
49 -- arbitrarily chooses a list of 'length' at most 'maxLen',
50 -- containing positive 'Rational's summing up to 'sized'.
51 arbitrarySizedPositiveRationalSum :: Int -> Gen [Rational]
52 arbitrarySizedPositiveRationalSum maxLen = sized (go maxLen . fromIntegral)
54 go :: Int -> Rational -> Gen [Rational]
55 go len tot | len <= 0 = return []
56 | len == 1 = return [tot]
57 | tot <= 0 = return [tot]
60 (d:) <$> go (len-1) (tot - d)
62 -- | @arbitrarySizedNaturalSum maxLen@
63 -- arbitrarily chooses a list of 'length' at most 'maxLen',
64 -- containing 'Int's summing up to 'sized'.
65 arbitrarySizedNaturalSum :: Int -> Gen [Int]
66 arbitrarySizedNaturalSum maxLen = sized (go maxLen)
68 go :: Int -> Int -> Gen [Int]
69 go len tot | len <= 0 = return []
70 | len == 1 = return [tot]
71 | tot <= 0 = return [tot]
74 (d:) <$> go (len-1) (tot - d)
76 -- | @arbitraryPad n pad xs@
77 -- arbitrarily grows list 'xs' with 'pad' elements
79 arbitraryPad :: (Num i, Integral i) => i -> Gen a -> [a] -> Gen [a]
80 arbitraryPad n pad [] = replicateM (fromIntegral n) pad
81 arbitraryPad n pad xs = do
84 then arbitraryPad r pad xs'
87 go r xs' | r <= 0 = return (0,xs')
88 go r [] = arbitrary >>= \b ->
89 if b then pad >>= \p -> ((p:)<$>) <$> go (r-1) []
91 go r (x:xs') = arbitrary >>= \b ->
92 if b then pad >>= \p -> (([p,x] ++)<$>) <$> go (r-1) xs'
93 else ((x:)<$>) <$> go r xs'
96 (Arbitrary g, Bounded g, Enum g, Ord g, Show g) =>
97 Arbitrary (Merit g) where
98 arbitrary = head <$> arbitraryMerits 1
99 shrink (Merit m) = Merit <$> shrink m
101 ( Arbitrary c, Bounded c, Enum c, Eq c, Hashable c, Show c
102 , Arbitrary g, Bounded g, Enum g, Ord g, Show g
103 ) => Arbitrary (MeritByChoice c g) where
105 minP <- choose (fromEnum(minBound::c), fromEnum(maxBound::c))
106 maxP <- choose (minP, fromEnum(maxBound::c))
107 let ps = toEnum minP`enumFromTo`toEnum maxP
108 let ms = arbitraryMerits (maxP - minP + 1)
109 fromList . zip ps <$> ms
111 (Arbitrary g, Bounded g, Enum g, Ord g) =>
112 Arbitrary (SameLength (Merit g, Merit g)) where
114 vs <- arbitraryMerits 2
116 [x,y] -> return $ SameLength (x,y)