Custom shrinkers like jsverify
See original GitHub issue💬 Question and Help
Despite loving the idea that you don’t have to bother writing your own shrinkers most of the time in fast-check
, for more complex cases it falls short. I was thinking of using a JSON diff as the counter examples produced contained too much noise to see what might have gone wrong. And rather than going that route or updating my old jsverify
code, I thought it better to just ask how to do this in fast-check
instead. My approach as mentioned in #1316, works good enough for most cases, but now that I have solved most issues, it seems to only produce counter examples that are not shrinked properly. I am pretty sure this is due to my use of .chain
, but without it I cannot express how it should go about shrinking. So unless there have been some updates addressing the shrinking of recursive data structures that would allow me to do without .chain
, I figure I will have to write my own shrinker.
In my previous issue #1316, I was told to check ArbitraryWithShrink.ts if I wanted to go about writing my own shrinker. The problem I have with this API is that for complex values, you want subvalues to use existing shrinkers, but I can’t. Fundamental building blocks like .array
don’t have a .shrinkableFor
. What then if I have an object value that I want to write a .shrinkableFor
for, containing an array in one of its values? I don’t want to reproduce the shrinking logic for arrays just because my object contains one. Ideally I would only have to write the custom shrinking logic and leverage builtins shrinkers for the rest, like you do in jsverify
. Am I going about it the wrong way, or are there any examples of this type of shrinking?
As an example, this is what I did in my jsverify
code to shrink Tag
objects and what I want to reproduce in fast-check
:
function shrinkTag(ensureIsAttribute: boolean) {
return shr.bless((tag: Tag) => {
let shrs = lazyseq.nil
if (tag.contents.length > 0) {
shrs = shrs.append(() => {
let tags = tag.contents.filter(content => typeof content !== "string") as Tag[]
if (ensureIsAttribute) {
tags = tags.map(tag => ({ ...tag, isAttribute: true }))
}
return tags
})
}
if (tag.attributes.length > 0) {
shrs = shrs.append(tag.attributes)
}
if (tag.contents.length > 0) {
if (tag.isLiteral) {
shrs = shrs.append(shrText(tag.contents[0] as string).map(text => ({ ...tag, contents: [text] })))
} else {
shrs = shrs.append(shrContents(tag.contents).map(contents => ({ ...tag, contents })))
}
}
if (tag.attributes.length > 0) {
shrs = shrs.append(
shr
.array(shrAttribute)(tag.attributes)
.map(attributes => ({ ...tag, attributes })),
)
}
shrs = shrs.append(shrTagName(tag.name).map(name => ({ ...tag, name })))
if (tag.isLiteral) {
shrs = shrs.append([{ ...tag, isLiteral: false }])
}
if (tag.isAttribute && !ensureIsAttribute) {
shrs = shrs.append([{ ...tag, isAttribute: false }])
}
if (tag.isQuoted) {
shrs = shrs.append([{ ...tag, isQuoted: false }])
}
return shrs
})
}
Issue Analytics
- State:
- Created 3 years ago
- Comments:18 (9 by maintainers)
Top GitHub Comments
Thanks for letting me know, you are doing great work! This library makes me have confidence in my code on a level I would otherwise never be able to get, and thus makes working in TypeScript much more enjoyable.
This is looking great! I have tested it locally and it works. Your example code does not type check for me (the helper), some
unknown
type gets injected, which requires quite a bit extra types to circumvent, so I just added a few@ts-ignore
comments for now. I look forward to experimenting with this new version to see how I can improve my tests! I will be closing the issue as it should be addressed now. Thank you!