Haskell quickcheck - how to generate only printable strings
Asked Answered
S

2

24

I have a set of simple demo programs which encode/decode strings, and want to generate some quickCheck tests for them, but to limit the tests to printable strings only. Using a guard is too slow and fails because of too many generated and rejected test cases, so I want to create a safe generator for this domain.

The references to this that I have seen say to either (1) define one's own Arbitrary instance for Char and use that to generate only printable characters for strings, or (2) to have to wrapper the functions themselves in a newtype and write an Arbitrary instance for that.

But trying to do (1) it fails because there is now a definition for this in Test.QuickCheck, and so how would one do this - create a safeChar generator for a new type and then again have to produce an adapter to the tested functions? (The RWH book section on this notes that it is out of date in recommending this DIY Char definition.)

Trying to do (2) seems like I can either just add a guard to the test proposition which is localized and simple (but fails), or writing a new wrapper and associated generator, which seems messier.

Clearly this is simple(!) and all the tools are provided, but could someone advise if this is a correct analysis, and give an example of how to best do this?

Sphygmic answered 5/1, 2014 at 14:7 Comment(3)
Good question, which I don't wasnt to detract from, and I can see that you might expect different kinds of output from valid and invalid data, but might it not be a good idea to also check that your library performs as intended even when given unprintable strings? It's conceivable, for example, that at some point in the future someone will be using your code in a web service and the behaviour on surprising data could be an attack vector.Notecase
Good point - but this was not "real world" but more of a quickcheck learning exercise - still in process!Sphygmic
problem here is not about generator for a single value, but customizing generators on complex/nested data structures.Raynold
H
25

The starting point is definitely a genSafeChar generator, which can have type Gen Char. For example:

genSafeChar :: Gen Char
genSafeChar = elements ['a'..'z']

Then you can build that up into a genSafeString generator, e.g. with listOf:

genSafeString :: Gen String
genSafeString = listOf genSafeChar

At this point you have a couple of reasonable choices. Either make a newtype wrapper for String:

newtype SafeString = SafeString { unwrapSafeString :: String }
    deriving Show

instance Arbitrary SafeString where
    arbitrary = SafeString <$> genSafeString

(in this case you might just inline the definition of genSafeString)

and you can then use it something like this:

testWibble (SafeString str) = str == str

Or, you can use forAll at each point you need a safe string:

testWibble = forAll genSafeString $ \str -> str == str
Hulen answered 5/1, 2014 at 17:9 Comment(7)
There really needs to be a Lorem Ipsum QuickCheck generator. This problem comes up over and over again.Rebuttal
@Ganesh - Thanks - the forAll works fine. But trying the other I get: *** Failed! Exception: 'no default generator' (after 1 test): SafeString {unWrap = "Exception thrown by generator: 'no default generator'. And could I build this generator more simply by using something like: arbitrary = FN {unFN = listOf $ elements ['a'..'z'] } .Sphygmic
Yes, you can definitely inline things as you suggest - I was just being more explicit to help show how the pieces build up. I can't see how you got the error you quote. It would happen there was some type that had instance Arbitrary Foo without an implementation of arbitrary and you try to call arbitrary on that type, because the method has a default implementation that just throws an error, but in the code above I can't see how that could happen. Can you share the code?Hulen
What if I need two arguments, e.g. an Int and a SafeString? the forall only generates one as I read it (e.g. testing myTake function).Sphygmic
You can supply arguments that don't need custom generators to your tests in the normal way, e.g. testWibble int = forAll genSafeString $ \str -> ...int...strHulen
thanks. Not sure on my generator error message - I tried it this way - lpaste.net/98171 - must be that I am unwrapping wrong?Sphygmic
Line 145 is your problem - it's not indented, so you've got a definition of instance Arbitrary AlphaString where with no subclauses, and then a fresh definition of a top-level arbitrary value of your own which isn't the QuickCheck one.Hulen
S
10

Currently QuickCheck has a PrintableString type, which also has an instance of arbitrary, which means that you can readily generate arbitrary strings with:

arbitraryPrintableString :: Gen String
arbitraryPrintableString = getPrintableString <$> arbitrary
Septuple answered 21/2, 2018 at 13:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.