1- {-# LANGUAGE DataKinds #-}
1+ {-# LANGUAGE DataKinds #-}
2+ {-# LANGUAGE ScopedTypeVariables #-}
3+ {-# LANGUAGE TypeApplications #-}
24-- The following warning is disabled due to a necessary instance of SatResult
35-- defined in this module.
46{-# OPTIONS_GHC -fno-warn-orphans #-}
57-- | Test copilot-theorem:Copilot.Theorem.What4.
68module Test.Copilot.Theorem.What4 where
79
810-- External imports
11+ import Control.Exception (Exception , try )
912import Data.Int (Int8 )
13+ import Data.Proxy (Proxy (.. ))
14+ import Data.Typeable (typeRep )
1015import Data.Word (Word32 )
1116import Test.Framework (Test , testGroup )
1217import Test.Framework.Providers.QuickCheck2 (testProperty )
13- import Test.HUnit (assertFailure )
18+ import Test.HUnit (Assertion , assertBool ,
19+ assertFailure )
1420import Test.QuickCheck (Arbitrary (arbitrary ), Property ,
1521 arbitrary , forAll )
1622import Test.QuickCheck.Monadic (monadicIO , run )
@@ -26,9 +32,9 @@ import Copilot.Core.Type (Field (..),
2632 Value (.. ))
2733
2834-- Internal imports: Modules being tested
29- import Copilot.Theorem.What4 (CounterExample (.. ), SatResult (.. ),
30- SatResultCex (.. ), Solver (.. ), prove ,
31- proveWithCounterExample )
35+ import Copilot.Theorem.What4 (CounterExample (.. ), ProveException (.. ),
36+ SatResult (.. ), SatResultCex (.. ), Solver ( .. ) ,
37+ prove , proveWithCounterExample )
3238
3339-- * Constants
3440
@@ -42,6 +48,7 @@ tests =
4248 , testProperty " Prove via Z3 that a struct update is valid" testProveZ3StructUpdate
4349 , testProperty " Counterexample with invalid base case" testCounterExampleBaseCase
4450 , testProperty " Counterexample with invalid induction step" testCounterExampleInductionStep
51+ , testProperty " Check that the What4 backend rejects existential quantification" testWhat4ExistsException
4552 ]
4653
4754-- * Individual tests
@@ -58,7 +65,7 @@ testProveZ3True =
5865 propName = " prop"
5966
6067 spec :: Spec
61- spec = propSpec propName [] $ Const typeOf True
68+ spec = forallPropSpec propName [] $ Const typeOf True
6269
6370-- | Test that Z3 is able to prove the following expression invalid:
6471-- @
@@ -72,7 +79,7 @@ testProveZ3False =
7279 propName = " prop"
7380
7481 spec :: Spec
75- spec = propSpec propName [] $ Const typeOf False
82+ spec = forallPropSpec propName [] $ Const typeOf False
7683
7784-- | Test that Z3 is able to prove the following expresion valid:
7885-- @
@@ -86,7 +93,7 @@ testProveZ3EqConst = forAll arbitrary $ \x ->
8693 propName = " prop"
8794
8895 spec :: Int8 -> Spec
89- spec x = propSpec propName [] $
96+ spec x = forallPropSpec propName [] $
9097 Op2 (Eq typeOf) (Const typeOf x) (Const typeOf x)
9198
9299-- | Test that Z3 is able to prove the following expresion valid:
@@ -102,7 +109,7 @@ testProveZ3StructUpdate = forAll arbitrary $ \x ->
102109 propName = " prop"
103110
104111 spec :: TestStruct -> Spec
105- spec s = propSpec propName [] $
112+ spec s = forallPropSpec propName [] $
106113 Op2
107114 (Eq typeOf)
108115 (getField
@@ -151,7 +158,7 @@ testCounterExampleBaseCase =
151158 sId = 0
152159
153160 spec :: Spec
154- spec = propSpec propName [s] $ Drop typeOf 0 sId
161+ spec = forallPropSpec propName [s] $ Drop typeOf 0 sId
155162
156163-- | Test that Z3 is able to produce a counterexample to the following property,
157164-- where the induction step is proved invalid:
@@ -183,7 +190,23 @@ testCounterExampleInductionStep =
183190 sId = 0
184191
185192 spec :: Spec
186- spec = propSpec propName [s] $ Drop typeOf 0 sId
193+ spec = forallPropSpec propName [s] $ Drop typeOf 0 sId
194+
195+ -- | Test that @copilot-theorem@'s @what4@ backend will throw an exception if it
196+ -- attempts to prove an existentially quantified proposition.
197+ testWhat4ExistsException :: Property
198+ testWhat4ExistsException =
199+ monadicIO $ run $
200+ checkException (prove Z3 spec) isUnexpectedExistentialProposition
201+ where
202+ isUnexpectedExistentialProposition :: ProveException -> Bool
203+ isUnexpectedExistentialProposition UnexpectedExistentialProposition = True
204+
205+ propName :: String
206+ propName = " prop"
207+
208+ spec :: Spec
209+ spec = existsPropSpec propName [] $ Const typeOf True
187210
188211-- | A simple data type with a 'Struct' instance and a 'Field'. This is only
189212-- used as part of 'testProveZ3StructUpdate'.
@@ -245,14 +268,42 @@ checkCounterExample solver propName spec cexPred = do
245268 UnknownCex {} ->
246269 assertFailure " Expected invalid result, but result was unknown"
247270
271+ -- | Check that the given 'IO' action throws a particular exception. This is
272+ -- largely taken from the implementation of @shouldThrow@ in
273+ -- @hspec-expectations@ (note that this test suite uses @test-framework@ instead
274+ -- of @hspec@).
275+ checkException :: forall e a . Exception e => IO a -> (e -> Bool ) -> Assertion
276+ checkException action p = do
277+ r <- try action
278+ case r of
279+ Right _ ->
280+ assertFailure $
281+ " did not get expected exception: " ++ exceptionType
282+ Left e ->
283+ assertBool
284+ (" predicate failed on expected exception: " ++ exceptionType ++
285+ " \n " ++ show e)
286+ (p e)
287+ where
288+ -- String representation of the expected exception's type
289+ exceptionType = show $ typeRep $ Proxy @ e
290+
248291-- * Auxiliary
249292
250293-- | Build a 'Spec' that contains one property with the given name, which
251- -- contains the given streams, and is defined by the given boolean expression.
252- propSpec :: String -> [Stream ] -> Expr Bool -> Spec
253- propSpec propName propStreams propExpr =
294+ -- contains the given streams, and is defined by the given boolean expression,
295+ -- which is universally quantified.
296+ forallPropSpec :: String -> [Stream ] -> Expr Bool -> Spec
297+ forallPropSpec propName propStreams propExpr =
254298 Spec propStreams [] [] [Copilot. Property propName (Copilot. Forall propExpr)]
255299
300+ -- | Build a 'Spec' that contains one property with the given name, which
301+ -- contains the given streams, and is defined by the given boolean expression,
302+ -- which is existentially quantified.
303+ existsPropSpec :: String -> [Stream ] -> Expr Bool -> Spec
304+ existsPropSpec propName propStreams propExpr =
305+ Spec propStreams [] [] [Copilot. Property propName (Copilot. Exists propExpr)]
306+
256307-- | Equality for 'SatResult'.
257308--
258309-- This is an orphan instance, so we suppress the warning that GHC would
0 commit comments