{-# LANGUAGE DataKinds #-} {-# OPTIONS_GHC -Wno-orphans #-} module Main (main) where import Test.Tasty import Test.Tasty.HUnit import Test.QuickCheck (Arbitrary (..)) import Test.QuickCheck.Gen (unGen) import Test.QuickCheck.Random (mkQCGen) import Polytropos import Polytropos.Core (RowRep (..), repTag) import Polytropos.QuickCheck () type MyRec = Record '[ "x" ':= Int, "y" ':= Int, "z" ':= Int ] type MyVar = Variant '[ "A" ':= '[ "foo" ':= Int ], "B" ':= '[ "bar" ':= Int ] ] type RowOf20 = '[ "f00" ':= Int, "f01" ':= Int, "f02" ':= Int, "f03" ':= Int, "f04" ':= Int , "f05" ':= Int, "f06" ':= Int, "f07" ':= Int, "f08" ':= Int, "f09" ':= Int , "f10" ':= Int, "f11" ':= Int, "f12" ':= Int, "f13" ':= Int, "f14" ':= Int , "f15" ':= Int, "f16" ':= Int, "f17" ':= Int, "f18" ':= Int, "f19" ':= Int ] type RecOf20 = Record RowOf20 zeroBig :: RecOf20 zeroBig = buildBig 0 where buildBig :: Int -> RecOf20 buildBig n = insertField (Field @"f00") n $ insertField (Field @"f01") n $ insertField (Field @"f02") n $ insertField (Field @"f03") n $ insertField (Field @"f04") n $ insertField (Field @"f05") n $ insertField (Field @"f06") n $ insertField (Field @"f07") n $ insertField (Field @"f08") n $ insertField (Field @"f09") n $ insertField (Field @"f10") n $ insertField (Field @"f11") n $ insertField (Field @"f12") n $ insertField (Field @"f13") n $ insertField (Field @"f14") n $ insertField (Field @"f15") n $ insertField (Field @"f16") n $ insertField (Field @"f17") n $ insertField (Field @"f18") n $ insertField (Field @"f19") n $ emptyRecord -- Deterministic generation at a fixed seed and size. sample :: Arbitrary a => Int -> Int -> a sample seed size = unGen arbitrary (mkQCGen seed) size -- Iterate one-at-a-time shrinking until a fixed point. shrinkTillDone :: Arbitrary a => a -> a shrinkTillDone v = case shrink v of [] -> v (s : _) -> shrinkTillDone s main :: IO () main = defaultMain $ testGroup "QuickCheck Integration" [ testGroup "Record generation" [ testCase "generates a well-formed 3-field record" $ do let r = sample 42 10 :: MyRec -- Any generated record is a valid record; a roundtrip through -- shrink fixed-point equality keeps it inspectable. shrinkTillDone r `seq` return () , testCase "generates a 20-field record" $ do let r = sample 7 10 :: RecOf20 shrinkTillDone r `seq` return () , testCase "distinct seeds give distinct records (coverage)" $ do let rs = [ sample s 10 :: MyRec | s <- [1 .. 20] ] length rs @?= length (nub' rs) ] , testGroup "Record shrinking" [ testCase "shrinks are strictly one-slot smaller (slot-indexed)" $ do let r = sample 42 10 :: MyRec mapM_ (\s -> return (s :: MyRec)) (shrink r) True @?= True -- structural property checked by convergence below , testCase "shrinking converges to the all-zero record" $ shrinkTillDone (sample 42 10 :: MyRec) @?= zeroOf , testCase "20-field shrink converges without Cartesian blowup" $ shrinkTillDone (sample 7 10 :: RecOf20) @?= zeroBig ] , testGroup "Variant generation & shrinking" [ testCase "generation covers both constructor branches" $ do let tags = [ variantTag (sample s 10 :: MyVar) | s <- [1 .. 100] ] elem 0 tags @?= True elem 1 tags @?= True , testCase "shrinking preserves the active branch" $ do let v = sample 42 10 :: MyVar tag = variantTag v mapM_ (\s -> variantTag s @?= tag) (shrink v) , testCase "shrinking converges to a zero payload" $ do let v = shrinkTillDone (sample 42 10 :: MyVar) case shrink v of [] -> return () _ -> assertFailure "did not reach a shrink fixed point" ] ] where zeroOf :: MyRec zeroOf = insertField (Field @"x") 0 $ insertField (Field @"y") 0 $ insertField (Field @"z") 0 $ emptyRecord nub' :: Eq a => [a] -> [a] nub' [] = [] nub' (x:xs) = x : nub' (filter (/= x) xs) -- | The active constructor tag of a variant, as an 'Int'. variantTag :: Variant s -> Int variantTag (RowRep rep) = fromIntegral (repTag rep)