Main.hs 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. {-
  2. - Copyright (C) 2019 Koz Ross <koz.ross@retro-freedom.nz>
  3. -
  4. - This program is free software: you can redistribute it and/or modify
  5. - it under the terms of the GNU General Public License as published by
  6. - the Free Software Foundation, either version 3 of the License, or
  7. - (at your option) any later version.
  8. -
  9. - This program is distributed in the hope that it will be useful,
  10. - but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. - GNU General Public License for more details.
  13. -
  14. - You should have received a copy of the GNU General Public License
  15. - along with this program. If not, see <http://www.gnu.org/licenses/>.
  16. -}
  17. {-# LANGUAGE DeriveAnyClass #-}
  18. {-# LANGUAGE DeriveGeneric #-}
  19. {-# LANGUAGE TypeInType #-}
  20. {-# LANGUAGE AllowAmbiguousTypes #-}
  21. {-# LANGUAGE ScopedTypeVariables #-}
  22. {-# LANGUAGE TypeApplications #-}
  23. {-# LANGUAGE OverloadedStrings #-}
  24. {-# LANGUAGE TypeOperators #-}
  25. {-# LANGUAGE TypeFamilies #-}
  26. {-# LANGUAGE GADTs #-}
  27. {-# LANGUAGE RankNTypes #-}
  28. module Main where
  29. import Data.List (sort, sortOn)
  30. import Data.Ord (Down)
  31. import Data.Maybe (isNothing, isJust)
  32. import Data.Int (Int8, Int16, Int32, Int64)
  33. import Data.Word (Word8, Word16, Word32, Word64)
  34. import GHC.TypeNats
  35. import GHC.Generics
  36. import Data.Kind (Type)
  37. import Data.Finite (Finite, finites)
  38. import Data.Proxy (Proxy(..))
  39. import Control.Monad.Loops (andM)
  40. import Data.Typeable (Typeable, typeRep)
  41. import Data.String (fromString)
  42. import Hedgehog
  43. import qualified Hedgehog.Gen as G
  44. import qualified Hedgehog.Range as R
  45. import qualified Data.Vector.Sized as VS
  46. import qualified Data.Vector.Unboxed.Sized as VUS
  47. import qualified Data.Vector.Storable.Sized as VSS
  48. import qualified Data.Bit as B
  49. import qualified Data.Bit.ThreadSafe as BTS
  50. import Data.Finitary
  51. -- A representation of types
  52. data SomeFinitaryRep where
  53. SomeFinitaryRep :: forall (a :: Type) . (Finitary a, Ord a, Show a, Typeable a, 1 <= (Cardinality a)) => Proxy a -> SomeFinitaryRep
  54. -- A representation of test functions
  55. data SomeTestFunction where
  56. SomeTestFunction :: (forall (a :: Type) . (Finitary a, Ord a, Show a, 1 <= (Cardinality a)) => Proxy a -> Property) -> SomeTestFunction
  57. -- Some weird generic
  58. data Foo = Bar | Baz (VS.Vector 4 Bool) | Quux Word8
  59. deriving (Eq, Show, Typeable, Generic, Finitary, Ord)
  60. -- Something I will need for further tests
  61. iterateMN :: forall (m :: Type -> Type) (a :: Type) (b :: Type) . (Eq a, Bounded a, Enum a, Monad m) => a -> (b -> m b) -> b -> m b
  62. iterateMN i f x = if i == minBound
  63. then f x
  64. else iterateMN (pred i) f x >>= f
  65. -- How much testing do I want to do for random inputs?
  66. testLimit :: forall (a :: Type) (b :: Type) . (Finitary a, Num b) => b
  67. testLimit = fromIntegral . (* 2) . min 32767 . natVal $ Proxy @(Cardinality a)
  68. -- Generators
  69. choose :: forall (a :: Type) m . (MonadGen m, Finitary a) => m a
  70. choose = fromFinite <$> chooseFinite
  71. chooseFinite :: forall (n :: Nat) m . (KnownNat n, MonadGen m) => m (Finite n)
  72. chooseFinite = fromIntegral <$> G.integral (R.linear 0 limit)
  73. where limit = subtract @Integer 1 . fromIntegral . natVal @n $ Proxy
  74. -- Data
  75. allTheTypes :: [SomeFinitaryRep]
  76. allTheTypes = [
  77. SomeFinitaryRep @(Proxy Int) Proxy,
  78. SomeFinitaryRep @Bool Proxy,
  79. SomeFinitaryRep @B.Bit Proxy,
  80. SomeFinitaryRep @BTS.Bit Proxy,
  81. SomeFinitaryRep @Ordering Proxy,
  82. SomeFinitaryRep @Char Proxy,
  83. SomeFinitaryRep @Word8 Proxy,
  84. SomeFinitaryRep @Word16 Proxy,
  85. SomeFinitaryRep @Word32 Proxy,
  86. SomeFinitaryRep @Word64 Proxy,
  87. SomeFinitaryRep @Int16 Proxy,
  88. SomeFinitaryRep @Int8 Proxy,
  89. SomeFinitaryRep @Int32 Proxy,
  90. SomeFinitaryRep @Int64 Proxy,
  91. SomeFinitaryRep @Int Proxy,
  92. SomeFinitaryRep @Word Proxy,
  93. SomeFinitaryRep @(Down Int) Proxy,
  94. SomeFinitaryRep @(Maybe Word8) Proxy,
  95. SomeFinitaryRep @(Either Word8 Int8) Proxy,
  96. SomeFinitaryRep @(Word8, Int8) Proxy,
  97. SomeFinitaryRep @(VS.Vector 4 Bool) Proxy,
  98. SomeFinitaryRep @(VUS.Vector 4 Bool) Proxy,
  99. SomeFinitaryRep @(VSS.Vector 4 Bool) Proxy
  100. ]
  101. constructTest :: SomeTestFunction -> GroupName -> IO Bool
  102. constructTest (SomeTestFunction testFunc) name = checkParallel . Group name $ fmap go allTheTypes
  103. where go (SomeFinitaryRep p) = (fromString . show . typeRep $ p, testFunc p)
  104. -- Properties
  105. isBijection :: forall (a :: Type) . (Finitary a, Show a) => Proxy a -> Property
  106. isBijection _ = withTests (testLimit @a) (property $ do x <- forAll $ choose @a
  107. x === (fromFinite . toFinite $ x))
  108. isOrderPreserving :: forall (a :: Type) . (Finitary a, Ord a) => Proxy a -> Property
  109. isOrderPreserving _ = withTests (testLimit @a) (property $ do i <- forAll $ chooseFinite @(Cardinality a)
  110. j <- forAll $ chooseFinite @(Cardinality a)
  111. assert (fromFinite @a i > fromFinite @a j || i <= j))
  112. startIsCorrect :: forall (a :: Type) . (Finitary a, Show a, 1 <= (Cardinality a)) => Proxy a -> Property
  113. startIsCorrect _ = property $ start @a === fromFinite minBound
  114. previousStartNothing :: forall (a :: Type) . (Finitary a, 1 <= (Cardinality a)) => Proxy a -> Property
  115. previousStartNothing _ = property $ assert (isNothing . previous $ start @a)
  116. endNextNothing :: forall (a :: Type) . (Finitary a, 1 <= (Cardinality a)) => Proxy a -> Property
  117. endNextNothing _ = property $ assert (isNothing . next $ end @a)
  118. endIsCorrect :: forall (a :: Type) . (Finitary a, Show a, 1 <= (Cardinality a)) => Proxy a -> Property
  119. endIsCorrect _ = property $ end @a === fromFinite maxBound
  120. previousIsCorrect :: forall (a :: Type) . (Finitary a, 1 <= Cardinality a, Show a) => Proxy a -> Property
  121. previousIsCorrect _ = withTests (testLimit @a) (property $ do x <- forAll $ choose @a
  122. if x == start
  123. then success
  124. else assert . isJust . previous $ x)
  125. nextIsCorrect :: forall (a :: Type) . (Finitary a, 1 <= Cardinality a, Show a) => Proxy a -> Property
  126. nextIsCorrect _ = withTests (testLimit @a) (property $ do x <- forAll $ choose @a
  127. if x == end
  128. then success
  129. else assert . isJust . next $ x)
  130. -- Check the behaviour of the tuple generic so that we don't violate
  131. -- order-preservation
  132. agreesWithOrd :: Property
  133. agreesWithOrd = property $ do let xs = (,) <$> [LT ..] <*> [False ..]
  134. sort xs === sortOn toFinite xs
  135. -- Check that we can enumerate properly and that our type, in order, is
  136. -- isomorphic to 'finites'
  137. enumeratesProperly :: Property
  138. enumeratesProperly = property $ do let xs = (,) <$> [LT ..] <*> [False ..]
  139. (toFinite <$> sort xs) === finites
  140. -- All the tests I want to use
  141. allTests :: [(SomeTestFunction, GroupName)]
  142. allTests = [
  143. (SomeTestFunction isBijection, "bijectivity"),
  144. (SomeTestFunction isOrderPreserving, "order preservation"),
  145. (SomeTestFunction startIsCorrect, "start"),
  146. (SomeTestFunction endIsCorrect, "end"),
  147. (SomeTestFunction previousStartNothing, "previous + start"),
  148. (SomeTestFunction endNextNothing, "next + end"),
  149. (SomeTestFunction previousIsCorrect, "previous"),
  150. (SomeTestFunction nextIsCorrect, "next")
  151. ]
  152. main :: IO Bool
  153. main = andM . (:) (checkSequential . Group "Ord agreement" $ [("ordering", agreesWithOrd), ("totality", enumeratesProperly)]) . fmap (uncurry constructTest) $ allTests