packages feed

qc-oi-testgenerator 1.2.0.1 → 1.2.0.2

raw patch · 4 files changed

+135/−24 lines, 4 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

Files

Test/OITestGenerator.hs view
@@ -1,5 +1,116 @@ {-# LANGUAGE TemplateHaskell #-} +{-|+Module      : Test.OITestGenerator+Description : Generate operation invariance tests from axioms at compile time+Copyright   : (c) Tobias Gödderz, 2014+License     : BSD3+Maintainer  : haskell@tobias.goedderz.info+Stability   : provisional+Portability : non-portable (Template-Haskell)++("QuickCheck"-)tests for data types are often formulated as equations, i.e.++>rev_test :: Property+>rev_test = property $ \xs ys -> reverse ys ++ reverse xs == reverse (xs ++ ys)++. There are assumptions everyone usually makes about the behaviour of @(==)@,+but does not necessarily think about:++ 1. @x == x@ should always be 'True' (reflexivity).++ 2. if @x == y@ is 'True', @y == x@ should also be 'True' (symmetry).++ 3. if @x == y@ and @y == z@ are 'True', @x == z@ should also be 'True'+    (transitivity).++ 4. if @x == y@ is true, @f x == f y@ should also be 'True' (congruence or+    operation invariance).++It is unlikely to accidentally write an 'Eq' instance that does not satisfy 1-3.+Operation invariance however is fickle, as it also implies sane behaviour of all+functions on the data type.++This framework takes "QuickCheck"-like equality tests (called axioms) of a data+type and functions on the data type (called operations), and uses them to+generate the corresponding "QuickCheck" tests, as well as additional operation+invariance tests.++For example, given a queue of integers 'IntQueue' with the following operations.++>empty   :: IntQueue                    -- create an empty queue+>isEmpty :: IntQueue -> Bool            -- test if a given queue is empty+>enqueue :: Int -> IntQueue -> IntQueue -- enqueue an integer to a queue+>dequeue :: IntQueue -> IntQueue        -- dequeue the first element of a queue+>front   :: IntQueue -> Int             -- get the first element of a queue++A "QuickCheck" test for this might be++>property $ \x q -> not (isEmpty q) ==> dequeue (enqueue x q) == enqueue x (dequeue q)++while this would be formulated as an axiom as follows:++>q6 x q = not (isEmpty q) ===> dequeue (enqueue x q) =!= enqueue x (dequeue q)++Assuming you wrote six axioms @q1@-@q6@, the six corresponding "QuickCheck"+tests can be generated by 'generate_basic_tests'.+++@+{\-\# LANGUAGE TemplateHaskell \#-\}+...+queue_basic_tests :: [Property]+queue_basic_tests = $(+    let axs = map axiom ['q1, 'q2, 'q3, 'q4, 'q5, 'q6]+    in generate_basic_tests axs)+@++The single quotes preceding @q1@ to @q6@ quote each following name+as a Template Haskell 'Language.Haskell.TH.Syntax.Name'. Note that @$(...)@ is+a Template Haskell splice and will be evaluated during compile time. Because of+this, you might not refer to declarations of the same module except by name+(this is called the stage restriction), which is the motivation for the @let ...+in ...@ construct.++Additional operation invariance tests can be generated with 'generate_oi_tests'.++>may_dequeue :: IntQueue -> Bool+>may_dequeue = not . isEmpty+>+>may_front :: IntQueue -> Bool+>may_front = not . isEmpty+>+>adt_oi_tests :: [Property]+>adt_oi_tests = $(+>    let ops = [ op 'empty, op 'enqueue, op 'isEmpty+>              , op 'dequeue `withConstraint` 'may_dequeue+>              , op 'front `withConstraint` 'may_front+>              ]+>        axs = map axiom ['q1, 'q2, 'q3, 'q4, 'q5, 'q6 ]+>    in generate_oi_tests axs ops)++For every operation @f@ and every axiom @lhs =!= rhs@, a test @f lhs == f rhs@+will be generated if it is typecorrect. Note that polymorphic types aren't+supported yet, but you can simply rename and specialize them, i.e.++>reverse_ints :: [Int] -> [Int]+>reverse_ints = reverse++and test the renamed function. Besides typechecking, 'generate_oi_tests' takes+care of++ * passing axiom arguments, e.g. @f@ and @ax x = lhs x =!= rhs x@ will get+   translated to @\x -> f (lhs x) == f (rhs x)@.++ * multiple operation arguments, e.g. for an @f :: A -> A -> B -> C@ and an+   axiom with result @A@, both tests @f lhs y z == f rhs y z@ and @f x lhs z ==+   f x rhs z@ will be generated.++ * translating constraints of both axioms and operations to '==>'. Axiom+   constraints are specified via '===>', while operation constraints are+   specified via 'withConstraint'.++-} module Test.OITestGenerator (     AxiomResult, (===>), (=!=),     Axiom, axiom,@@ -29,28 +140,28 @@ -- -- It returns an expression of type @[Property]@, i.e. ----- > $(generate_basic_tests axs) :: [Property]+-- >$(generate_basic_tests axs) :: [Property] generate_basic_tests :: [Axiom] -> ExpQ generate_basic_tests = liftM ListE . mapM gen_basic_test  -- | Generate all operation invariance tests that typecheck. For example, given -- an operation ----- > f :: A -> A -> B -> C+-- >f :: A -> A -> B -> C -- -- and an axiom ----- > ax :: D -> AxiomResult A--- > ax x = lhs x =!= rhs x+-- >ax :: D -> AxiomResult A+-- >ax x = lhs x =!= rhs x -- -- the following tests are generated: ----- > property $ \y2 y3 -> f (lhs x) y2 y3 == f (rhs x) y2 y3--- > property $ \y1 y3 -> f y1 (lhs x) y3 == f y1 (rhs x) y3+-- >property $ \y2 y3 -> f (lhs x) y2 y3 == f (rhs x) y2 y3+-- >property $ \y1 y3 -> f y1 (lhs x) y3 == f y1 (rhs x) y3 -- -- but not ----- > property $ \y1 y2 -> f y1 y2 (lhs x) == f y1 y2 (rhs x)+-- >property $ \y1 y2 -> f y1 y2 (lhs x) == f y1 y2 (rhs x) -- -- because of the types. --@@ -58,7 +169,7 @@ -- -- It returns an expression of type @[Property]@, i.e. ----- > $(generate_oi_tests axs ops) :: [Property]+-- >$(generate_oi_tests axs ops) :: [Property] generate_oi_tests :: [Axiom] -> [Op] -> ExpQ generate_oi_tests = gen_tests @@ -66,7 +177,7 @@ -- -- It returns an expression of type @[Property]@, i.e. ----- > $(generate_axiom's_tests ax ops) :: [Property]+-- >$(generate_axiom's_tests ax ops) :: [Property] generate_axiom's_tests :: Axiom -> [Op] -> ExpQ generate_axiom's_tests ax = gen_tests [ax] @@ -78,7 +189,7 @@ -- -- It returns an expression of type @Property@, i.e. ----- > $(generate_single_test ax op) :: Property+-- >$(generate_single_test ax op) :: Property generate_single_test :: Axiom -> Op -> ExpQ generate_single_test ax opn = op2opArgs opn     >>= filter_Ops ax@@ -110,12 +221,12 @@ -- The first argument can replace the default function generating a name for -- each test, which is ----- > \opn argi ax -> opn ++ show argi ++ "_" ++ ax+-- >\opn argi ax -> opn ++ show argi ++ "_" ++ ax -- -- . An example output looks like this: ----- > enqueue1_q3 :: Property--- > enqueue1_q3 = $(generate_single_test (axiom 'q3) (op 'enqueue `only` 1))+-- >enqueue1_q3 :: Property+-- >enqueue1_q3 = $(generate_single_test (axiom 'q3) (op 'enqueue `only` 1)) show_all_tests :: Maybe (String -> Int -> String -> String) -> [Name] -> [Name] -> ExpQ show_all_tests Nothing axs ops = show_all_tests (Just def_test_name) axs ops show_all_tests (Just fun) axs ops =
Test/OITestGenerator/Axiom.hs view
@@ -21,13 +21,13 @@ -- | Combine the results of the left- and right-hand sides of an axiom. --   A simple example is -----   > q1 :: AxiomResult Bool---   > q1 = isEmpty empty =!= True+--   >q1 :: AxiomResult Bool+--   >q1 = isEmpty empty =!= True -- --   while an axiom with variables would be written as a function like this: -----   > q2 :: Int -> IntQueue -> AxiomResult Bool---   > q2 x q = isEmpty (enqueue x q) =!= False+--   >q2 :: Int -> IntQueue -> AxiomResult Bool+--   >q2 x q = isEmpty (enqueue x q) =!= False (=!=) :: a -> a -> AxiomResult a lhs =!= rhs = AxiomResult (True, lhs, rhs) @@ -38,8 +38,8 @@ -- --   Example usage: ----- > q4 :: Int -> IntQueue -> AxiomResult Int--- > q4 x q = not (isEmpty q) ===> front (enqueue x q) =!= front q+-- >q4 :: Int -> IntQueue -> AxiomResult Int+-- >q4 x q = not (isEmpty q) ===> front (enqueue x q) =!= front q (===>) :: Bool -> AxiomResult a -> AxiomResult a cond ===> AxiomResult (cond', lhs, rhs) = AxiomResult (cond && cond', lhs, rhs) @@ -61,11 +61,11 @@  -- | 'Axiom' constructor. Takes a 'Language.Haskell.TH.Syntax.Name', i.e. ----- > axiom 'q1+-- >axiom 'q1 -- -- or ----- > map axiom ['q1, 'q2, 'q3]+-- >map axiom ['q1, 'q2, 'q3] -- -- . axiom :: Name -> Axiom
Test/OITestGenerator/Op.hs view
@@ -49,7 +49,7 @@ -- -- Example: ----- > op 'dequeue `but` arg 1+-- >op 'dequeue `but` arg 1 but :: Op -> Arg -> Op but op' excl = modify argis (liftM $ delete excl) op' @@ -57,7 +57,7 @@ -- -- Example: ----- > op 'dequeue `only` arg 1+-- >op 'dequeue `only` arg 1 only :: Op -> Arg -> Op only op' argi = modify argis (>>= \args ->         if argi `elem` args
qc-oi-testgenerator.cabal view
@@ -2,7 +2,7 @@ -- documentation, see http://haskell.org/cabal/users-guide/  name:                qc-oi-testgenerator-version:             1.2.0.1+version:             1.2.0.2 synopsis:            Compile time generation of operation invariance tests for QuickCheck -- description: --homepage:            http://tobias.goedderz.info/haskell/qc-oi-testgenerator/