Skip to content

Commit 3f553c4

Browse files
committed
Split Properties tests into multiple files.
The original property testing used a single file to specify all property tests, which caused compilation issues and intermittent Travis CI build failures. This commit breaks the Properties file into multiple files to make it faster to compile. | Properties | GHC | QuickCheck | Clean build/test | properties build/test | |-------------+-------+------------+-----------------------+-----------------------| | single file | 8.4.4 | 2.11.3 | 3m48s / 5m56s / 1m33s | 1m42s / 1m58s / 0m31s | | | | 2.12.6.1 | 3m27s / 5m42s / 0m58s | 1m45s / 2m2s / 0m33s | |-------------+-------+------------+-----------------------+-----------------------| | multi-file | 8.4.4 | 2.11.3 | 2m19s / 4m26s / 0m37s | 0m35s / 0m44s / 0m9s | | | | 2.12.6.1 | 2m20s / 4m26s / 0m37s | 0m36s / 0m45s / 0m10s | (times are in real, user, and sys time)
1 parent 2c144d1 commit 3f553c4

File tree

9 files changed

+641
-529
lines changed

9 files changed

+641
-529
lines changed

aeson.cabal

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,13 @@ 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

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)