packages feed

moonlight-core-0.1.0.0: test/basis/RelationalSpec.hs

{-# LANGUAGE DerivingStrategies #-}

module RelationalSpec (tests) where

import Moonlight.Core (IsLawName (..), constructorLawName)
import Moonlight.Core (PartialOrder (..))
import Moonlight.Core
  ( atomIdKey,
    initialLiveEpoch,
    initialQuotientEpoch,
    liveEpochKey,
    mkAtomId,
    mkLiveEpoch,
    mkQueryId,
    mkQuotientEpoch,
    mkSlotId,
    nextLiveEpoch,
    nextQuotientEpoch,
    queryIdKey,
    quotientEpochKey,
    slotIdKey,
  )
import LawProperty (lawProperty)
import Test.Tasty (TestTree, testGroup)
import Test.Tasty.QuickCheck
  ( Arbitrary (..),
    Gen,
    Property,
    counterexample,
    elements,
    frequency,
    property,
    (===),
    (==>),
    (.&&.),
  )

newtype EpochKey = EpochKey Int
  deriving stock (Eq, Show)

instance Arbitrary EpochKey where
  arbitrary =
    EpochKey <$> epochKey

  shrink (EpochKey value) =
    EpochKey <$> shrink value

data RelationalLaw
  = RelationalQueryIdKeyRoundTrip
  | RelationalAtomIdKeyRoundTrip
  | RelationalSlotIdKeyRoundTrip
  | RelationalQuotientEpochKeyRoundTrip
  | RelationalLiveEpochKeyRoundTrip
  | RelationalQuotientEpochInitialKey
  | RelationalLiveEpochInitialKey
  | RelationalQuotientEpochNextKeySuccessor
  | RelationalLiveEpochNextKeySuccessor
  | RelationalQuotientEpochNextMaxBoundCeiling
  | RelationalLiveEpochNextMaxBoundCeiling
  | RelationalQuotientEpochNextMonotoneBelowCeiling
  | RelationalLiveEpochNextMonotoneBelowCeiling
  deriving stock (Bounded, Enum, Eq, Ord, Show)

instance IsLawName RelationalLaw where
  lawNameText =
    constructorLawName . show

tests :: TestTree
tests =
  testGroup
    "Relational"
    [ lawProperty RelationalQueryIdKeyRoundTrip propQueryIdKeyRoundTrip,
      lawProperty RelationalAtomIdKeyRoundTrip propAtomIdKeyRoundTrip,
      lawProperty RelationalSlotIdKeyRoundTrip propSlotIdKeyRoundTrip,
      lawProperty RelationalQuotientEpochKeyRoundTrip propQuotientEpochKeyRoundTrip,
      lawProperty RelationalLiveEpochKeyRoundTrip propLiveEpochKeyRoundTrip,
      lawProperty RelationalQuotientEpochInitialKey propQuotientEpochInitialKey,
      lawProperty RelationalLiveEpochInitialKey propLiveEpochInitialKey,
      lawProperty RelationalQuotientEpochNextKeySuccessor propQuotientEpochNextKeySuccessor,
      lawProperty RelationalLiveEpochNextKeySuccessor propLiveEpochNextKeySuccessor,
      lawProperty RelationalQuotientEpochNextMaxBoundCeiling propQuotientEpochNextMaxBoundCeiling,
      lawProperty RelationalLiveEpochNextMaxBoundCeiling propLiveEpochNextMaxBoundCeiling,
      lawProperty RelationalQuotientEpochNextMonotoneBelowCeiling propQuotientEpochNextMonotoneBelowCeiling,
      lawProperty RelationalLiveEpochNextMonotoneBelowCeiling propLiveEpochNextMonotoneBelowCeiling
    ]

epochKey :: Gen Int
epochKey =
  frequency
    [ (8, elements [maxBound]),
      (3, elements [minBound, -1, 0, 1, maxBound - 1]),
      (1, arbitrary)
    ]

propQueryIdKeyRoundTrip :: Int -> Property
propQueryIdKeyRoundTrip key =
  keyRoundTrip mkQueryId queryIdKey key

propAtomIdKeyRoundTrip :: Int -> Property
propAtomIdKeyRoundTrip key =
  keyRoundTrip mkAtomId atomIdKey key

propSlotIdKeyRoundTrip :: Int -> Property
propSlotIdKeyRoundTrip key =
  keyRoundTrip mkSlotId slotIdKey key

propQuotientEpochKeyRoundTrip :: EpochKey -> Property
propQuotientEpochKeyRoundTrip (EpochKey key) =
  keyRoundTrip mkQuotientEpoch quotientEpochKey key

propLiveEpochKeyRoundTrip :: EpochKey -> Property
propLiveEpochKeyRoundTrip (EpochKey key) =
  keyRoundTrip mkLiveEpoch liveEpochKey key

propQuotientEpochInitialKey :: Property
propQuotientEpochInitialKey =
  quotientEpochKey initialQuotientEpoch === 0

propLiveEpochInitialKey :: Property
propLiveEpochInitialKey =
  liveEpochKey initialLiveEpoch === 0

propQuotientEpochNextKeySuccessor :: EpochKey -> Property
propQuotientEpochNextKeySuccessor (EpochKey key) =
  quotientEpochKey (nextQuotientEpoch (mkQuotientEpoch key)) === successorEpochKey key

propLiveEpochNextKeySuccessor :: EpochKey -> Property
propLiveEpochNextKeySuccessor (EpochKey key) =
  liveEpochKey (nextLiveEpoch (mkLiveEpoch key)) === successorEpochKey key

propQuotientEpochNextMaxBoundCeiling :: Property
propQuotientEpochNextMaxBoundCeiling =
  quotientEpochKey (nextQuotientEpoch (mkQuotientEpoch maxBound)) === maxBound

propLiveEpochNextMaxBoundCeiling :: Property
propLiveEpochNextMaxBoundCeiling =
  liveEpochKey (nextLiveEpoch (mkLiveEpoch maxBound)) === maxBound

propQuotientEpochNextMonotoneBelowCeiling :: EpochKey -> Property
propQuotientEpochNextMonotoneBelowCeiling (EpochKey key) =
  key < maxBound ==> epochMonotone mkQuotientEpoch quotientEpochKey nextQuotientEpoch key

propLiveEpochNextMonotoneBelowCeiling :: EpochKey -> Property
propLiveEpochNextMonotoneBelowCeiling (EpochKey key) =
  key < maxBound ==> epochMonotone mkLiveEpoch liveEpochKey nextLiveEpoch key

keyRoundTrip :: (Eq identifier, Show identifier) => (Int -> identifier) -> (identifier -> Int) -> Int -> Property
keyRoundTrip construct project key =
  project identifier === key
    .&&. construct (project identifier) === identifier
  where
    identifier =
      construct key

successorEpochKey :: Int -> Int
successorEpochKey key
  | key < maxBound = key + 1
  | otherwise = maxBound

epochMonotone ::
  (PartialOrder epoch, Show epoch) =>
  (Int -> epoch) ->
  (epoch -> Int) ->
  (epoch -> epoch) ->
  Int ->
  Property
epochMonotone construct project advance key =
  counterexample monotonicityFailure $
    property (leq epochValue nextEpoch && project epochValue < project nextEpoch)
  where
    epochValue =
      construct key
    nextEpoch =
      advance epochValue
    monotonicityFailure =
      "epoch did not strictly increase from "
        <> show epochValue
        <> " to "
        <> show nextEpoch