Skip to content

Commit 2532376

Browse files
authored
Merge pull request #684 from kquick/testcompile
Bump QuickCheck + base-orphan; faster test compilation
2 parents c3d0418 + 3f553c4 commit 2532376

File tree

10 files changed

+646
-531
lines changed

10 files changed

+646
-531
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ cabal.sandbox.config
1313
.\#*
1414
\#*
1515

16+
# Emacs backup files
17+
*~
18+
1619
benchmarks/AesonEncode
1720

1821
tests/qc

aeson.cabal

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -197,20 +197,26 @@ test-suite tests
197197
Functions
198198
Instances
199199
Options
200+
PropUtils
200201
Properties
202+
PropertyGeneric
203+
PropertyKeys
204+
PropertyRoundTrip
205+
PropertyRTFunctors
206+
PropertyTH
201207
SerializationFormatSpec
202208
Types
203209
UnitTests
204210
UnitTests.NullaryConstructors
205211

206212
build-depends:
207-
QuickCheck >= 2.10.0.1 && < 2.12,
213+
QuickCheck >= 2.10.0.1 && < 2.13,
208214
aeson,
209215
integer-logarithms >= 1 && <1.1,
210216
attoparsec,
211217
base,
212218
base-compat,
213-
base-orphans >= 0.5.3 && <0.8,
219+
base-orphans >= 0.5.3 && <0.9,
214220
base16-bytestring,
215221
containers,
216222
directory,

tests/DataFamilies/Properties.hs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ import Prelude.Compat
77

88
import DataFamilies.Encoders
99
import DataFamilies.Instances ()
10-
import Properties hiding (tests)
10+
import PropUtils
11+
1112

1213
import Test.Tasty (TestTree, testGroup)
1314
import Test.Tasty.QuickCheck (testProperty)

