1{-# LANGUAGE NoImplicitPrelude #-}
2{-# LANGUAGE OverloadedStrings #-}
3{-# LANGUAGE RankNTypes #-}
5module PropUtils (module PropUtils) where
9import Data.Aeson (eitherDecode, encode)
10import Data.Aeson.Encoding (encodingToLazyByteString)
11import Data.Aeson.Internal (IResult(..), formatError, ifromJSON, iparse)
12import qualified Data.Aeson.Internal as I
13import Data.Aeson.Parser (value)
14import Data.Aeson.Types
15import Data.HashMap.Strict (HashMap)
16import Data.Hashable (Hashable)
19import Data.Time (ZonedTime)
22import Test.QuickCheck (Arbitrary(..), Property, Testable, (===), (.&&.), counterexample)
24import qualified Data.Attoparsec.Lazy as L
25import qualified Data.ByteString.Lazy.Char8 as L
26import qualified Data.HashMap.Strict as H
27import qualified Data.Map as Map
28import qualified Data.Text as T
29import qualified Data.Vector as V
32encodeDouble :: Double -> Double -> Property
34 | isInfinite d || isNaN d = encode d === "null"
35 | otherwise = (read . L.unpack . encode) d === d
38encodeInteger :: Integer -> Property
39encodeInteger i = encode i === L.pack (show i)
41toParseJSON :: (Eq a, Show a) =>
42 (Value -> Parser a) -> (a -> Value) -> a -> Property
43toParseJSON parsejson tojson x =
44 case iparse parsejson . tojson $ x of
45 IError path msg -> failure "parse" (formatError path msg) x
46 ISuccess x' -> x === x'
49 :: (Eq (f Int), Show (f Int))
50 => (forall a. LiftParseJSON f a)
51 -> (forall a. LiftToJSON f a)
54toParseJSON1 parsejson1 tojson1 = toParseJSON parsejson tojson
56 parsejson = parsejson1 parseJSON (listParser parseJSON)
57 tojson = tojson1 toJSON (listValue toJSON)
59roundTripEnc :: (FromJSON a, ToJSON a, Show a) =>
60 (a -> a -> Property) -> a -> a -> Property
62 case fmap ifromJSON . L.parse value . encode $ i of
63 L.Done _ (ISuccess v) -> v `eq` i
64 L.Done _ (IError path err) -> failure "fromJSON" (formatError path err) i
65 L.Fail _ _ err -> failure "parse" err i
67roundTripNoEnc :: (FromJSON a, ToJSON a, Show a) =>
68 (a -> a -> Property) -> a -> a -> Property
69roundTripNoEnc eq _ i =
70 case ifromJSON . toJSON $ i of
71 (ISuccess v) -> v `eq` i
72 (IError path err) -> failure "fromJSON" (formatError path err) i
74roundTripEq :: (Eq a, FromJSON a, ToJSON a, Show a) => a -> a -> Property
75roundTripEq x y = roundTripEnc (===) x y .&&. roundTripNoEnc (===) x y
77-- We test keys by encoding HashMap and Map with it
79 :: (Ord a, Hashable a, FromJSONKey a, ToJSONKey a, Show a)
80 => a -> HashMap a Int -> Map a Int -> Property
81roundTripKey _ h m = roundTripEq h h .&&. roundTripEq m m
84(==~) :: (ApproxEq a, Show a) => a -> a -> Property
86 counterexample (show x ++ " /= " ++ show y) (x =~ y)
88toFromJSON :: (Arbitrary a, Eq a, FromJSON a, ToJSON a, Show a) => a -> Property
89toFromJSON x = case ifromJSON (toJSON x) of
90 IError path err -> failure "fromJSON" (formatError path err) x
91 ISuccess x' -> x === x'
93modifyFailureProp :: String -> String -> Bool
94modifyFailureProp orig added =
95 result == Error (added ++ orig)
97 parser = const $ modifyFailure (added ++) $ fail orig
99 result = parse parser ()
101parserThrowErrorProp :: String -> Property
102parserThrowErrorProp msg =
105 parser = const $ parserThrowError [] msg
107 result = parse parser ()
109-- | Tests (also) that we catch the JSONPath and it has elements in the right order.
110parserCatchErrorProp :: [String] -> String -> Property
111parserCatchErrorProp path msg =
112 result === Success ([I.Key "outer", I.Key "inner"] ++ jsonPath, msg)
114 parser = parserCatchError outer (curry pure)
116 outer = inner I.<?> I.Key "outer"
117 inner = parserThrowError jsonPath msg I.<?> I.Key "inner"
119 result :: Result (I.JSONPath, String)
120 result = parse (const parser) ()
122 jsonPath = map (I.Key . T.pack) path
124-- | Perform a structural comparison of the results of two encoding
125-- methods. Compares decoded values to account for HashMap-driven
126-- variation in JSON object key ordering.
127sameAs :: (a -> Value) -> (a -> Encoding) -> a -> Property
128sameAs toVal toEnc v =
129 counterexample (show s) $
130 eitherDecode s === Right (toVal v)
132 s = encodingToLazyByteString (toEnc v)
135 :: (forall a. LiftToJSON f a)
136 -> (forall a. LiftToEncoding f a)
139sameAs1 toVal1 toEnc1 v = lhs === rhs
141 rhs = Right $ toVal1 toJSON (listValue toJSON) v
142 lhs = eitherDecode . encodingToLazyByteString $
143 toEnc1 toEncoding (listEncoding toEncoding) v
148 -> (forall b. LiftToEncoding f b)
151sameAs1Agree toEnc toEnc1 v = rhs === lhs
153 rhs = encodingToLazyByteString $ toEnc v
154 lhs = encodingToLazyByteString $ toEnc1 toEncoding (listEncoding toEncoding) v
156type P6 = Product6 Int Bool String (Approx Double) (Int, Approx Double) ()
157type S4 = Sum4 Int8 ZonedTime T.Text (Map.Map String Int)
159--------------------------------------------------------------------------------
161--------------------------------------------------------------------------------
163-- | Add the formatted @Value@ to the printed counterexample when the property
165checkValue :: Testable a => (Value -> a) -> Value -> Property
166checkValue prop v = counterexample (L.unpack (encode v)) (prop v)
168isString :: Value -> Bool
169isString (String _) = True
172is2ElemArray :: Value -> Bool
173is2ElemArray (Array v) = V.length v == 2 && isString (V.head v)
174is2ElemArray _ = False
176isTaggedObjectValue :: Value -> Bool
177isTaggedObjectValue (Object obj) = "tag" `H.member` obj &&
178 "contents" `H.member` obj
179isTaggedObjectValue _ = False
181isNullaryTaggedObject :: Value -> Bool
182isNullaryTaggedObject obj = isTaggedObject' obj && isObjectWithSingleField obj
184isTaggedObject :: Value -> Property
185isTaggedObject = checkValue isTaggedObject'
187isTaggedObject' :: Value -> Bool
188isTaggedObject' (Object obj) = "tag" `H.member` obj
189isTaggedObject' _ = False
191isObjectWithSingleField :: Value -> Bool
192isObjectWithSingleField (Object obj) = H.size obj == 1
193isObjectWithSingleField _ = False
195-- | is untaggedValue of EitherTextInt
196isUntaggedValueETI :: Value -> Bool
197isUntaggedValueETI (String s)
198 | s == "nonenullary" = True
199isUntaggedValueETI (Bool _) = True
200isUntaggedValueETI (Number _) = True
201isUntaggedValueETI (Array a) = length a == 2
202isUntaggedValueETI _ = False
204isEmptyArray :: Value -> Property
205isEmptyArray = checkValue isEmptyArray'
207isEmptyArray' :: Value -> Bool
208isEmptyArray' = (Array mempty ==)
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list