Semiautomatic checking of typeclass-laws
This commit is contained in:
parent
c28c823082
commit
b713369134
@ -221,6 +221,7 @@ tests:
|
|||||||
- QuickCheck
|
- QuickCheck
|
||||||
- yesod-test
|
- yesod-test
|
||||||
- conduit-extra
|
- conduit-extra
|
||||||
|
- quickcheck-classes
|
||||||
- quickcheck-instances
|
- quickcheck-instances
|
||||||
hlint:
|
hlint:
|
||||||
main: Hlint.hs
|
main: Hlint.hs
|
||||||
|
|||||||
@ -49,6 +49,10 @@ instance Arbitrary SheetType where
|
|||||||
|
|
||||||
spec :: Spec
|
spec :: Spec
|
||||||
spec = do
|
spec = do
|
||||||
|
describe "UUID" $
|
||||||
|
lawsCheckHspec (Proxy :: Proxy UUID)
|
||||||
|
[ persistFieldLaws, pathPieceLaws, eqLaws, ordLaws, showReadLaws, hashableLaws, jsonLaws, storableLaws, jsonKeyLaws
|
||||||
|
]
|
||||||
describe "TermIdentifier" $ do
|
describe "TermIdentifier" $ do
|
||||||
it "has compatible encoding/decoding to/from Text" . property $
|
it "has compatible encoding/decoding to/from Text" . property $
|
||||||
\term -> termFromText (termToText term) == Right term
|
\term -> termFromText (termToText term) == Right term
|
||||||
|
|||||||
13
test/Test/QuickCheck/Classes/Hashable.hs
Normal file
13
test/Test/QuickCheck/Classes/Hashable.hs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
module Test.QuickCheck.Classes.Hashable
|
||||||
|
( hashableLaws
|
||||||
|
) where
|
||||||
|
|
||||||
|
import ClassyPrelude
|
||||||
|
import Test.QuickCheck
|
||||||
|
import Test.QuickCheck.Classes
|
||||||
|
import Data.Proxy
|
||||||
|
|
||||||
|
hashableLaws :: forall a. (Arbitrary a, Hashable a, Eq a, Show a) => Proxy a -> Laws
|
||||||
|
hashableLaws _ = Laws "Hashable"
|
||||||
|
[ ("Injectivity", property $ \(a :: a) (a' :: a) (s :: Int) -> hashWithSalt s a /= hashWithSalt s a' ==> a /= a')
|
||||||
|
]
|
||||||
38
test/Test/QuickCheck/Classes/JSON.hs
Normal file
38
test/Test/QuickCheck/Classes/JSON.hs
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
module Test.QuickCheck.Classes.JSON
|
||||||
|
( jsonKeyLaws
|
||||||
|
) where
|
||||||
|
|
||||||
|
import ClassyPrelude
|
||||||
|
import Test.QuickCheck
|
||||||
|
import Test.QuickCheck.Property (failed)
|
||||||
|
import Test.QuickCheck.Classes
|
||||||
|
import Data.Aeson
|
||||||
|
import Data.Aeson.Encoding.Internal
|
||||||
|
import Data.Aeson.Types (parseMaybe)
|
||||||
|
import Data.Proxy
|
||||||
|
import Unsafe.Coerce -- DON'T PANIC, it's aeson's fault
|
||||||
|
|
||||||
|
jsonKeyLaws :: forall a. (Arbitrary a, FromJSONKey a, ToJSONKey a, Eq a, Show a, FromJSON a, ToJSON a) => Proxy a -> Laws
|
||||||
|
jsonKeyLaws _ = Laws "ToJSONKey/FromJSONKey"
|
||||||
|
[ ("Partial Isomorphism", property $ \(a :: a) -> partialIsomorphism a)
|
||||||
|
, ("Partial Isomorphism (List)", property $ \(as :: [a]) -> partialIsomorphism as)
|
||||||
|
, ("Encoding Equals Value", property $ \(a :: a)
|
||||||
|
-> let (toVal, toEnc) = case toJSONKey of
|
||||||
|
ToJSONKeyText toVal' toEnc' -> (String . toVal', retagEncoding . toEnc')
|
||||||
|
ToJSONKeyValue toVal' toEnc' -> (toVal', toEnc')
|
||||||
|
in decode (encodingToLazyByteString $ toEnc a) == Just (toVal a)
|
||||||
|
)
|
||||||
|
]
|
||||||
|
where
|
||||||
|
partialIsomorphism :: forall a'. (Arbitrary a', FromJSONKey a', ToJSONKey a', Eq a', Show a') => a' -> Property
|
||||||
|
partialIsomorphism a = case (toJSONKey, fromJSONKey) of
|
||||||
|
(ToJSONKeyText toVal _, FromJSONKeyCoerce _)
|
||||||
|
-> property $ unsafeCoerce (toVal a) == a
|
||||||
|
(ToJSONKeyText toVal _, FromJSONKeyText fromVal)
|
||||||
|
-> property $ fromVal (toVal a) == a
|
||||||
|
(ToJSONKeyText toVal _, FromJSONKeyTextParser parser)
|
||||||
|
-> property $ parseMaybe parser (toVal a) == Just a
|
||||||
|
(ToJSONKeyValue toVal _, FromJSONKeyValue parser)
|
||||||
|
-> property $ parseMaybe parser (toVal a) == Just a
|
||||||
|
(_, _)
|
||||||
|
-> property failed
|
||||||
14
test/Test/QuickCheck/Classes/PathPiece.hs
Normal file
14
test/Test/QuickCheck/Classes/PathPiece.hs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
module Test.QuickCheck.Classes.PathPiece
|
||||||
|
( pathPieceLaws
|
||||||
|
) where
|
||||||
|
|
||||||
|
import ClassyPrelude
|
||||||
|
import Test.QuickCheck
|
||||||
|
import Test.QuickCheck.Classes
|
||||||
|
import Web.PathPieces
|
||||||
|
import Data.Proxy
|
||||||
|
|
||||||
|
pathPieceLaws :: forall a. (Arbitrary a, PathPiece a, Eq a, Show a) => Proxy a -> Laws
|
||||||
|
pathPieceLaws _ = Laws "PathPiece"
|
||||||
|
[ ("Partial Isomorphism", property $ \(a :: a) -> fromPathPiece (toPathPiece a) == Just a)
|
||||||
|
]
|
||||||
14
test/Test/QuickCheck/Classes/PersistField.hs
Normal file
14
test/Test/QuickCheck/Classes/PersistField.hs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
module Test.QuickCheck.Classes.PersistField
|
||||||
|
( persistFieldLaws
|
||||||
|
) where
|
||||||
|
|
||||||
|
import ClassyPrelude
|
||||||
|
import Test.QuickCheck
|
||||||
|
import Test.QuickCheck.Classes
|
||||||
|
import Database.Persist
|
||||||
|
import Data.Proxy
|
||||||
|
|
||||||
|
persistFieldLaws :: forall a. (Arbitrary a, PersistField a, Eq a, Show a) => Proxy a -> Laws
|
||||||
|
persistFieldLaws _ = Laws "PersistField"
|
||||||
|
[ ("Partial Isomorphism", property $ \(a :: a) -> fromPersistValue (toPersistValue a) == Right a)
|
||||||
|
]
|
||||||
@ -18,6 +18,13 @@ import Test.QuickCheck as X
|
|||||||
import Test.QuickCheck.Gen as X
|
import Test.QuickCheck.Gen as X
|
||||||
import Data.Default as X
|
import Data.Default as X
|
||||||
import Test.QuickCheck.Instances as X ()
|
import Test.QuickCheck.Instances as X ()
|
||||||
|
import Test.QuickCheck.Classes as X
|
||||||
|
import Test.QuickCheck.Classes.PathPiece as X
|
||||||
|
import Test.QuickCheck.Classes.PersistField as X
|
||||||
|
import Test.QuickCheck.Classes.Hashable as X
|
||||||
|
import Test.QuickCheck.Classes.JSON as X
|
||||||
|
import Data.Proxy as X
|
||||||
|
import Data.UUID as X (UUID)
|
||||||
import System.IO as X (hPrint, hPutStrLn, stderr)
|
import System.IO as X (hPrint, hPutStrLn, stderr)
|
||||||
import Jobs (handleJobs, stopJobCtl)
|
import Jobs (handleJobs, stopJobCtl)
|
||||||
|
|
||||||
@ -105,3 +112,9 @@ createUser userIdent = do
|
|||||||
userMailLanguages = def
|
userMailLanguages = def
|
||||||
userNotificationSettings = def
|
userNotificationSettings = def
|
||||||
runDB $ insertEntity User{..}
|
runDB $ insertEntity User{..}
|
||||||
|
|
||||||
|
lawsCheckHspec :: Proxy a -> [Proxy a -> Laws] -> Spec
|
||||||
|
lawsCheckHspec p = mapM_ (checkHspec . ($ p))
|
||||||
|
where
|
||||||
|
checkHspec (Laws className properties) = describe className $
|
||||||
|
forM_ properties $ \(name, prop) -> it name $ property prop
|
||||||
|
|||||||
Reference in New Issue
Block a user