Skip to content

Commit 3e5427f

Browse files
authored
Merge pull request #54 from purescript/tailrec
Use tailRecM to avoid stack overflows
2 parents ccbce47 + 9c2ebeb commit 3e5427f

File tree

1 file changed

+47
-25
lines changed

1 file changed

+47
-25
lines changed

src/Test/QuickCheck.purs

Lines changed: 47 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -33,16 +33,22 @@ module Test.QuickCheck
3333

3434
import Prelude
3535

36-
import Control.Monad.Eff (Eff())
37-
import Control.Monad.Eff.Console (CONSOLE(), log)
38-
import Control.Monad.Eff.Exception (EXCEPTION(), throwException, error)
39-
import Control.Monad.Eff.Random (RANDOM())
40-
41-
import Data.List (List(..))
36+
import Control.Monad.Eff (Eff)
37+
import Control.Monad.Eff.Console (CONSOLE, log)
38+
import Control.Monad.Eff.Exception (EXCEPTION, throwException, error)
39+
import Control.Monad.Eff.Random (RANDOM)
40+
import Control.Monad.Rec.Class (Step(..), tailRec)
41+
42+
import Data.Foldable (for_)
43+
import Data.List (List)
44+
import Data.Maybe (Maybe(..))
45+
import Data.Maybe.First (First(..))
46+
import Data.Monoid (mempty)
47+
import Data.Tuple (Tuple(..))
4248
import Data.Unfoldable (replicateA)
4349

4450
import Test.QuickCheck.Arbitrary (class Arbitrary, arbitrary)
45-
import Test.QuickCheck.Gen (Gen, evalGen)
51+
import Test.QuickCheck.Gen (Gen, evalGen, runGen)
4652
import Test.QuickCheck.LCG (Seed, randomSeed)
4753

4854
-- | A type synonym which represents the effects used by the `quickCheck` function.
@@ -52,36 +58,52 @@ type QC eff a = Eff (console :: CONSOLE, random :: RANDOM, err :: EXCEPTION | ef
5258
-- |
5359
-- | This function generates a new random seed, runs 100 tests and
5460
-- | prints the test results to the console.
55-
quickCheck :: forall eff prop. (Testable prop) => prop -> QC eff Unit
61+
quickCheck :: forall eff prop. Testable prop => prop -> QC eff Unit
5662
quickCheck prop = quickCheck' 100 prop
5763

5864
-- | A variant of the `quickCheck` function which accepts an extra parameter
5965
-- | representing the number of tests which should be run.
60-
quickCheck' :: forall eff prop. (Testable prop) => Int -> prop -> QC eff Unit
66+
quickCheck' :: forall eff prop. Testable prop => Int -> prop -> QC eff Unit
6167
quickCheck' n prop = do
6268
seed <- randomSeed
63-
let results = quickCheckPure seed n prop
64-
let successes = countSuccesses results
65-
log $ show successes <> "/" <> show n <> " test(s) passed."
66-
throwOnFirstFailure one results
67-
69+
let result = tailRec loop { seed, index: 0, successes: 0, firstFailure: mempty }
70+
log $ show result.successes <> "/" <> show n <> " test(s) passed."
71+
for_ result.firstFailure \{ index, message } ->
72+
throwException $ error $ "Test " <> show (index + 1) <> " failed: \n" <> message
6873
where
69-
70-
throwOnFirstFailure :: Int -> List Result -> QC eff Unit
71-
throwOnFirstFailure _ Nil = pure unit
72-
throwOnFirstFailure n (Cons (Failed msg) _) = throwException $ error $ "Test " <> show n <> " failed: \n" <> msg
73-
throwOnFirstFailure n (Cons _ rest) = throwOnFirstFailure (n + one) rest
74-
75-
countSuccesses :: List Result -> Int
76-
countSuccesses Nil = zero
77-
countSuccesses (Cons Success rest) = one + countSuccesses rest
78-
countSuccesses (Cons _ rest) = countSuccesses rest
74+
loop :: LoopState -> Step LoopState (LoopResult ())
75+
loop { seed, index, successes, firstFailure }
76+
| index == n = Done { successes, firstFailure }
77+
| otherwise =
78+
case runGen (test prop) { newSeed: seed, size: 10 } of
79+
Tuple Success s ->
80+
Loop
81+
{ seed: s.newSeed
82+
, index: index + 1
83+
, successes: successes + 1
84+
, firstFailure
85+
}
86+
Tuple (Failed message) s ->
87+
Loop
88+
{ seed: s.newSeed
89+
, index: index + 1
90+
, successes
91+
, firstFailure: firstFailure <> First (Just { index, message })
92+
}
93+
94+
type LoopResult r =
95+
{ successes :: Int
96+
, firstFailure :: First { index :: Int, message :: String }
97+
| r
98+
}
99+
100+
type LoopState = LoopResult (seed :: Seed, index :: Int)
79101

80102
-- | Test a property, returning all test results as an array.
81103
-- |
82104
-- | The first argument is the _random seed_ to be passed to the random generator.
83105
-- | The second argument is the number of tests to run.
84-
quickCheckPure :: forall prop. (Testable prop) => Seed -> Int -> prop -> List Result
106+
quickCheckPure :: forall prop. Testable prop => Seed -> Int -> prop -> List Result
85107
quickCheckPure s n prop = evalGen (replicateA n (test prop)) { newSeed: s, size: 10 }
86108

87109
-- | The `Testable` class represents _testable properties_.

0 commit comments

Comments
 (0)