Skip to content

Commit 5262a30

Browse files
Soupstrawclaude
andcommitted
Add positive generator tests for Huddle examples
Extract genAndValidateRule and genAndValidateCddl from genAndValidateFromFile to enable reuse for testing Huddle schemas. Add "Generated value validates" tests that verify generated CBOR values pass validation. This exposes a bug in optionalMapExample where the generator cannot reliably produce 10 unique keys from a 10-element range (1..10), causing most generated maps to have fewer than the required 10 entries. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 59b7002 commit 5262a30

File tree

2 files changed

+48
-18
lines changed

2 files changed

+48
-18
lines changed

test/Test/Codec/CBOR/Cuddle/CDDL/GeneratorSpec.hs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ import Test.Codec.CBOR.Cuddle.CDDL.Examples.Huddle (
3030
sizeBytesExample,
3131
sizeTextExample,
3232
)
33-
import Test.Codec.CBOR.Cuddle.CDDL.Validator (expectInvalid)
33+
import Test.Codec.CBOR.Cuddle.CDDL.Validator (expectInvalid, genAndValidateRule)
3434
import Test.Hspec (HasCallStack, Spec, describe, runIO, shouldBe, shouldSatisfy)
3535
import Test.Hspec.Core.Spec (SpecM)
3636
import Test.Hspec.QuickCheck (prop)
@@ -68,8 +68,25 @@ zapInvalidatesHuddle n huddle = do
6868
prop n . counterexample (TL.unpack . pShow $ mapIndex @_ @_ @MonoSimple cddl) $
6969
expectZapInvalidates cddl "root"
7070

71+
-- | Test that generated values are valid for a Huddle schema
72+
genAndValidateHuddle :: String -> Huddle -> Spec
73+
genAndValidateHuddle n huddle = do
74+
cddl <- tryResolveHuddle huddle
75+
genAndValidateRule n "root" cddl
76+
7177
spec :: Spec
7278
spec = do
79+
describe "Positive generator" $ do
80+
describe "Generated value validates" $ do
81+
-- Note: simpleTermExample and refTermExample use custom generators
82+
-- that intentionally produce type-mismatched values, so they're excluded
83+
genAndValidateHuddle "opCert" opCertExample
84+
genAndValidateHuddle "sizeText" sizeTextExample
85+
genAndValidateHuddle "sizeBytes" sizeBytesExample
86+
genAndValidateHuddle "rangeList" rangeListExample
87+
genAndValidateHuddle "rangeMap" rangeMapExample
88+
genAndValidateHuddle "optionalMapExample" optionalMapExample
89+
7390
describe "Negative generator" $ do
7491
describe "Zapped value fails to validate" $ do
7592
zapInvalidatesHuddle "simpleTerm" simpleTermExample

test/Test/Codec/CBOR/Cuddle/CDDL/Validator.hs

Lines changed: 30 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ module Test.Codec.CBOR.Cuddle.CDDL.Validator (
66
spec,
77
expectValid,
88
expectInvalid,
9+
genAndValidateCddl,
10+
genAndValidateRule,
911
) where
1012

1113
import Codec.CBOR.Cuddle.CBOR.Gen (generateFromName)
@@ -24,7 +26,7 @@ import Codec.CBOR.Cuddle.CDDL.CBORGenerator (CustomValidatorResult (..))
2426
import Codec.CBOR.Cuddle.CDDL.CTree (CTreeRoot (..))
2527
import Codec.CBOR.Cuddle.CDDL.CTree qualified as CTree
2628
import Codec.CBOR.Cuddle.CDDL.Postlude (appendPostlude)
27-
import Codec.CBOR.Cuddle.CDDL.Resolve (fullResolveCDDL)
29+
import Codec.CBOR.Cuddle.CDDL.Resolve (MonoReferenced, fullResolveCDDL)
2830
import Codec.CBOR.Cuddle.Huddle (
2931
Huddle,
3032
HuddleItem (..),
@@ -90,31 +92,42 @@ import Test.QuickCheck (
9092
)
9193
import Text.Megaparsec (runParser)
9294

95+
-- | Test that a specific rule in a resolved CDDL generates valid values
96+
genAndValidateRule :: String -> Name -> CTreeRoot MonoReferenced -> Spec
97+
genAndValidateRule description name resolvedCddl =
98+
prop description $ do
99+
cborTerm <- runAntiGen $ generateFromName (mapIndex resolvedCddl) name
100+
let
101+
generatedCbor = toStrictByteString $ encodeTerm cborTerm
102+
res = validateCBOR generatedCbor name (mapIndex resolvedCddl)
103+
extraInfo =
104+
unlines
105+
[ "CBOR term:"
106+
, prettyHexEnc $ encodeTerm cborTerm
107+
]
108+
pure . counterexample extraInfo $ expectValid res
109+
110+
-- | Test that all rules in a resolved CDDL generate valid values
111+
genAndValidateCddl :: String -> CTreeRoot MonoReferenced -> Spec
112+
genAndValidateCddl description resolvedCddl@(CTreeRoot m) = do
113+
let
114+
isRule CTree.Group {} = False
115+
isRule _ = True
116+
describe description $ do
117+
forM_ (Map.keys $ Map.filter isRule m) $ \name@(Name n) ->
118+
genAndValidateRule (T.unpack n) name resolvedCddl
119+
93120
genAndValidateFromFile :: FilePath -> Spec
94121
genAndValidateFromFile path = do
95122
contents <- runIO $ T.readFile =<< getDataFileName path
96123
let
97124
cddl = fromRight (error "Failed to parse CDDL") $ runParser pCDDL path contents
98125
resolverError x =
99126
error $ "Failed to resolve the CDDL from file " <> show path <> ":\n" <> show x
100-
resolvedCddl@(CTreeRoot m) =
127+
resolvedCddl =
101128
either resolverError id . fullResolveCDDL . appendPostlude $
102129
mapCDDLDropExt cddl
103-
isRule CTree.Group {} = False
104-
isRule _ = True
105-
describe path $ do
106-
forM_ (Map.keys $ Map.filter isRule m) $ \name@(Name n) ->
107-
prop (T.unpack n) $ do
108-
cborTerm <- runAntiGen $ generateFromName (mapIndex resolvedCddl) name
109-
let
110-
generatedCbor = toStrictByteString $ encodeTerm cborTerm
111-
res = validateCBOR generatedCbor name (mapIndex resolvedCddl)
112-
extraInfo =
113-
unlines
114-
[ "CBOR term:"
115-
, prettyHexEnc $ encodeTerm cborTerm
116-
]
117-
pure . counterexample extraInfo $ expectValid res
130+
genAndValidateCddl path resolvedCddl
118131

119132
genInfiniteUniqueList :: Ord a => Gen a -> Gen [a]
120133
genInfiniteUniqueList = fmap nubOrd . infiniteListOf

0 commit comments

Comments
 (0)