test-invariant (empty) → 0.3.1.2
raw patch · 6 files changed
+312/−0 lines, 6 filesdep +QuickCheckdep +basedep +criterionsetup-changed
Dependencies added: QuickCheck, base, criterion, tasty, tasty-quickcheck, test-invariant
Files
- LICENSE +30/−0
- Setup.hs +2/−0
- bench/Bench.hs +12/−0
- src/Test/Invariant.hs +181/−0
- test-invariant.cabal +56/−0
- test/Test.hs +31/−0
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2015, Florian Knupfer++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ * Redistributions in binary form must reproduce the above+ copyright notice, this list of conditions and the following+ disclaimer in the documentation and/or other materials provided+ with the distribution.++ * Neither the name of Florian Knupfer nor the names of other+ contributors may be used to endorse or promote products derived+ from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ bench/Bench.hs view
@@ -0,0 +1,12 @@+module Main where++import Criterion.Main+import Test.Invariant+import Test.QuickCheck++main :: IO ()+main = defaultMain benchmarks++benchmarks :: [Benchmark]+benchmarks = [ bench "Monotonic" . nfIO . quickCheck $ monotonicIncreasing (*(0::Int)) ]+
+ src/Test/Invariant.hs view
@@ -0,0 +1,181 @@+module Test.Invariant where++import Test.QuickCheck+infix 1 <=>, &>++-- | Defines extensional equality. This allows concise, point-free,+-- definitions of laws. For example idempotence:+--+-- > f <=> f . f+(<=>) :: Eq b => (a -> b) -> (a -> b) -> a -> Bool+(f <=> g) x = f x == g x++-- | Pointfree version of QuickChecks ==>. This notation reduces a+-- lot of lambdas, for example:+--+-- >>> quickCheck $ (/=0) &> not . idempotent (*(2::Int))+-- +++ OK, passed 100 tests.+(&>) :: Testable b => (a -> Bool) -> (a -> b) -> a -> Property+(a &> b) x = a x ==> b x++-- | Checks whether a function is idempotent.+--+-- > f(f(x)) == f(x)+--+-- >>> quickCheck $ idempotent (abs :: Int -> Int)+-- +++ OK, passed 100 tests.+idempotent :: Eq a => (a -> a) -> a -> Bool+idempotent f = f <=> f . f++-- | Checks whether a function is pointSymmetric.+--+-- > f(-x) == -f(x)+--+-- >>> quickCheck $ pointSymmetric (^3)+-- +++ OK, passed 100 tests.+pointSymmetric :: (Num a, Num b, Eq b) => (a -> b) -> a -> Bool+pointSymmetric f = f . negate <=> negate . f++-- | Checks whether a function is reflectionSymmetric.+--+-- > f(x) == f(-x)+--+-- >>> quickCheck $ pointSymmetric (^2)+-- +++ OK, passed 100 tests.+reflectionSymmetric :: (Num a, Eq b) => (a -> b) -> a -> Bool+reflectionSymmetric f = f . negate <=> f++-- | Checks whether a function is monotonicIncreasing.+--+-- > x >= y, f(x) >= f(y)+--+-- >>> quickCheck $ monotonicIncreasing ceiling+-- +++ OK, passed 100 tests.+monotonicIncreasing :: (Ord a, Ord b) => (a -> b) -> a -> a -> Bool+monotonicIncreasing f x y = compare (f x) (f y) `elem` [EQ, compare x y]++-- | Checks whether a function is strictly monotonicIncreasing'.+--+-- > x > y, f(x) > f(y)+--+-- >>> quickCheck $ monotonicIncreasing' (+1)+-- +++ OK, passed 100 tests.+monotonicIncreasing' :: (Ord a, Ord b) => (a -> b) -> a -> a -> Bool+monotonicIncreasing' f x y = compare (f x) (f y) == compare x y++-- | Checks whether a function is monotonicDecreasing.+--+-- > x >= y, f(x) <= f(y)+--+-- >>> quickCheck $ monotonicDecreasing (\x -> floor $ negate x)+-- +++ OK, passed 100 tests.+monotonicDecreasing :: (Ord a, Ord b) => (a -> b) -> a -> a -> Bool+monotonicDecreasing f x y = compare (f x) (f y) `elem` [EQ, compare y x]++-- | Checks whether a function is strictly monotonicDecreasing'.+--+-- > x > y, f(x) < f(y)+--+-- >>> quickCheck $ monotonicDecreasing' (-1)+-- +++ OK, passed 100 tests.+monotonicDecreasing' :: (Ord a, Ord b) => (a -> b) -> a -> a -> Bool+monotonicDecreasing' f x y = compare (f x) (f y) == compare y x++-- TODO create sorted list and fold with predicate over it++-- | Checks whether a function is involutory.+--+-- > f(f(x)) = x+--+-- >>> quickCheck $ involutory negate+-- +++ OK, passed 100 tests.+involutory :: Eq a => (a -> a) -> a -> Bool+involutory f = f . f <=> id++-- | Checks whether a function is the inverse of another function.+--+-- > f(g(x)) = x+--+-- >>> quickCheck $ (`div` 2) `inverts` (*2)+-- +++ OK, passed 100 tests.+inverts :: Eq a => (b -> a) -> (a -> b) -> a -> Bool+f `inverts` g = f . g <=> id++-- | Checks whether an binary operator is commutative.+--+-- > a * b = b * a+--+-- >>> quickCheck $ commutative (+)+-- +++ OK, passed 100 tests.+commutative :: Eq b => (a -> a -> b) -> a -> a -> Bool+commutative f x y = x `f` y == y `f` x++-- | Checks whether an binary operator is associative.+--+-- > a + (b + c) = (a + b) + c+--+-- >>> quickCheck $ associative (+)+-- +++ OK, passed 100 tests.+associative :: Eq a => (a -> a -> a) -> a -> a -> a -> Bool+associative f x y z = x `f` (y `f` z) == (x `f` y) `f` z++-- | Checks whether an operator is left-distributive over an other operator.+--+-- > a * (b + c) = (a * b) + (a * c)+--+-- >>> quickCheck $ (*) `distributesLeftOver` (+)+-- +++ OK, passed 100 tests.+distributesLeftOver :: Eq a => (a -> a -> a) -> (a -> a -> a) -> a -> a -> a -> Bool+(f `distributesLeftOver` g) x y z = x `f` (y `g` z) == (x `f` y) `g` (x `f` z)++-- | Checks whether an operator is right-distributive over an other operator.+--+-- > (b + c) / a = (b / a) + (c / a)+--+-- >>> quickCheck $ (/) `distributesRightOver` (+)+-- +++ OK, passed 100 tests.+distributesRightOver :: Eq a => (a -> a -> a) -> (a -> a -> a) -> a -> a -> a -> Bool+(f `distributesRightOver` g) x y z = (y `g` z) `f` x == (x `f` y) `g` (x `f` z)++-- | Checks whether an operator is distributive over an other operator.+--+-- > a * (b + c) = (a * b) + (a * c) = (b + c) * a+--+-- >>> quickCheck $ (*) `distributesOver` (+)+-- +++ OK, passed 100 tests.+distributesOver :: Eq a => (a -> a -> a) -> (a -> a -> a) -> a -> a -> a -> Bool+(f `distributesOver` g) x y z = (f `distributesLeftOver` g) x y z+ && (f `distributesRightOver` g) x y z++-- | Checks whether a function increases the size of a foldable.+--+-- >>> quickCheck $ inflating (1:)+-- +++ OK, passed 100 tests.+inflating :: (Foldable f, Foldable f') => (f a -> f' b) -> f a -> Bool+inflating f xs = length (f xs) > length xs++-- | Checks whether a function decreases the size of a foldable.+--+--+-- >>> quickCheck $ deflating tail+-- +++ OK, passed 100 tests.+deflating :: (Foldable f, Foldable f') => (f a -> f' b) -> f a -> Bool+deflating f xs = null xs || length (f xs) < length xs++-- | Checks whether a function is cyclic by applying its result to+-- itself within n applications.+--+-- >>> quickCheck $ (`div` 10) `cyclesWithin` 100+-- +++ OK, passed 100 tests.+cyclesWithin :: Eq a => (a -> a) -> Int -> a -> Bool+f `cyclesWithin` n = go [] . take (n + 1) . iterate f+ where go xs (y:ys) | y `elem` xs = True+ | otherwise = go (y:xs) ys+ go _ _ = False++-- | Checks whether a function is invariant over an other function.+--+-- >>> quickCheck $ length `invariatesOver` reverse+-- +++ OK, passed 100 tests.+invariatesOver :: Eq b => (a -> b) -> (a -> a) -> a -> Bool+f `invariatesOver` g = f . g <=> f
+ test-invariant.cabal view
@@ -0,0 +1,56 @@+name: test-invariant+version: 0.3.1.2+synopsis: Provide common invariants to be checked with QuickCheck++description: test-invariant is a library for providing common invariants of+ functions as higher order polymorphic functions. This reduces for a+ lot of cases the need for writing prop_ functions for QuickCheck.+ .+ >>> quickCheck $ idempotent (abs :: Int -> Int)+ >>> quickCheck $ involutory not+ >>> quickCheck $ not . involutory (+ (2 :: Int))++license: BSD3+license-file: LICENSE+author: Florian Knupfer <fknupfer@gmail.com>+maintainer: Florian Knupfer <fknupfer@gmail.com>+copyright: 2015 Florian Knupfer+category: Testing+build-type: Simple+homepage: https://github.com/knupfer/test-invariant+-- extra-source-files:+cabal-version: >=1.10++source-repository head+ type: git+ location: https://github.com/knupfer/test-invariant+ +library+ exposed-modules: Test.Invariant+ -- other-modules:+ -- other-extensions:+ build-depends: base >=4.8 && <4.9+ , QuickCheck+ hs-source-dirs: src+ default-language: Haskell2010++test-suite Tasty+ type: exitcode-stdio-1.0+ build-depends: base >=4.8 && <4.9+ , test-invariant+ , QuickCheck+ , tasty+ , tasty-quickcheck+ hs-source-dirs: test+ main-is: Test.hs+ default-language: Haskell2010++benchmark Criterion+ build-depends: base+ , test-invariant+ , criterion+ , QuickCheck+ default-language: Haskell2010+ hs-source-dirs: bench+ main-is: Bench.hs+ type: exitcode-stdio-1.0
+ test/Test.hs view
@@ -0,0 +1,31 @@+module Main where++import Test.Invariant+import Test.Tasty+import Test.Tasty.QuickCheck++main :: IO ()+main = defaultMain $+ testGroup "Tests"+ [ testGroup "Arity 1"+ [ testProperty "Idempotence" $+ idempotent (* (0 :: Int))++ , testProperty "Idempotence" $+ (/=0) &> not . idempotent (* (2 :: Int))++ , testProperty "Idempotence" $+ idempotent (abs :: Double -> Double)++ , testProperty "Idempotence" $+ idempotent (signum :: Int -> Int)++ , testProperty "Idempotence" $+ ((*) :: Int -> Int -> Int) `distributesOver` (+)++ , testProperty "Idempotence" $+ (*) `distributesOver` ((+) :: Int -> Int -> Int)+ ]+ ]++