packages feed

sdp-quickcheck-0.2: src/Test/SDP/Index.hs

{-# LANGUAGE TypeOperators, FlexibleContexts #-}

{- |
    Module      :  Test.SDP.Index
    Copyright   :  (c) Andrey Mulik 2019
    License     :  BSD-style
    Maintainer  :  work.a.mulik@gmail.com
    Portability :  non-portable (requires non-portable modules)
    
    @Test.SDP.Index@ provides basic test suite for 'Index' class.
-}
module Test.SDP.Index
(
  -- * Shape test
  TestShape, shapeTest,
  
  -- * Index test
  TestIndex, indexTest,
  
  -- ** Particular tests
  basicIndexTest, inBoundsTest, rangeTest, prevTest, nextTest, dumbSizeTest
)
where

import SDP.Index

default ()

--------------------------------------------------------------------------------

-- | 'TestShape' is service type synonym for more comfortable quickCheck using.
type TestShape s = s -> Bool

{- |
  @'shapeTest' r sh@ is default 'Shape' test, where @r@ is expected rank for
  this shape type. Note that 'shapeTest' also checks @'rank' 'undefined'@ case,
  to make sure 'rank' is correct.
-}
shapeTest :: (Shape s, Eq s, Eq (DimInit s), Eq (DimLast s)) => Int -> s -> Bool
shapeTest r sh' = let (s, sh) = unconsDim sh' in and
  [
    r == rank (undefined `asTypeOf` sh'),
    r == rank sh',
    
    consDim s sh == sh',
    lastDim sh' == sh,
    initDim sh' == s
  ]

--------------------------------------------------------------------------------

-- | TestIndex is service type synonym for more comfortable quickCheck using.
type TestIndex i = (i, i) -> i -> Bool

lim :: Int
lim =  65536

{- |
  'rangeTest' checks relations of 'inRange', 'isOverflow', 'isUnderflow' and
  'isEmpty'.
-}
rangeTest :: (Index i) => (i, i) -> i -> Bool
rangeTest bnds i = and
  [
    not (inRange bnds i && isUnderflow bnds i),
    not (inRange bnds i && isOverflow  bnds i),
    not (inRange bnds i && isEmpty     bnds),
    
    not (isEmpty bnds)  || isOverflow  bnds i,
    not (isEmpty bnds)  || isUnderflow bnds i
  ]

-- | 'prevTest' checks relations of 'prev' and 'range'.
prevTest :: (Index i) => (i, i) -> Bool
prevTest bnds =
  let test = take lim $ zipWith (==) (range bnds) (tail $ prev bnds <$> range bnds)
  in  isEmpty bnds || and test

-- | 'nextTest' checks relations of 'next' and 'range'.
nextTest :: (Index i) => (i, i) -> Bool
nextTest bnds =
  let test = take lim $ zipWith (==) (range bnds) (tail $ prev bnds <$> range bnds)
  in  isEmpty bnds || and test

-- | 'inBoundsTest' checks relations of 'inBounds' and other range functions.
inBoundsTest :: (Index i) => (i, i) -> i -> Bool
inBoundsTest bnds i = case inBounds bnds i of
  ER -> isEmpty     bnds
  IN -> inRange     bnds i
  OR -> isOverflow  bnds i
  UR -> isUnderflow bnds i

{- |
  'dumbSizeTest' is O(n) (may be very long) test, that checks relation of range
  'size' and 'range' length.
-}
dumbSizeTest :: (Index i) => (i, i) -> Bool
dumbSizeTest bnds = length (range bnds) == size bnds

-- | 'basicIndexTest' checks relations of 'rank', 'size' and 'sizes'.
basicIndexTest :: (Index i) => (i, i) -> i -> Bool
basicIndexTest bnds@(l, u) i = and
  [
    rank u == rank i,
    rank l == rank i,
    
    length  (sizes bnds) == rank i,
    product (sizes bnds) == size bnds
  ]

{- |
  'indexTest' is complex test, that includes all other tests.
  May crash with very big numbers (Word64, Integer) because the tested functions
  are limited by size of type Int.
  In practice, structures of such sizes would take more memory than the address
  space of computers can accommodate.
-}
indexTest :: (Index i) => (i, i) -> i -> Bool
indexTest bnds i = and
  [
    basicIndexTest bnds i,
    inBoundsTest   bnds i,
    rangeTest      bnds i,
    prevTest       bnds,
    nextTest       bnds
  ]