tests/PropUtils.hs

Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
{-# LANGUAGE NoImplicitPrelude #-}
2+
{-# LANGUAGE OverloadedStrings #-}
3+
{-# LANGUAGE RankNTypes #-}
4+
5+
module PropUtils (module PropUtils) where
6+
7+
import Prelude.Compat
8+
9+
import Data.Aeson (eitherDecode, encode)
10+
import Data.Aeson.Encoding (encodingToLazyByteString)
11+
import Data.Aeson.Internal (IResult(..), formatError, ifromJSON, iparse)
12+
import qualified Data.Aeson.Internal as I
13+
import Data.Aeson.Parser (value)
14+
import Data.Aeson.Types
15+
import Data.HashMap.Strict (HashMap)
16+
import Data.Hashable (Hashable)
17+
import Data.Int (Int8)
18+
import Data.Map (Map)
19+
import Data.Time (ZonedTime)
20+
import Encoders
21+
import Instances ()
22+
import Test.QuickCheck (Arbitrary(..), Property, Testable, (===), (.&&.), counterexample)
23+
import Types
24+
import qualified Data.Attoparsec.Lazy as L
25+
import qualified Data.ByteString.Lazy.Char8 as L
26+
import qualified Data.HashMap.Strict as H
27+
import qualified Data.Map as Map
28+
import qualified Data.Text as T
29+
import qualified Data.Vector as V
30+
31+
32+
encodeDouble :: Double -> Double -> Property
33+
encodeDouble num denom
34+
| isInfinite d || isNaN d = encode d === "null"
35+
| otherwise = (read . L.unpack . encode) d === d
36+
where d = num / denom
37+
38+
encodeInteger :: Integer -> Property
39+
encodeInteger i = encode i === L.pack (show i)
40+
41+
toParseJSON :: (Eq a, Show a) =>
42+
(Value -> Parser a) -> (a -> Value) -> a -> Property
43+
toParseJSON 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'
47+
48+
toParseJSON1
49+
:: (Eq (f Int), Show (f Int))
50+
=> (forall a. LiftParseJSON f a)
51+
-> (forall a. LiftToJSON f a)
52+
-> f Int
53+
-> Property
54+
toParseJSON1 parsejson1 tojson1 = toParseJSON parsejson tojson
55+
where
56+
parsejson = parsejson1 parseJSON (listParser parseJSON)
57+
tojson = tojson1 toJSON (listValue toJSON)
58+
59+
roundTripEnc :: (FromJSON a, ToJSON a, Show a) =>
60+
(a -> a -> Property) -> a -> a -> Property
61+
roundTripEnc eq _ i =
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
66+
67+
roundTripNoEnc :: (FromJSON a, ToJSON a, Show a) =>
68+
(a -> a -> Property) -> a -> a -> Property
69+
roundTripNoEnc eq _ i =
70+
case ifromJSON . toJSON $ i of
71+
(ISuccess v) -> v `eq` i
72+
(IError path err) -> failure "fromJSON" (formatError path err) i
73+
74+
roundTripEq :: (Eq a, FromJSON a, ToJSON a, Show a) => a -> a -> Property
75+
roundTripEq x y = roundTripEnc (===) x y .&&. roundTripNoEnc (===) x y
76+
77+
-- We test keys by encoding HashMap and Map with it
78+
roundTripKey
79+
:: (Ord a, Hashable a, FromJSONKey a, ToJSONKey a, Show a)
80+
=> a -> HashMap a Int -> Map a Int -> Property
81+
roundTripKey _ h m = roundTripEq h h .&&. roundTripEq m m
82+
83+
infix 4 ==~
84+
(==~) :: (ApproxEq a, Show a) => a -> a -> Property
85+
x ==~ y =
86+
counterexample (show x ++ " /= " ++ show y) (x =~ y)
87+
88+
toFromJSON :: (Arbitrary a, Eq a, FromJSON a, ToJSON a, Show a) => a -> Property
89+
toFromJSON x = case ifromJSON (toJSON x) of
90+
IError path err -> failure "fromJSON" (formatError path err) x
91+
ISuccess x' -> x === x'
92+
93+
modifyFailureProp :: String -> String -> Bool
94+
modifyFailureProp orig added =
95+
result == Error (added ++ orig)
96+
where
97+
parser = const $ modifyFailure (added ++) $ fail orig
98+
result :: Result ()
99+
result = parse parser ()
100+
101+
parserThrowErrorProp :: String -> Property
102+
parserThrowErrorProp msg =
103+
result === Error msg
104+
where
105+
parser = const $ parserThrowError [] msg
106+
result :: Result ()
107+
result = parse parser ()
108+
109+
-- | Tests (also) that we catch the JSONPath and it has elements in the right order.
110+
parserCatchErrorProp :: [String] -> String -> Property
111+
parserCatchErrorProp path msg =
112+
result === Success ([I.Key "outer", I.Key "inner"] ++ jsonPath, msg)
113+
where
114+
parser = parserCatchError outer (curry pure)
115+
116+
outer = inner I.<?> I.Key "outer"
117+
inner = parserThrowError jsonPath msg I.<?> I.Key "inner"
118+
119+
result :: Result (I.JSONPath, String)
120+
result = parse (const parser) ()
121+
122+
jsonPath = map (I.Key . T.pack) path
123+
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.
127+
sameAs :: (a -> Value) -> (a -> Encoding) -> a -> Property
128+
sameAs toVal toEnc v =
129+
counterexample (show s) $
130+
eitherDecode s === Right (toVal v)
131+
where
132+
s = encodingToLazyByteString (toEnc v)
133+
134+
sameAs1
135+
:: (forall a. LiftToJSON f a)
136+
-> (forall a. LiftToEncoding f a)
137+
-> f Int
138+
-> Property
139+
sameAs1 toVal1 toEnc1 v = lhs === rhs
140+
where
141+
rhs = Right $ toVal1 toJSON (listValue toJSON) v
142+
lhs = eitherDecode . encodingToLazyByteString $
143+
toEnc1 toEncoding (listEncoding toEncoding) v
144+
145+
sameAs1Agree
146+
:: ToJSON a
147+
=> (f a -> Encoding)
148+
-> (forall b. LiftToEncoding f b)
149+
-> f a
150+
-> Property
151+
sameAs1Agree toEnc toEnc1 v = rhs === lhs
152+
where
153+
rhs = encodingToLazyByteString $ toEnc v
154+
lhs = encodingToLazyByteString $ toEnc1 toEncoding (listEncoding toEncoding) v
155+
156+
type P6 = Product6 Int Bool String (Approx Double) (Int, Approx Double) ()
157+
type S4 = Sum4 Int8 ZonedTime T.Text (Map.Map String Int)
158+
159+
--------------------------------------------------------------------------------
160+
-- Value properties
161+
--------------------------------------------------------------------------------
162+
163+
-- | Add the formatted @Value@ to the printed counterexample when the property
164+
-- fails.
165+
checkValue :: Testable a => (Value -> a) -> Value -> Property
166+
checkValue prop v = counterexample (L.unpack (encode v)) (prop v)
167+
168+
isString :: Value -> Bool
169+
isString (String _) = True
170+
isString _ = False
171+
172+
is2ElemArray :: Value -> Bool
173+
is2ElemArray (Array v) = V.length v == 2 && isString (V.head v)
174+
is2ElemArray _ = False
175+
176+
isTaggedObjectValue :: Value -> Bool
177+
isTaggedObjectValue (Object obj) = "tag" `H.member` obj &&
178+
"contents" `H.member` obj
179+
isTaggedObjectValue _ = False
180+
181+
isNullaryTaggedObject :: Value -> Bool
182+
isNullaryTaggedObject obj = isTaggedObject' obj && isObjectWithSingleField obj
183+
184+
isTaggedObject :: Value -> Property
185+
isTaggedObject = checkValue isTaggedObject'
186+
187+
isTaggedObject' :: Value -> Bool
188+
isTaggedObject' (Object obj) = "tag" `H.member` obj
189+
isTaggedObject' _ = False
190+
191+
isObjectWithSingleField :: Value -> Bool
192+
isObjectWithSingleField (Object obj) = H.size obj == 1
193+
isObjectWithSingleField _ = False
194+
195+
-- | is untaggedValue of EitherTextInt
196+
isUntaggedValueETI :: Value -> Bool
197+
isUntaggedValueETI (String s)
198+
| s == "nonenullary" = True
199+
isUntaggedValueETI (Bool _) = True
200+
isUntaggedValueETI (Number _) = True
201+
isUntaggedValueETI (Array a) = length a == 2
202+
isUntaggedValueETI _ = False
203+
204+
isEmptyArray :: Value -> Property
205+
isEmptyArray = checkValue isEmptyArray'
206+
207+
isEmptyArray' :: Value -> Bool
208+
isEmptyArray' = (Array mempty ==)

0 commit comments

Comments
 (0)