| Safe Haskell | None |
|---|---|
| Language | GHC2024 |
Proarrow.Testing
Description
Generic property-testing infrastructure for categories: Testable says how to generate and
enumerate the objects of a kind, TestableProfunctor and TestableType how to generate values
(using falsify generators), and TestingEqShow provides semantic equality and display for
values without useful structural Eq/Show (functions, opaque morphisms). Instances for your
own category plus the law checks in Proarrow.Testing.Laws give it a test suite.
Synopsis
- data GenTotal a where
- GenEmpty :: forall a. ~(forall x. a -> x) -> GenTotal a
- GenNENonFun :: forall a. Gen a -> GenTotal a
- GenFun :: forall a1 b a. (TestableType a1, TestableType b) => ((a1 -> b) -> a) -> Gen (Fun (ShowP a1) (ShowP b)) -> GenTotal a
- invmap :: (a -> b) -> (b -> a) -> GenTotal a -> GenTotal b
- flatten :: GenTotal a -> Gen a
- pattern GenNonEmpty :: Gen a -> GenTotal a
- oneOfTotal :: [GenTotal a] -> GenTotal a
- class TestingEqShow a where
- class TestingEqShow a => TestableType a where
- newtype ShowP a = ShowP {
- unShowP :: a
- applyFunP :: Fun (ShowP a) (ShowP b) -> a -> b
- genP :: TestableType a => Property a
- genNamed :: TestableType a => String -> Property a
- genWithNamed :: String -> (a -> Maybe String) -> Gen a -> Property a
- isGenNonEmpty :: TestableType a => Bool
- genSuchThat :: Gen key -> (key -> Bool) -> Gen key
- maxTries :: Int
- genObSuchThat :: Testable k => (Some k -> Bool) -> Property (Some k)
- data SomeProfunctorElt (p :: j +-> k) where
- SomeP :: forall {k} {j} (a :: k) (b :: j) (p :: j +-> k). (TestOb a, TestOb b) => p a b -> SomeProfunctorElt p
- someP :: forall {k} {j} p (a :: j) (b :: k). (Profunctor p, TestObIsOb j, TestObIsOb k) => p a b -> SomeProfunctorElt p
- class (forall (a :: k) (b :: j). (TestOb a, TestOb b) => TestableType (p a b)) => TestableTypeP (p :: j +-> k)
- class (Testable j, Testable k, Profunctor p, forall (a :: k) (b :: j). (TestOb a, TestOb b) => TestingEqShow (p a b)) => TestableProfunctor (p :: j +-> k) where
- genProfunctorElt :: String -> Property (SomeProfunctorElt p)
- class (forall (a :: k). TestOb a => Ob' a, TestableProfunctor (Hom k), TestableTypeP (Hom k), CategoryOf k) => Testable k where
- genOb :: Testable k => Property (Some k)
- class TestOb a => TestOb' (a :: k)
- class (forall (a :: k). Ob a => TestOb' a) => TestObIsOb k
- obFromTestOb :: forall {k} (a :: k) r. (Testable k, TestOb a) => (Ob a => r) -> r
- data Some k where
- mapSome :: forall (f :: j -> k) -> (forall (a :: j). TestOb a => TestOb' (f a)) => Some j -> Some k
- class MkSomeList (as :: [k]) where
- mkSomeList :: [Some k]
- someElem :: Show a => [a] -> Property a
- someElemNamed :: Show a => String -> [a] -> Property a
- someElemWith :: (a -> String) -> [a] -> Property a
- genSomeDef :: forall {k} (obs :: [k]). (Testable k, MkSomeList obs) => Gen (Some k)
- optGen :: [a] -> GenTotal a
- oneElem :: a -> GenTotal a
- eqHask :: (TestableType a, TestingEqShow b) => (a -> b) -> (a -> b) -> Property Bool
- sampleT :: TestableType t => IO (Maybe String)
- sampleP :: forall {j} {k} (p :: j +-> k). (Testable j, Testable k, TestableProfunctor p) => IO (Maybe String)
- sampleK :: Testable k => IO (Maybe String)
Documentation
data GenTotal a where Source Github #
Constructors
| GenEmpty :: forall a. ~(forall x. a -> x) -> GenTotal a | |
| GenNENonFun :: forall a. Gen a -> GenTotal a | |
| GenFun :: forall a1 b a. (TestableType a1, TestableType b) => ((a1 -> b) -> a) -> Gen (Fun (ShowP a1) (ShowP b)) -> GenTotal a |
oneOfTotal :: [GenTotal a] -> GenTotal a Source Github #
Uniformly choose among any number of alternatives, dropping the empty ones. Plain <|>
only combines two generators at 50/50, so chaining it over more than two alternatives
associates pairwise and skews weight towards whichever branch ends up outermost in the
resulting tree instead of splitting evenly — use this instead whenever there are more than
two alternatives to pick fairly among.
class TestingEqShow a where Source Github #
Minimal complete definition
Nothing
Instances
| TestingEqShow (Unit a b) Source Github # | |
| (TestableType a, TestingEqShow b) => TestingEqShow (a -> b) Source Github # | |
| TestingEqShow (p b a) => TestingEqShow (Op p ('OP a) ('OP b)) Source Github # | |
| (TestingEqShow (a ~> (f @ b)), Ob b) => TestingEqShow (Rep f a b) Source Github # | |
| (TestingEqShow (catk a1 b1), TestingEqShow (catj a2 b2)) => TestingEqShow ((catk :**: catj) '(a1, a2) '(b1, b2)) Source Github # | |
class TestingEqShow a => TestableType a where Source Github #
Instances
| (Ob a, Ob b) => TestableType (Unit a b) Source Github # | |
| (Function a, TestableType a, TestableType b) => TestableType (a -> b) Source Github # | |
| TestableType (p b a) => TestableType (Op p ('OP a) ('OP b)) Source Github # | |
| (TestableType (a ~> (f @ b)), Ob b) => TestableType (Rep f a b) Source Github # | |
| (TestableType (catk a1 b1), TestableType (catj a2 b2)) => TestableType ((catk :**: catj) '(a1, a2) '(b1, b2)) Source Github # | |
newtype ShowP a Source Github #
Supplies a Show instance derived from showP.
falsify's Show instance for Fun needs Show on both parameters, and we
only ever have TestingEqShow. Rather than reinterpret a at the
wrapped type after the fact, Fun a bGenFun generates at
from the start, so Fun (ShowP a) (ShowP b)show applies directly and
no coercion is involved. applyFunP unwraps on the way back out.
applyFunP :: Fun (ShowP a) (ShowP b) -> a -> b Source Github #
Apply a generated function, wrapping and unwrapping the ShowP it was
generated at. Both directions are ordinary newtype constructor applications.
isGenNonEmpty :: TestableType a => Bool Source Github #
True if a type's generator is non-empty. A pure check on TestableTypes gen — it
doesn't sample anything, so it's safe (and cheap) to call as many times as convenient, e.g.
once in a genSuchThat predicate and again via the real gen/genNamed call that
actually produces a value.
genSuchThat :: Gen key -> (key -> Bool) -> Gen key Source Github #
Resample genKey (cheaply, within Gen) up to maxTries times until isUsable accepts
the draw, before ever asking Property to commit to a choice.
Property-level discard restarts the *whole* property from scratch (and can trip
falsify's per-slot discard-ratio limit, aborting the entire test run early) — so when a
later, dependent draw (e.g. "a morphism out of this object") is likely to be empty for a
"bad" choice made here, it's far cheaper to reject that choice immediately, inside Gen,
than to commit to it via Property and let the dependent draw discover the problem. A
choice that's still unusable after maxTries attempts is returned anyway, so a genuinely
unsatisfiable requirement still falls through to whatever ordinary discard the caller's
own dependent generation triggers.
maxTries :: Int Source Github #
How many times genSuchThat resamples before giving up. There's no principled formula
for this — it depends on how sparse the specific requirement being searched for is, which
genSuchThat has no way to know in advance. 100 is a pragmatic default: comfortably more
than the number of candidates a small test object palette usually offers (so a single
unlucky pick is very unlikely to exhaust it), while still cheap, since each attempt is a
plain Gen sample rather than a Property-level discard.
genObSuchThat :: Testable k => (Some k -> Bool) -> Property (Some k) Source Github #
genOb, but resampled (see genSuchThat) until isUsable accepts the object.
data SomeProfunctorElt (p :: j +-> k) where Source Github #
Constructors
| SomeP :: forall {k} {j} (a :: k) (b :: j) (p :: j +-> k). (TestOb a, TestOb b) => p a b -> SomeProfunctorElt p |
Instances
| (forall (a :: k) (b :: j). (TestOb a, TestOb b) => TestingEqShow (p a b), Testable k, Testable j) => Show (SomeProfunctorElt p) Source Github # | |
Defined in Proarrow.Testing | |
someP :: forall {k} {j} p (a :: j) (b :: k). (Profunctor p, TestObIsOb j, TestObIsOb k) => p a b -> SomeProfunctorElt p Source Github #
class (forall (a :: k) (b :: j). (TestOb a, TestOb b) => TestableType (p a b)) => TestableTypeP (p :: j +-> k) Source Github #
Instances
| (forall (a :: k) (b :: j). (TestOb a, TestOb b) => TestableType (p a b)) => TestableTypeP (p :: j +-> k) Source Github # | |
Defined in Proarrow.Testing | |
class (Testable j, Testable k, Profunctor p, forall (a :: k) (b :: j). (TestOb a, TestOb b) => TestingEqShow (p a b)) => TestableProfunctor (p :: j +-> k) where Source Github #
Minimal complete definition
Nothing
Methods
genProfunctorElt :: String -> Property (SomeProfunctorElt p) Source Github #
The default implementation generates types a and b and then generates a value of type p a b.
But that can cause too many discarded tests.
default genProfunctorElt :: forall. TestableTypeP p => String -> Property (SomeProfunctorElt p) Source Github #
Instances
| TestableProfunctor Unit Source Github # | |
Defined in Proarrow.Testing Methods genProfunctorElt :: String -> Property (SomeProfunctorElt Unit) Source Github # | |
| TestableProfunctor p => TestableProfunctor (Op p :: OPPOSITE j -> OPPOSITE k -> Type) Source Github # | |
Defined in Proarrow.Testing Methods genProfunctorElt :: String -> Property (SomeProfunctorElt (Op p)) Source Github # | |
| (TestableProfunctor p, TestableProfunctor q) => TestableProfunctor (p :**: q :: (k1, k2) -> (j1, j2) -> Type) Source Github # | |
Defined in Proarrow.Testing Methods genProfunctorElt :: String -> Property (SomeProfunctorElt (p :**: q)) Source Github # | |
class (forall (a :: k). TestOb a => Ob' a, TestableProfunctor (Hom k), TestableTypeP (Hom k), CategoryOf k) => Testable k where Source Github #
Methods
showOb :: forall (a :: k). TestOb a => String Source Github #
eqOb :: forall (a :: k) (b :: k). (TestOb a, TestOb b) => Maybe (a :~: b) Source Github #
class (forall (a :: k). Ob a => TestOb' a) => TestObIsOb k Source Github #
Instances
| (forall (a :: k). Ob a => TestOb' a) => TestObIsOb k Source Github # | |
Defined in Proarrow.Testing | |
obFromTestOb :: forall {k} (a :: k) r. (Testable k, TestOb a) => (Ob a => r) -> r Source Github #
Recover from Ob a (the TestOb aTestable superclass entailment), packaged as a
function so that call sites with other quantified givens in scope (e.g. the comonoid supply of a
CopyDiscard category, whose head has Ob as a
superclass) don't have to rely on GHC expanding superclasses of quantified-constraint heads.
Observed on GHC 9.10.3: with such a given in scope, \r -> r at this type fails with "Could not
deduce Ob a", while the same lambda compiles without it (cf. propSymMonoidal_
versus propCopyDiscard_). Likely a solver limitation; retry dropping this
helper after a GHC upgrade.
mapSome :: forall (f :: j -> k) -> (forall (a :: j). TestOb a => TestOb' (f a)) => Some j -> Some k Source Github #
class MkSomeList (as :: [k]) where Source Github #
Methods
mkSomeList :: [Some k] Source Github #
Instances
| MkSomeList ('[] :: [k]) Source Github # | |
Defined in Proarrow.Testing Methods mkSomeList :: [Some k] Source Github # | |
| (TestOb a, MkSomeList as) => MkSomeList (a ': as :: [k]) Source Github # | |
Defined in Proarrow.Testing Methods mkSomeList :: [Some k] Source Github # | |
genSomeDef :: forall {k} (obs :: [k]). (Testable k, MkSomeList obs) => Gen (Some k) Source Github #
eqHask :: (TestableType a, TestingEqShow b) => (a -> b) -> (a -> b) -> Property Bool Source Github #