test-framework-quickcheck2 0.3.0 → 0.3.0.1
raw patch · 2 files changed
+38/−179 lines, 2 filesdep ~test-framework
Dependency ranges changed: test-framework
Files
Test/Framework/Providers/QuickCheck2.hs view
@@ -8,20 +8,11 @@ import Test.Framework.Providers.API -import Test.QuickCheck.Gen-import Test.QuickCheck.Property hiding ( Property, Result( reason, interrupted ) )-import qualified Test.QuickCheck.Property as P+import Test.QuickCheck.Property (Testable, Callback(PostTest), CallbackKind(NotCounterexample), callback)+import Test.QuickCheck.State (numSuccessTests) import Test.QuickCheck.Test-import Test.QuickCheck.Text-import Test.QuickCheck.State -import Control.Concurrent.MVar-import qualified Control.Exception.Extensible as E--import Data.IORef import Data.Typeable-import System.Random-import Unsafe.Coerce -- | Create a 'Test' for a QuickCheck2 'Testable' property@@ -43,22 +34,20 @@ -- tests previously run if the test times out, hence we need a Maybe here for that case. } -data PropertyStatus = PropertyOK -- ^ The property is true as far as we could check it- | PropertyArgumentsExhausted -- ^ The property may be true, but we ran out of arguments to try it out on- | PropertyFalsifiable String -- ^ The property was not true. The string is the reason.- | PropertyNoExpectedFailure -- ^ We expected that a property would fail but it didn't- | PropertyTimedOut -- ^ The property timed out during execution- | PropertyException String -- ^ The property raised an exception during execution+data PropertyStatus = PropertyOK -- ^ The property is true as far as we could check it+ | PropertyArgumentsExhausted -- ^ The property may be true, but we ran out of arguments to try it out on+ | PropertyFalsifiable String String -- ^ The property was not true. The strings are the reason and the output.+ | PropertyNoExpectedFailure -- ^ We expected that a property would fail but it didn't+ | PropertyTimedOut -- ^ The property timed out during execution instance Show PropertyResult where show (PropertyResult { pr_status = status, pr_used_seed = used_seed, pr_tests_run = mb_tests_run }) = case status of- PropertyOK -> "OK, passed " ++ tests_run_str ++ " tests"- PropertyArgumentsExhausted -> "Arguments exhausted after " ++ tests_run_str ++ " tests"- PropertyFalsifiable fail_reason -> "Falsifiable with seed " ++ show used_seed ++ ", after " ++ tests_run_str ++ " tests. Reason: " ++ fail_reason- PropertyNoExpectedFailure -> "No expected failure with seed " ++ show used_seed ++ ", after " ++ tests_run_str ++ " tests"- PropertyTimedOut -> "Timed out after " ++ tests_run_str ++ " tests"- PropertyException text -> "Got an exception: " ++ text+ PropertyOK -> "OK, passed " ++ tests_run_str ++ " tests"+ PropertyArgumentsExhausted -> "Arguments exhausted after " ++ tests_run_str ++ " tests"+ PropertyFalsifiable _rsn otpt -> otpt ++ "(used seed " ++ show used_seed ++ ")"+ PropertyNoExpectedFailure -> "No expected failure with seed " ++ show used_seed ++ ", after " ++ tests_run_str ++ " tests"+ PropertyTimedOut -> "Timed out after " ++ tests_run_str ++ " tests" where tests_run_str = fmap show mb_tests_run `orElse` "an unknown number of" @@ -78,162 +67,32 @@ runProperty :: Testable a => CompleteTestOptions -> a -> IO (PropertyTestCount :~> PropertyResult, IO ()) runProperty topts testable = do- (seed, mk_state) <- initialState topts- runImprovingIO $ do- mb_result <- maybeTimeoutImprovingIO (unK (topt_timeout topts)) $ do- (state, get_out) <- liftIO mk_state- myTest state get_out (unGen (property testable))- return $ toPropertyResult seed $ case mb_result of- Nothing -> (PropertyTimedOut, Nothing)- Just (status, tests_run) -> (status, Just tests_run)- where- toPropertyResult seed (status, mb_tests_run) = PropertyResult {- pr_status = status,- pr_used_seed = seed,- pr_tests_run = mb_tests_run- }----- I've copied these parts out of QuickCheck 2 source code and modified them to fit my purpose. My--- central problem with using the code as-is is that it insists on writing to stdout!--initialState :: CompleteTestOptions -> IO (Int, IO (State, IO String))-initialState topts = do (gen, seed) <- newSeededStdGen (unK $ topt_seed topts)- let max_success = unK $ topt_maximum_generated_tests topts- max_size = unK $ topt_maximum_test_size topts-- -- Copied from the unexported function Test.QuickCheck.Text.output- -- Very horrible hack here since the Output data constructor is also not exported!- output f = do- r <- newIORef ""- return (unsafeCoerce (TestQuickCheckTextOutput f r))-- mk_state = do- -- My code is very careful not to write to the terminal since it will screw up my own- -- output code, but I need to fill in the terminal field anyway. This is useful for- -- catching the failing examples that get written to the output.- out_var <- newMVar ""- out <- output $ \extra -> modifyMVar_ out_var $ \str -> return (str ++ extra)- tm <- newTerminal out out-- return (MkState { terminal = tm- , maxSuccessTests = unK $ topt_maximum_generated_tests topts- , maxDiscardedTests = unK $ topt_maximum_unsuitable_generated_tests topts- , computeSize = \n d -> (n * max_size) `div` max_success + (d `div` 10)- , numSuccessTests = 0- , numDiscardedTests = 0- , collected = []- , expectedFailure = False- , randomSeed = gen- , numSuccessShrinks = 0- , numTryShrinks = 0+ max_discard = unK $ topt_maximum_unsuitable_generated_tests topts+ args = stdArgs { replay = Just (gen, 0) -- NB: the 0 is the saved size. Defaults to 0 if you supply "Nothing" for "replay".+ , maxSuccess = max_success #if MIN_VERSION_QuickCheck(2,5,0)- , numTotTryShrinks = 0+ , maxDiscardRatio = max_discard `div` max_success+#else+ , maxDiscard = max_discard #endif- },- modifyMVar out_var $ \str -> return ("", str))- return (seed, mk_state)---- If this doesn't exactly match the definition of Test.QuickCheck.Text.Out you can get segfaults-data TestQuickCheckTextOutput = TestQuickCheckTextOutput (String -> IO ()) (IORef String)----- NB: could use (summary st) in the messages produced by myTest-myTest :: State -> IO String -> (StdGen -> Int -> Prop) -> ImprovingIO PropertyTestCount f (PropertyStatus, PropertyTestCount)-myTest st get_out f- | ntest >= maxSuccessTests st = return (if expectedFailure st then PropertyOK else PropertyNoExpectedFailure, ntest)- | numDiscardedTests st >= maxDiscardedTests st = return (PropertyArgumentsExhausted, ntest)- | otherwise = yieldImprovement ntest >> myRunATest st get_out f- where ntest = numSuccessTests st--myRunATest :: State -> IO String -> (StdGen -> Int -> Prop) -> ImprovingIO PropertyTestCount f (PropertyStatus, PropertyTestCount)-myRunATest st get_out f = do- let size = computeSize st (numSuccessTests st) (numDiscardedTests st)- -- Careful to catch exceptions, or else they might bring down the whole test framework- ei_st_res <- liftIO $ flip E.catch (\e -> return $ Left $ show (e :: E.SomeException)) $ do- MkRose res ts <- protectRose (reduceRose (unProp (f rnd1 size)))- return (Right (res, ts))- - case ei_st_res of- Left text -> liftIO get_out >>= \extra_text -> return (PropertyException (text ++ extra_text), numSuccessTests st + 1)- Right (res, ts) -> do- liftIO $ callbackPostTest st res- - case res of- MkResult{ok = Just True, stamp = stamp, expect = expect} -> -- successful test- do myTest st{ numSuccessTests = numSuccessTests st + 1- , randomSeed = rnd2- , collected = stamp : collected st- , expectedFailure = expect- } get_out f- - MkResult{ok = Nothing, expect = expect} -> -- discarded test- do myTest st{ numDiscardedTests = numDiscardedTests st + 1- , randomSeed = rnd2- , expectedFailure = expect- } get_out f- - MkResult{ok = Just False} -> -- failed test- do if expect res- then liftIO $ myFoundFailure st get_out res ts- -- Could terminate immediately without any shrinking by doing this instead:- -- return (PropertyFalsifiable (reason res), numSuccessTests st + 1)- else return (PropertyOK, numSuccessTests st + 1)+ , maxSize = unK $ topt_maximum_test_size topts+ , chatty = False }+ -- FIXME: yield gradual improvement after each test+ runImprovingIO $ do+ tunnel <- tunnelImprovingIO+ mb_result <- maybeTimeoutImprovingIO (unK (topt_timeout topts)) $+ liftIO $ quickCheckWithResult args (callback (PostTest NotCounterexample (\s _r -> tunnel $ yieldImprovement $ numSuccessTests s)) testable)+ return $ case mb_result of+ Nothing -> PropertyResult { pr_status = PropertyTimedOut, pr_used_seed = seed, pr_tests_run = Nothing }+ Just result -> PropertyResult {+ pr_status = toPropertyStatus result,+ pr_used_seed = seed,+ pr_tests_run = Just (numTests result)+ } where- (rnd1,rnd2) = split (randomSeed st)----- | This function eventually reports a failure but attempts to shrink the counterexample before it does so-myFoundFailure :: State -> IO String -> P.Result -> [Rose P.Result] -> IO (PropertyStatus, PropertyTestCount)-myFoundFailure st get_out res ts =- do myLocalMin st{ numTryShrinks = 0 } get_out res ts--myLocalMin :: State -> IO String -> P.Result -> [Rose P.Result] -> IO (PropertyStatus, PropertyTestCount)-myLocalMin st get_out res _ | P.interrupted res = myLocalMinFound st get_out res-myLocalMin st get_out res ts = do- r <- tryEvaluate ts- case r of- Left err ->- myLocalMinFound st get_out- (exception "Exception while generating shrink-list" err) { callbacks = callbacks res }- Right ts' -> myLocalMin' st get_out res ts'---myLocalMin' :: State -> IO String -> P.Result -> [Rose P.Result] -> IO (PropertyStatus, PropertyTestCount)-myLocalMin' st get_out res [] = myLocalMinFound st get_out res-myLocalMin' st get_out res (t:ts) =- do -- CALLBACK before_test- MkRose res' ts' <- protectRose (reduceRose t)- callbackPostTest st res'- -- NB: both (numSuccessShrinks st) (numTryShrinks st) contain interesting information here.- -- I'm not going to output any message at all here (unlike QuickCheck2) because I want a- -- single error message I can give to the user.- if ok res' == Just False- then myFoundFailure st{ numSuccessShrinks = numSuccessShrinks st + 1 } get_out res' ts'- else myLocalMin st{ numTryShrinks = numTryShrinks st + 1 } get_out res ts--myLocalMinFound :: State -> IO String -> P.Result -> IO (PropertyStatus, PropertyTestCount)-myLocalMinFound st get_out res =- do callbackPostFinalFailure st res- -- NB: could use (numSuccessShrinks st) in the message somehow- extra_text <- get_out- let reason = dropWhileRev (`elem` "\r\n") extra_text ++- if P.reason res /= "Falsifiable" then P.reason res else ""- return (PropertyFalsifiable reason, numSuccessTests st + 1)--dropWhileRev :: (a -> Bool) -> [a] -> [a]-dropWhileRev p = reverse . dropWhile p . reverse-------- Hidden module Test.QuickCheck.Exception-----tryEvaluate :: a -> IO (Either E.SomeException a)-tryEvaluate x = tryEvaluateIO (return x)--tryEvaluateIO :: IO a -> IO (Either E.SomeException a)-tryEvaluateIO m = E.try (m >>= E.evaluate)---tryEvaluateIO m = Right `fmap` m+ toPropertyStatus (Success {}) = PropertyOK+ toPropertyStatus (GaveUp {}) = PropertyArgumentsExhausted+ toPropertyStatus (Failure { reason = rsn, output = otpt }) = PropertyFalsifiable rsn otpt+ toPropertyStatus (NoExpectedFailure {}) = PropertyNoExpectedFailure
test-framework-quickcheck2.cabal view
@@ -1,5 +1,5 @@ Name: test-framework-quickcheck2-Version: 0.3.0+Version: 0.3.0.1 Cabal-Version: >= 1.2.3 Category: Testing Synopsis: QuickCheck2 support for the test-framework package.@@ -22,7 +22,7 @@ Library Exposed-Modules: Test.Framework.Providers.QuickCheck2 - Build-Depends: test-framework >= 0.6, QuickCheck >= 2.4 && < 2.6, extensible-exceptions >= 0.1.1 && < 0.2.0+ Build-Depends: test-framework >= 0.7.1, QuickCheck >= 2.4 && < 2.6, extensible-exceptions >= 0.1.1 && < 0.2.0 if flag(base3) Build-Depends: base >= 3 && < 4, random >= 1 else