packages feed

mockcat-1.0.0.0: test/Property/ParamSpecNormalizeProp.hs

{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
module Property.ParamSpecNormalizeProp (spec, prop_enum_duplicate_equivalence) where

import Test.Hspec
import Test.QuickCheck
import Data.List (nub)
import Support.ParamSpec (ParamSpec(..))
import Support.ParamSpecNormalize

-- Generator for small ParamSpec Int
-- Avoid empty enum (library contract), but include duplicates.
genParamSpecInt :: Gen (ParamSpec Int)
genParamSpecInt = frequency
  [ (3, PSExact <$> smallInt)
  , (3, sizedEnum)
  , (2, rangeSpec)
  , (1, pure PSAny)
  , (1, pure (PSPredicate even "even"))
  ]
  where
    smallInt = choose (-5, 5)
    sizedEnum = do
      n <- choose (1,5)
      xs <- vectorOf n smallInt
      pure (PSEnum xs)
    rangeSpec = do
      a <- smallInt; PSRangeInt a <$> smallInt

pretty :: ParamSpec Int -> String
pretty ps = case ps of
  PSExact a -> "Exact " ++ show a
  PSEnum xs -> "Enum " ++ show xs
  PSRangeInt l h -> "Range " ++ show (l,h)
  PSAny -> "Any"
  PSPredicate _ l -> "Pred " ++ l

-- Property: normalise . toParamSpec . normalise ≡ normalise (idempotence via roundtrip)
prop_normalise_idempotent :: Property
prop_normalise_idempotent = forAll (Blind <$> genParamSpecInt) $ \(Blind ps) ->
  let n1 = normalise ps
      n2 = normalise (toParamSpec n1)
  in counterexample ("ps=" ++ pretty ps) (n1 == n2)

-- Property: acceptance equivalence on a finite sample domain
prop_acceptance_equivalence :: Property
prop_acceptance_equivalence = forAll (Blind <$> genParamSpecInt) $ \(Blind ps) ->
  forAll sampleDomain $ \xs ->
    let n = normalise ps
        ok = all (\x -> acceptsParamSpec ps x == acceptsNormal n x) xs
    in counterexample ("ps=" ++ pretty ps ++ "; xs sample mismatch") ok
  where
    sampleDomain = vectorOf 20 (choose (-7,7))

spec :: Spec
spec = do
  describe "ParamSpec Normalisation" $ do
    it "idempotent (roundtrip)" $ prop_normalise_idempotent
    it "acceptance equivalence on sample domain" $ prop_acceptance_equivalence
    it "enum duplicate equivalence" $ prop_enum_duplicate_equivalence

-- Property A: Verify that the normalized form is as expected even if Enum has duplicates (single point -> NExact / multiple -> NEnum uniq)
prop_enum_duplicate_equivalence :: Property
prop_enum_duplicate_equivalence = forAll genDupEnum $ \(xs :: [Int]) ->
  let n = normalise (PSEnum xs)
      uniq = nub xs
  in case uniq of
       []    -> counterexample "unexpected empty after nub" False
       [x]   -> case n of
                  NExact y -> counterexample (show n) (y == x)
                  _        -> counterexample (show n) False
       _     -> case n of
                  NEnum ys -> counterexample (show n) (ys == uniq)
                  _        -> counterexample (show n ++ " expected NEnum") False
  where
    genDupEnum :: Gen [Int]
    genDupEnum = do
      baseLen <- choose (1,5)
      base <- vectorOf baseLen (choose (-5,5))
      dupFactor <- choose (0,3)
      extras <- concat <$> mapM (vectorOf dupFactor . pure) base
      shuffle (base ++ extras)