falsify-0.4.0: test/TestSuite/Sanity/Selective.hs
module TestSuite.Sanity.Selective (tests) where
import Control.Selective
import Data.Word
import System.Timeout
import Test.Tasty
import Test.Tasty.HUnit
import Data.Falsify.Tree (Tree(..))
import Test.Falsify
import Test.Falsify.Interactive (sample, shrink')
import qualified Test.Falsify.Generator as Gen
{-------------------------------------------------------------------------------
List of tests
-------------------------------------------------------------------------------}
tests :: TestTree
tests = testGroup "TestSuite.Sanity.Selective" [
testGroup "tree" [
testCaseInfo "ifBoth" test_tree_ifBoth
, testGroup "ifS" [
testCase "10" $ test_tree_ifS 10
, testCase "20" $ test_tree_ifS 20
, testCase "30" $ test_tree_ifS 30
, testCase "40" $ test_tree_ifS 40
, testCase "50" $ test_tree_ifS 50
, testCase "60" $ test_tree_ifS 60
, testCase "70" $ test_tree_ifS 70
, testCase "80" $ test_tree_ifS 80
, testCase "90" $ test_tree_ifS 90
, testCase "100" $ test_tree_ifS 100
]
]
]
{-------------------------------------------------------------------------------
Tree
In this test we construct a "biased tree" (aka list) using a generator for a
/complete/ tree but then only using part of the result. Clearly, if we
/actually/ used the entire complete tree, this would have exponential
complexity, so that's not an option.
The problem is not in /generation/, which is sufficiently lazy, but in
shrinking. With the monadic interface, there are two non-solutions:
- With the shrinking shortcut in place (reducing entire prats of the tree
to 'Minimal'), then shrinking isn't all that interesting: the part of the
tree we're not using will be set to all zeroes immediately (this is what
the @either@ examples were demonstrating)
- Without the shrinking shortcut in place, the /generator/ might not look
at the full complete tree, but the /shrinker/ will, and so shrinking will
have abysmal performance. This is demonstrated in 'test_tree_ifBoth'.
With the selective interface, however, everything works just fine.
-------------------------------------------------------------------------------}
test_tree_ifBoth :: IO String
test_tree_ifBoth = do
let depth = 15
-- Verify that we /don't/ get a timeout during generation
sampled <- sample (tree ifBoth depth)
assertBool "initial" $ isBiased sampled
-- But we /do/ get a timeout during shrinking
didTimeout <- timeout 10_000_000 $ do
Just history <- shrink' Just (tree ifBoth depth)
assertBool "shrunk" $ all isBiased history
return history
case didTimeout of
Nothing -> return "Timed out as expected"
Just history -> assertFailure $ unlines [
"Expected timeout, but did not get it. "
, "Shrink history: " ++ show history
]
test_tree_ifS :: Word64 -> Assertion
test_tree_ifS depth = do
sampled <- sample (tree ifS depth)
assertBool "initial" $ isBiased sampled
Just shrunk <- shrink' Just (tree ifS depth)
assertBool "shrunk" $ all isBiased shrunk
isBiased :: Tree a -> Bool
isBiased Leaf = True
isBiased (Branch _ Leaf t ) = isBiased t
isBiased (Branch _ t Leaf ) = isBiased t
isBiased (Branch _ Branch{} Branch{}) = False
tree ::
(forall a. Gen Bool -> Gen a -> Gen a -> Gen a)
-> Word64 -> Gen (Tree Word64)
tree if_ = go
where
go :: Word64 -> Gen (Tree Word64)
go 0 = pure Leaf
go d =
Gen.prim `Gen.bindWithoutShortcut` \x ->
if_ ((== 0) <$> Gen.prim)
((\t -> Branch x t Leaf) <$> go (d - 1))
((\t -> Branch x Leaf t) <$> go (d - 1))
{-------------------------------------------------------------------------------
Generic auxiliary
-------------------------------------------------------------------------------}
ifBoth :: Gen Bool -> Gen a -> Gen a -> Gen a
ifBoth cond t f =
t `Gen.bindWithoutShortcut` \x ->
f `Gen.bindWithoutShortcut` \y ->
cond `Gen.bindWithoutShortcut` \b ->
return $ if b then x else y