Skip to content

Commit

Permalink
Preparation for release. (#15)
Browse files Browse the repository at this point in the history
  • Loading branch information
jonathanknowles authored Feb 5, 2023
2 parents 02afd6c + 9c30c9c commit 7c91aab
Show file tree
Hide file tree
Showing 10 changed files with 411 additions and 129 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# 0.0.0.0

- Provides support for testing instances of classes defined in the following
modules:
- `Data.Group`
77 changes: 76 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,76 @@
QuickCheck support for testing instances of classes defined in the `groups` library.
# `quickcheck-groups`

<a href="http://jonathanknowles.net/quickcheck-groups/"><img src="https://img.shields.io/badge/API-Documentation-green" /></a>

## Overview

The `quickcheck-groups` library provides:
- [QuickCheck](https://hackage.haskell.org/package/QuickCheck) support for testing instances of type classes defined in the [`groups`](https://hackage.haskell.org/package/groups) library.
- Compatibility with the [`quickcheck-classes`](https://hackage.haskell.org/package/quickcheck-classes) library.
- Reusable properties for type class laws, in the form of [`Laws`](https://hackage.haskell.org/package/quickcheck-classes/docs/Test-QuickCheck-Classes.html#t:Laws) definitions.

## Usage

In general, usage is identical to that of the [`quickcheck-classes`](https://hackage.haskell.org/package/quickcheck-classes) library. If you're already familiar with [`quickcheck-classes`](https://hackage.haskell.org/package/quickcheck-classes), then using this library should be straightforward.

### Testing laws for a single type class

To test that the laws of a particular class hold for a particular type, use the [`lawsCheck`](https://hackage.haskell.org/package/quickcheck-classes/docs/Test-QuickCheck-Classes.html#v:lawsCheck) function with the [`Laws`](https://hackage.haskell.org/package/quickcheck-classes/docs/Test-QuickCheck-Classes.html#t:Laws) definition for the class you wish to test.

> #### :stars: Example
>
> To test that the [`Group`](https://hackage.haskell.org/package/groups/docs/Data-Group.html#t:Group) laws hold for the [`Sum`](https://hackage.haskell.org/package/base/docs/Data-Monoid.html#t:Sum) [`Integer`](https://hackage.haskell.org/package/base/docs/Prelude.html#t:Integer) type:
>
> ```hs
> import Data.Monoid (Sum)
> import Data.Proxy (Proxy (Proxy))
> import Test.QuickCheck.Classes (lawsCheck)
> import Test.QuickCheck.Classes.Group (groupLaws)
>
> lawsCheck (groupLaws (Proxy :: Proxy (Sum Integer)))
> ```
>
> If all tests pass, you should see output similar to:
>
> ```hs
> Group: groupLaw_invert_mempty +++ OK, passed 1 test.
> Group: groupLaw_invert_invert +++ OK, passed 100 tests.
> Group: groupLaw_invert_mappend_1 +++ OK, passed 100 tests.
> Group: groupLaw_invert_mappend_2 +++ OK, passed 100 tests.
> Group: groupLaw_subtract_mempty +++ OK, passed 100 tests.
> Group: groupLaw_subtract_self +++ OK, passed 100 tests.
> Group: groupLaw_subtract_other +++ OK, passed 100 tests.
> Group: groupLaw_pow_zero +++ OK, passed 100 tests.
> Group: groupLaw_pow_nonNegative +++ OK, passed 100 tests.
> Group: groupLaw_pow_nonPositive +++ OK, passed 100 tests.
> ```
### Testing laws for multiple type classes
To test that the laws of __multiple__ classes hold for a particular type, use the [`lawsCheckOne`](https://hackage.haskell.org/package/quickcheck-classes/docs/Test-QuickCheck-Classes.html#v:lawsCheckOne) function with the [`Laws`](https://hackage.haskell.org/package/quickcheck-classes/docs/Test-QuickCheck-Classes.html#t:Laws) definitions for the classes you wish to test.
> #### :stars: Example
>
> To test that the [`Sum`](https://hackage.haskell.org/package/base/docs/Data-Monoid.html#t:Sum) [`Integer`](https://hackage.haskell.org/package/base/docs/Prelude.html#t:Integer) type satisfies the laws of [`Abelian`](https://hackage.haskell.org/package/groups/docs/Data-Group.html#t:Abelian) and all superclasses:
>
> ```hs
> import Data.Monoid (Sum)
> import Data.Proxy (Proxy (Proxy))
> import Test.QuickCheck.Classes
> import Test.QuickCheck.Classes.Group
>
> lawsCheckOne (Proxy :: Proxy (Sum Integer))
> [ semigroupLaws
> , monoidLaws
> , groupLaws
> , abelianLaws
> ]
> ```
## Subclasses and superclasses
Each of the [`Laws`](https://hackage.haskell.org/package/quickcheck-classes/docs/Test-QuickCheck-Classes.html#t:Laws) definitions provided by this library corresponds to exactly __one__ type class, and includes __just__ the laws for that class. Laws for subclasses and superclasses are __not__ automatically included. Therefore, you'll need to __explicitly__ test the laws of every single class you wish to cover.
## Coverage checks
This library includes __coverage checks__ to ensure that important cases are covered, and to reduce the probability of test passes that are false positives. These coverage checks are performed automatically.
96 changes: 52 additions & 44 deletions quickcheck-groups.cabal
Original file line number Diff line number Diff line change
@@ -1,27 +1,41 @@
cabal-version: 3.0
name: quickcheck-groups
version: 0.0.0
version: 0.0.0.0
bug-reports: https://github.com/jonathanknowles/quickcheck-groups/issues
license: Apache-2.0
license-file: LICENSE
author: Jonathan Knowles
maintainer: [email protected]
copyright: 2022–2023 Jonathan Knowles
category: Testing
synopsis: QuickCheck support for testing instances of classes defined in
the groups library.
synopsis: Testing group class instances with QuickCheck
description:

This library provides:

* QuickCheck support for testing instances of classes defined in the
'groups' library.
* Reusable properties in the form of 'Laws' definitions.
QuickCheck support for testing instances of type classes defined in the
groups library.

extra-source-files:
CHANGELOG.md
README.md

common common-extensions
common dependency-base
build-depends:base >= 4.14.3.0 && < 4.18
common dependency-hspec
build-depends:hspec >= 2.10.7 && < 2.11
common dependency-groups
build-depends:groups >= 0.5.3 && < 0.6
common dependency-pretty-show
build-depends:pretty-show >= 1.10 && < 1.11
common dependency-QuickCheck
build-depends:QuickCheck >= 2.14.2 && < 2.15
common dependency-quickcheck-classes
build-depends:quickcheck-classes >= 0.6.5.0 && < 0.7
common dependency-quickcheck-instances
build-depends:quickcheck-instances >= 0.3.28 && < 0.4
common dependency-semigroupoids
build-depends:semigroupoids >= 5.3.7 && < 5.4

common extensions
default-extensions:
DerivingStrategies
FlexibleContexts
Expand All @@ -39,78 +53,72 @@ source-repository head

library
import:
common-extensions
, dependency-base
, dependency-groups
, dependency-QuickCheck
, dependency-quickcheck-classes
, extensions
hs-source-dirs:
src/public
exposed-modules:
Test.QuickCheck.Classes.Group
default-language:
Haskell2010
build-depends:
, base >=4.7 && <5
, groups
, QuickCheck
, quickcheck-classes
, quickcheck-groups:internal
, internal

library internal
import:
common-extensions
, dependency-base
, dependency-pretty-show
, dependency-QuickCheck
, dependency-semigroupoids
, extensions
hs-source-dirs:
src/internal
exposed-modules:
Data.Semigroup.Eq
Test.QuickCheck.Classes.Group.Internal
Test.QuickCheck.Classes.Group.Tuple
Internal
Internal.Semigroup.Eq
Internal.Semigroup.Tuple
default-language:
Haskell2010
build-depends:
, base >=4.7 && <5
, pretty-show
, QuickCheck
, semigroupoids

library prelude
import:
common-extensions
, dependency-base
, dependency-groups
, dependency-QuickCheck
, dependency-quickcheck-classes
, dependency-quickcheck-instances
, extensions
hs-source-dirs:
src/prelude
exposed-modules:
Test.QuickCheck.Classes.Group.Prelude
Internal.Prelude
default-language:
Haskell2010
build-depends:
, base >=4.7 && <5
, containers
, groups
, QuickCheck
, quickcheck-groups
, quickcheck-groups:internal
, quickcheck-instances

test-suite test
import:
common-extensions
, dependency-base
, dependency-hspec
, dependency-groups
, dependency-QuickCheck
, dependency-quickcheck-classes
, extensions
main-is:
Spec.hs
hs-source-dirs:
src/test
other-modules:
Test.QuickCheck.Classes.Hspec
Test.QuickCheck.Classes.GroupSpec
ClassSpec
Test.Hspec.Laws
type: exitcode-stdio-1.0
default-language:
Haskell2010
build-tool-depends:
hspec-discover:hspec-discover ==2.*
build-depends:
, base >=4.7 && <5
, bytestring
, containers
, groups
, hspec
, QuickCheck
, quickcheck-classes
, quickcheck-groups
, quickcheck-instances
, text
Original file line number Diff line number Diff line change
Expand Up @@ -2,45 +2,55 @@
-- Copyright: © 2022–2023 Jonathan Knowles
-- License: Apache-2.0
--
module Test.QuickCheck.Classes.Group.Internal
( makeLaw0
module Internal
( cover
, makeLaw0
, makeLaw1
, makeLaw2
, makeLaw3
, makeProperty
, report
, (==>)
)
where

import Data.Function
( (&) )
import Data.Proxy
( Proxy (..) )
import Data.Semigroup.Eq
( allUnique, canVerifyAllNonNull )
import Internal.Semigroup.Eq
( allNonNull, allUnique, allUniqueNonNull )
import Internal.Semigroup.Tuple
( Tuple1, Tuple2, Tuple3, evalTuple1, evalTuple2, evalTuple3 )
import Test.QuickCheck
( Arbitrary (..)
, Property
, Testable
, checkCoverage
, counterexample
, cover
, property
)
import Test.QuickCheck.Classes.Group.Tuple
( Tuple1, Tuple2, Tuple3, evalTuple1, evalTuple2, evalTuple3 )

import qualified Test.QuickCheck as QC

infixr 0 ==>
(==>) :: Bool -> Bool -> Bool
a ==> b = not a || b

cover :: Testable t => String -> Bool -> t -> Property
cover name = flip (QC.cover 0.1) (replaceSpecialChars <$> name)

makeLaw :: Testable t => String -> t -> (String, Property)
makeLaw title t = (title, checkCoverage $ property t)

makeLaw0
:: forall a. (Eq a, Monoid a)
=> String
:: String
-> (Proxy a -> Property)
-> (String, Property)
makeLaw0 s = makeLaw s . makeProperty0

makeLaw1
:: (Arbitrary a, Show a, Eq a, Monoid a, Testable t)
:: (Arbitrary a, Show a, Eq a, Semigroup a, Testable t)
=> String
-> (a -> t)
-> (String, Property)
Expand Down Expand Up @@ -70,9 +80,6 @@ makeProperty propertyDescription t =
& fmap replaceSpecialChars
]
where
replaceSpecialChars = \case
'λ' -> '\\'
other -> other

makeProperty0
:: forall a t. Testable t
Expand All @@ -81,42 +88,49 @@ makeProperty0
makeProperty0 p = property $ p $ Proxy @a

makeProperty1
:: (Eq a, Monoid a, Testable t)
:: (Eq a, Semigroup a, Testable t)
=> (a -> t)
-> (Tuple1 a -> Property)
makeProperty1 p (evalTuple1 -> a)
= cover 1 (a == mempty) "a == mempty"
$ cover 1 (a /= mempty) "a /= mempty"
$ property $ p a
= property $ p a

makeProperty2
:: (Eq a, Semigroup a, Testable t)
=> (a -> a -> t)
-> (Tuple2 a -> Property)
makeProperty2 p (evalTuple2 -> (a, b))
= cover 1
(allUnique [a, b])
= cover
"allUnique [a, b]"
$ cover 1
(canVerifyAllNonNull [a, b])
"canVerifyAllNonNull [a, b]"
$ cover 1
(allUnique [a, b] && canVerifyAllNonNull [a, b])
"allUnique [a, b] && canVerifyAllNonNull [a, b]"
(allUnique [a, b])
$ cover
"allNonNull [a, b]"
(allNonNull [a, b])
$ cover
"allUniqueNonNull [a, b]"
(allUniqueNonNull [a, b])
$ property $ p a b

makeProperty3
:: (Eq a, Semigroup a, Testable t)
=> (a -> a -> a -> t)
-> (Tuple3 a -> Property)
makeProperty3 p (evalTuple3 -> (a, b, c))
= cover 1
(allUnique [a, b, c])
= cover
"allUnique [a, b, c]"
$ cover 1
(canVerifyAllNonNull [a, b, c])
"canVerifyAllNonNull [a, b, c]"
$ cover 1
(allUnique [a, b, c] && canVerifyAllNonNull [a, b, c])
"allUnique [a, b, c] && canVerifyAllNonNull [a, b, c]"
(allUnique [a, b, c])
$ cover
"allNonNull [a, b, c]"
(allNonNull [a, b, c])
$ cover
"allUniqueNonNull [a, b, c]"
(allUniqueNonNull [a, b, c])
$ property $ p a b c

report :: (Show a, Testable prop) => String -> a -> prop -> Property
report name a = counterexample $
(replaceSpecialChars <$> name) <> ":\n" <> show a <> "\n"

replaceSpecialChars :: Char -> Char
replaceSpecialChars = \case
'λ' -> '\\'
other -> other
Loading

0 comments on commit 7c91aab

Please sign in to comment.