packages feed

holmes-0.3.2.0: test/Test/Regression/Issue7.hs

{-# OPTIONS_GHC -Wno-missing-methods #-}

{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE ViewPatterns #-}
module Test.Regression.Issue7 where

import Data.Function ((&))
import Data.Hashable (Hashable)
import Data.Holmes
import GHC.Generics (Generic)
import Test.Tasty.Hspec (Spec, describe, it, shouldBe)

-------------------------------------------------------------------------------
-- #7: stack overflow / infinite loop when solving with Intersect instead of
-- Defined

countEqual
  :: ( Eq (f x)
     , Lifting f c
     , Mapping f c
     , c x, c v
     , Num v, Num (f v), SumR (f v)
     , MonadCell m
     )
  => [(Int, x)]
  -> [Prop m (f x)]
  -> Prop m (f v)
countEqual values cells = foldr (.+) (lift 0) (map f values)
  where
    f (index, expected) = cells !! index & over \actual ->
      if lift' expected == actual then 1 else 0

spec_17_defined :: Spec
spec_17_defined = describe "Issue #17" do
  it "Defined" do
    let checks :: [( Int, Int )]
        checks = [( 0, 3 )]

        threshold :: MonadCell m => Prop m (Defined Int)
        threshold = lift 0

    example <- (1 `from` [ 1 ]) `satisfying` \cells -> do
      countEqual checks cells .>= threshold

    example `shouldBe` Just [ 1 ]

  it "Intersect" do
    let checks :: [( Int, Val4 )]
        checks = [( 0, 3 )]

        threshold :: MonadCell m => Prop m (Intersect Val4)
        threshold = lift 0

    example <- (1 `from` [ 1 ]) `satisfying` \cells -> do
      countEqual checks cells .>= threshold

    example `shouldBe` Just [ 1 ]

newtype Val4 = V4 Int
  deriving stock (Show, Eq, Ord, Generic)
  deriving anyclass (Hashable)

instance Num Val4 where

  fromInteger = toEnum . fromInteger

  -- this is not a valid Num instance, we just want to use
  -- it for counting
  V4 a + V4 b = if a + b > 4 then V4 4 else V4 (a + b)
  V4 a - V4 b = if a - b < 0 then V4 0 else V4 (a - b)

instance Enum Val4 where
  toEnum n
    | n < 0 || n > 4 = error $ "toEnum Val4 out of bounds: " ++ show n
    | otherwise = V4 n

  fromEnum v@(V4 m)
    | m < 0 || m > 4 = error $ "fromEnum Val4 out of bounds: " ++ show v
    | otherwise = m

instance Bounded Val4 where
  minBound = toEnum 0
  maxBound = toEnum 4