Although I’m employing property-based testing for several years already, I keep on being surprised about its ability to find obscure bugs before they occur in production. I’m using the Hypothesis Python package regularily in software projects and lately it helped me to catch a very simple but still somehow surprising bug while working on tmtoolkit:
I had a simple function that converted Windows line breaks to UNIX line breaks. Something like:
What could go wrong with such a simple function? Let’s write a property-based test with Hypothesis that generates strings with maximum length of 20 characters from an alphabet of
c, space, carriage return
\r and line feed
\n. We only check for the property that after conversion, there shouldn’t be any Windows line breaks
\r\n left in the converted string.
If you (like me) didn’t give much thought into the problem, because it seemed to simple, you’ll be surprised that the test fails:
On second thought, though, it is clear why this happens: If you have a string that contains
\r\r\n, only the last two characters will be translated to
\n which in the end leads to the string
\r\n so that the result string still contains a Windows line break. A possible solution would be to perform the replacements iteratively:
It’s natural not to think about such issues, because when thinking about which input such a function could receive, you’d imagine files with lines that end with
\r\n. You wouldn’t probably come up with edge cases like a file that contains
\r\r\n. But it is these edge cases that make programs fail, not the “regular” inputs. Property-based testing helps to find such edge cases.