@@ -9,62 +9,185 @@ module ErrorMessages
99
1010import Prelude.Compat
1111
12- import Data.Aeson (FromJSON (.. ), eitherDecode )
12+ import Data.Aeson (FromJSON (.. ), Value , json )
13+ import Data.Aeson.Types (Parser )
14+ import Data.Aeson.Parser (eitherDecodeWith )
15+ import Data.Aeson.Internal (formatError , iparse )
16+ import Data.Algorithm.Diff (Diff (.. ), getGroupedDiff )
1317import Data.Proxy (Proxy (.. ))
18+ import Data.Semigroup ((<>) )
19+ import Data.Sequence (Seq )
1420import Instances ()
1521import Numeric.Natural (Natural )
16- import Test.Tasty (TestTree )
17- import Test.Tasty.HUnit ( Assertion , assertFailure , assertEqual , testCase )
22+ import Test.Tasty (TestTree , TestName )
23+ import Test.Tasty.Golden.Advanced ( goldenTest )
1824import qualified Data.ByteString.Lazy.Char8 as L
1925import qualified Data.HashMap.Strict as HM
2026
27+ import Encoders
28+ import Types
29+
2130tests :: [TestTree ]
2231tests =
23- [
24- testCase " Int" int
25- , testCase " Integer" integer
26- , testCase " Natural" natural
27- , testCase " String" string
28- , testCase " HashMap" hashMap
29- ]
30-
31- int :: Assertion
32- int = do
33- let t = test (Proxy :: Proxy Int )
34- t " \"\" " $ expected " Int" " String"
35- t " []" $ expected " Int" " Array"
36- t " {}" $ expected " Int" " Object"
37- t " null" $ expected " Int" " Null"
38-
39- integer :: Assertion
40- integer = do
41- let t = test (Proxy :: Proxy Integer )
42- t " 44.44" $ expected " Integer" " floating number 44.44"
43-
44- natural :: Assertion
45- natural = do
46- let t = test (Proxy :: Proxy Natural )
47- t " 44.44" $ expected " Natural" " floating number 44.44"
48- t " -50" $ expected " Natural" " negative number -50"
49-
50- string :: Assertion
51- string = do
52- let t = test (Proxy :: Proxy String )
53- t " 1" $ expected " String" " Number"
54- t " []" $ expected " String" " Array"
55- t " {}" $ expected " String" " Object"
56- t " null" $ expected " String" " Null"
57-
58- hashMap :: Assertion
59- hashMap = do
60- let t = test (Proxy :: Proxy (HM. HashMap String Int ))
61- t " \"\" " $ expected " HashMap k v" " String"
62- t " []" $ expected " HashMap k v" " Array"
63-
64- expected :: String -> String -> String
65- expected ex enc = " Error in $: expected " ++ ex ++ " , encountered " ++ enc
66-
67- test :: forall a proxy . (FromJSON a , Show a ) => proxy a -> L. ByteString -> String -> Assertion
68- test _ v msg = case eitherDecode v of
69- Left e -> assertEqual " Invalid error message" msg e
70- Right (x :: a ) -> assertFailure $ " Expected parsing to fail but it suceeded with: " ++ show x
32+ [ aesonGoldenTest " simple" " tests/golden/simple.expected" output
33+ , aesonGoldenTest " generic" " tests/golden/generic.expected" (outputGeneric G )
34+ ]
35+
36+ output :: Output
37+ output = concat
38+ [ testFor " Int" (Proxy :: Proxy Int )
39+ [ " \"\" "
40+ , " []"
41+ , " {}"
42+ , " null"
43+ ]
44+
45+ , testFor " Integer" (Proxy :: Proxy Integer )
46+ [ " 44.44"
47+ ]
48+
49+ , testFor " Natural" (Proxy :: Proxy Natural )
50+ [ " 44.44"
51+ , " -50"
52+ ]
53+
54+ , testFor " String" (Proxy :: Proxy String )
55+ [ " 1"
56+ , " []"
57+ , " {}"
58+ , " null"
59+ ]
60+
61+ , testFor " HashMap" (Proxy :: Proxy (HM. HashMap String Int ))
62+ [ " \"\" "
63+ , " []"
64+ ]
65+
66+ -- issue #356
67+ , testFor " Either" (Proxy :: Proxy (Int , Either (Int , Bool ) () ))
68+ [ " [1,{\" Left\" :[2,3]}]"
69+ ]
70+
71+ -- issue #358
72+ , testFor " Seq" (Proxy :: Proxy (Seq Int ))
73+ [ " [0,1,true]"
74+ ]
75+ ]
76+
77+ data Choice = TH | G
78+
79+ outputGeneric :: Choice -> Output
80+ outputGeneric choice = concat
81+ [ testWith " OneConstructor"
82+ (select
83+ thOneConstructorParseJSONDefault
84+ gOneConstructorParseJSONDefault)
85+ [ " \" X\" "
86+ , " [0]"
87+ ]
88+
89+ , testWith " Nullary"
90+ (select
91+ thNullaryParseJSONString
92+ gNullaryParseJSONString)
93+ [ " \" X\" "
94+ , " []"
95+ ]
96+
97+ , testWithSomeType " SomeType (tagged)"
98+ (select
99+ thSomeTypeParseJSONTaggedObject
100+ gSomeTypeParseJSONTaggedObject)
101+ [ " {\" tag\" : \" unary\" , \" contents\" : true}"
102+ , " {\" tag\" : \" unary\" }"
103+ , " {\" tag\" : \" record\" }"
104+ , " {\" tag\" : \" record\" , \" testone\" : true, \" testtwo\" : null, \" testthree\" : null}"
105+ , " {\" tag\" : \" X\" }"
106+ , " {}"
107+ , " []"
108+ ]
109+
110+ , testWithSomeType " SomeType (single-field)"
111+ (select
112+ thSomeTypeParseJSONObjectWithSingleField
113+ gSomeTypeParseJSONObjectWithSingleField)
114+ [ " {\" unary\" : {}}"
115+ , " {\" unary\" : []}"
116+ , " {\" X\" : []}"
117+ , " {\" record\" : {}, \" W\" :{}}"
118+ , " {}"
119+ , " []"
120+ ]
121+
122+ , testWithSomeType " SomeType (two-element array)"
123+ (select
124+ thSomeTypeParseJSON2ElemArray
125+ gSomeTypeParseJSON2ElemArray)
126+ [ " [\" unary\" , true]"
127+ , " [\" record\" , null]"
128+ , " [\" X\" , 0]"
129+ , " [null, 0]"
130+ , " []"
131+ , " {}"
132+ ]
133+
134+ , testWith " EitherTextInt"
135+ (select
136+ thEitherTextIntParseJSONUntaggedValue
137+ gEitherTextIntParseJSONUntaggedValue)
138+ [ " \" X\" "
139+ , " []"
140+ ]
141+
142+ , testWith " Product2 Int Bool"
143+ (select
144+ thProduct2ParseJSON
145+ gProduct2ParseJSON)
146+ [ " [1, null]"
147+ , " []"
148+ , " {}"
149+ ]
150+ ]
151+ where
152+ select a b = case choice of
153+ TH -> a
154+ G -> b
155+
156+ -- Test infrastructure
157+
158+ type Output = [String ]
159+
160+ outputLine :: String -> Output
161+ outputLine = pure
162+
163+ aesonGoldenTest :: TestName -> FilePath -> Output -> TestTree
164+ aesonGoldenTest name ref out = goldenTest name (L. readFile ref) act cmp upd
165+ where
166+ act = pure (L. pack (unlines out))
167+ upd = L. writeFile ref
168+ cmp x y | x == y = return Nothing
169+ cmp x y = return $ Just $ unlines $
170+ concatMap f (getGroupedDiff (L. lines x) (L. lines y))
171+ where
172+ f (First xs) = map (cons3 ' -' . L. unpack) xs
173+ f (Second ys) = map (cons3 ' +' . L. unpack) ys
174+ -- we print unchanged lines too. It shouldn't be a problem while we have
175+ -- reasonably small examples
176+ f (Both xs _) = map (cons3 ' ' . L. unpack) xs
177+ -- we add three characters, so the changed lines are easier to spot
178+ cons3 c cs = c : c : c : ' ' : cs
179+
180+ testWith :: Show a => String -> (Value -> Parser a ) -> [L. ByteString ] -> Output
181+ testWith name parser ts =
182+ outputLine name <>
183+ foldMap (\ s ->
184+ case eitherDecodeWith json (iparse parser) s of
185+ Left err -> outputLine $ uncurry formatError err
186+ Right a -> outputLine $ show a) ts
187+
188+ testFor :: forall a proxy . (FromJSON a , Show a )
189+ => String -> proxy a -> [L. ByteString ] -> Output
190+ testFor name _ = testWith name (parseJSON :: Value -> Parser a )
191+
192+ testWithSomeType :: String -> (Value -> Parser (SomeType Int )) -> [L. ByteString ] -> Output
193+ testWithSomeType = testWith
0 commit comments