-
Notifications
You must be signed in to change notification settings - Fork 484
Kwxm/test/extra integer property tests #7134
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
3fedc1f
dda65fa
e21ee78
8eb83d3
1258a59
6ca0ed9
09f2df1
ff3d023
d7da44e
26cc11a
0007cec
44d991e
43418c6
9d3efb7
5cb109b
2cf2ee2
dbca1e9
5852366
df3f3e6
1007e59
93429af
1f980ad
5197e25
9744dae
5232cf4
343d638
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
{-# LANGUAGE AllowAmbiguousTypes #-} | ||
{-# LANGUAGE TypeApplications #-} | ||
{-# LANGUAGE TypeOperators #-} | ||
module Evaluation.Builtins.Integer.Common | ||
where | ||
|
||
import Prelude hiding (and, not, or) | ||
|
||
import PlutusCore qualified as PLC | ||
import PlutusCore.Generators.QuickCheck.Builtin (AsArbitraryBuiltin (..)) | ||
import PlutusCore.MkPlc (builtin, mkIterAppNoAnn, mkTyBuiltin, tyInst) | ||
|
||
import Evaluation.Builtins.Common | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. More utility functions for creating PLC terms. I'm probably reinventing the wheel yet again here. |
||
import Test.QuickCheck (Gen, arbitrary) | ||
|
||
-- For property tests, we don't want to use the standard QuickCheck generator | ||
-- for Integers because that only produces values in [-99..99]. Here are some | ||
-- utilities to make it easier to use the better generator for | ||
-- AsArbitraryBuiltin Integer. | ||
type BigInteger = AsArbitraryBuiltin Integer | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. They're not always big, but I couldn't think of a better name. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
biginteger :: BigInteger -> PlcTerm | ||
biginteger (AsArbitraryBuiltin n) = integer n | ||
|
||
arbitraryBigInteger :: Gen Integer | ||
arbitraryBigInteger = unAsArbitraryBuiltin <$> arbitrary | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe we can make it more generic. e.g. arbitraryVia :: (a -> r) -> Gen r
arbitraryVia getter = getter <$> arbitrary ? |
||
|
||
-- Functions creating terms that are applications of various builtins, for | ||
-- convenience. | ||
|
||
addInteger :: PlcTerm -> PlcTerm -> PlcTerm | ||
addInteger a b = mkIterAppNoAnn (builtin () PLC.AddInteger) [a, b] | ||
|
||
subtractInteger :: PlcTerm -> PlcTerm -> PlcTerm | ||
subtractInteger a b = mkIterAppNoAnn (builtin () PLC.SubtractInteger) [a, b] | ||
|
||
divideInteger :: PlcTerm -> PlcTerm -> PlcTerm | ||
divideInteger a b = mkIterAppNoAnn (builtin () PLC.DivideInteger) [a, b] | ||
|
||
modInteger :: PlcTerm -> PlcTerm -> PlcTerm | ||
modInteger a b = mkIterAppNoAnn (builtin () PLC.ModInteger) [a, b] | ||
|
||
multiplyInteger :: PlcTerm -> PlcTerm -> PlcTerm | ||
multiplyInteger a b = mkIterAppNoAnn (builtin () PLC.MultiplyInteger) [a, b] | ||
|
||
quotientInteger :: PlcTerm -> PlcTerm -> PlcTerm | ||
quotientInteger a b = mkIterAppNoAnn (builtin () PLC.QuotientInteger) [a, b] | ||
|
||
remainderInteger :: PlcTerm -> PlcTerm -> PlcTerm | ||
remainderInteger a b = mkIterAppNoAnn (builtin () PLC.RemainderInteger) [a, b] | ||
|
||
equalsInteger :: PlcTerm -> PlcTerm -> PlcTerm | ||
equalsInteger a b = mkIterAppNoAnn (builtin () PLC.EqualsInteger) [a, b] | ||
|
||
lessThanInteger :: PlcTerm -> PlcTerm -> PlcTerm | ||
lessThanInteger a b = mkIterAppNoAnn (builtin () PLC.LessThanInteger) [a, b] | ||
|
||
lessThanEqualsInteger :: PlcTerm -> PlcTerm -> PlcTerm | ||
lessThanEqualsInteger a b = mkIterAppNoAnn (builtin () PLC.LessThanEqualsInteger) [a, b] | ||
|
||
le0 :: PlcTerm -> PlcTerm | ||
le0 t = lessThanEqualsInteger t zero | ||
|
||
ge0 :: PlcTerm -> PlcTerm | ||
ge0 t = lessThanEqualsInteger zero t | ||
|
||
ite | ||
:: forall a | ||
. PLC.DefaultUni `PLC.HasTypeLevel` a | ||
=> PlcTerm -> PlcTerm -> PlcTerm -> PlcTerm | ||
ite b t f = | ||
let ty = mkTyBuiltin @_ @a () | ||
iteInst = tyInst () (builtin () PLC.IfThenElse) ty | ||
in mkIterAppNoAnn iteInst [b, t, f] | ||
|
||
-- Various logical combinations of boolean terms. | ||
|
||
abs :: PlcTerm -> PlcTerm | ||
abs t = ite @Integer (lessThanEqualsInteger zero t) t (subtractInteger zero t) | ||
|
||
not :: PlcTerm -> PlcTerm | ||
not t = ite @Bool t false true | ||
|
||
and :: PlcTerm -> PlcTerm -> PlcTerm | ||
and t1 t2 = ite @Bool t1 (ite @Bool t2 true false) false | ||
|
||
or :: PlcTerm -> PlcTerm -> PlcTerm | ||
or t1 t2 = ite @Bool t1 true (ite @Bool t2 true false) | ||
|
||
xor :: PlcTerm -> PlcTerm -> PlcTerm | ||
xor t1 t2 = ite @Bool t1 (ite @Bool t2 false true) t2 | ||
|
||
implies :: PlcTerm -> PlcTerm -> PlcTerm | ||
implies t1 t2 = (not t1) `or` t2 | ||
|
||
iff :: PlcTerm -> PlcTerm -> PlcTerm | ||
iff t1 t2 = (t1 `implies` t2) `and` (t2 `implies` t1) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
-- editorconfig-checker-disable | ||
{-# LANGUAGE OverloadedStrings #-} | ||
{-# LANGUAGE ViewPatterns #-} | ||
|
||
{- | Property tests for the `divideInteger` and `modInteger` builtins -} | ||
module Evaluation.Builtins.Integer.DivModProperties (test_integer_div_mod_properties) | ||
where | ||
|
||
import Evaluation.Builtins.Common | ||
import Evaluation.Builtins.Integer.Common | ||
|
||
import Test.Tasty (TestName, TestTree, testGroup) | ||
import Test.Tasty.QuickCheck | ||
|
||
numberOfTests :: Int | ||
numberOfTests = 200 | ||
|
||
testProp :: Testable prop => TestName -> prop -> TestTree | ||
testProp s p = testProperty s $ withMaxSuccess numberOfTests p | ||
|
||
-- `divideInteger _ 0` always fails. | ||
prop_div_0_fails :: BigInteger -> Property | ||
prop_div_0_fails (biginteger -> a) = | ||
fails $ divideInteger a zero | ||
|
||
-- `modInteger _ 0` always fails. | ||
prop_mod_0_fails :: BigInteger -> Property | ||
prop_mod_0_fails (biginteger -> a) = | ||
fails $ modInteger a zero | ||
|
||
-- b /= 0 => a = b * (a `div` b) + (a `mod` b) | ||
-- This is the crucial property relating `divideInteger` and `modInteger`. | ||
prop_div_mod_compatible :: BigInteger -> NonZero BigInteger -> Property | ||
prop_div_mod_compatible (biginteger -> a) (NonZero (biginteger -> b)) = | ||
let t = addInteger (multiplyInteger b (divideInteger a b) ) (modInteger a b) | ||
in evalOkEq t a | ||
|
||
-- (k*b) `div` b = b and (k*b) `mod` b = 0 for all k | ||
prop_div_mod_multiple :: BigInteger -> NonZero BigInteger -> Property | ||
prop_div_mod_multiple (biginteger -> k) (NonZero (biginteger -> b)) = | ||
let t1 = divideInteger (multiplyInteger k b) b | ||
t2 = modInteger (multiplyInteger k b) b | ||
in evalOkEq t1 k .&&. evalOkEq t2 zero | ||
|
||
-- For fixed b, `modInteger _ b` is an additive homomorphism: | ||
-- (a+a') `mod` b = ((a `mod` b) + (a' `mod` b)) `mod` b | ||
-- Together with prop_div_mod_multiple this means that `mod _ b` is | ||
-- periodic: (a+k*b) `mod` b = a mod b` for all k. | ||
prop_mod_additive :: BigInteger -> BigInteger -> NonZero BigInteger -> Property | ||
prop_mod_additive (biginteger -> a) (biginteger -> a') (NonZero (biginteger -> b)) = | ||
let t1 = modInteger (addInteger a a') b | ||
t2 = modInteger (addInteger (modInteger a b) (modInteger a' b)) b | ||
in evalOkEq t1 t2 | ||
|
||
-- For fixed b, `modInteger _ b` is a multiplicative homomorphism: | ||
-- (a*a') `mod` b = ((a `mod` b) * (a' `mod` b)) `mod` b | ||
prop_mod_multiplicative :: BigInteger -> BigInteger -> NonZero BigInteger -> Property | ||
prop_mod_multiplicative (biginteger -> a) (biginteger -> a') (NonZero (biginteger -> b)) = | ||
let t1 = modInteger (multiplyInteger a a') b | ||
t2 = modInteger (multiplyInteger (modInteger a b) (modInteger a' b)) b | ||
in evalOkEq t1 t2 | ||
|
||
-- For b > 0, 0 <= a `mod` b < b; | ||
prop_mod_size_pos :: BigInteger -> Positive BigInteger -> Property | ||
prop_mod_size_pos (biginteger -> a) (Positive (biginteger -> b)) = | ||
let t1 = lessThanEqualsInteger zero (modInteger a b) | ||
t2 = lessThanInteger (modInteger a b) b | ||
in evalOkTrue t1 .&&. evalOkTrue t2 | ||
|
||
-- For b < 0, b < a `mod` b <= 0 | ||
prop_mod_size_neg :: BigInteger -> Negative BigInteger -> Property | ||
prop_mod_size_neg (biginteger -> a) (Negative (biginteger -> b)) = | ||
let t1 = lessThanEqualsInteger (modInteger a b) zero | ||
t2 = lessThanInteger b (modInteger a b) | ||
in evalOkTrue t1 .&&. evalOkTrue t2 | ||
|
||
-- a >= 0 && b > 0 => a `div` b >= 0 and a `mod` b >= 0 | ||
-- a <= 0 && b > 0 => a `div` b <= 0 and a `mod` b >= 0 | ||
-- a >= 0 && b < 0 => a `div` b <= 0 and a `mod` b <= 0 | ||
-- a < 0 && b < 0 => a `div` b >= 0 and a `mod` b <= 0 | ||
|
||
prop_div_pos_pos :: (NonNegative BigInteger) -> (Positive BigInteger) -> Property | ||
prop_div_pos_pos (NonNegative (biginteger -> a)) (Positive (biginteger -> b)) = | ||
evalOkTrue $ ge0 (divideInteger a b) | ||
|
||
prop_div_neg_pos :: (NonPositive BigInteger) -> (Positive BigInteger) -> Property | ||
prop_div_neg_pos (NonPositive (biginteger -> a)) (Positive (biginteger -> b)) = | ||
evalOkTrue $ le0 (divideInteger a b) | ||
|
||
prop_div_pos_neg :: (NonNegative BigInteger) -> (Negative BigInteger) -> Property | ||
prop_div_pos_neg (NonNegative (biginteger -> a)) (Negative (biginteger -> b)) = | ||
evalOkTrue $ le0 (divideInteger a b) | ||
|
||
prop_div_neg_neg :: (NonPositive BigInteger) -> (Negative BigInteger) -> Property | ||
prop_div_neg_neg (NonPositive (biginteger -> a)) (Negative (biginteger -> b)) = | ||
evalOkTrue $ ge0 (divideInteger a b) | ||
|
||
prop_mod_pos_pos :: (NonNegative BigInteger) -> (Positive BigInteger) -> Property | ||
prop_mod_pos_pos (NonNegative (biginteger -> a)) (Positive (biginteger -> b)) = | ||
evalOkTrue $ ge0 (modInteger a b) | ||
|
||
prop_mod_neg_pos :: (NonPositive BigInteger) -> (Positive BigInteger) -> Property | ||
prop_mod_neg_pos (NonPositive (biginteger -> a)) (Positive (biginteger -> b)) = | ||
evalOkTrue $ ge0 (modInteger a b) | ||
|
||
prop_mod_pos_neg :: (NonNegative BigInteger) -> (Negative BigInteger) -> Property | ||
prop_mod_pos_neg (NonNegative (biginteger -> a)) (Negative (biginteger -> b)) = | ||
evalOkTrue $ le0 (modInteger a b) | ||
|
||
prop_mod_neg_neg :: (NonPositive BigInteger) -> (Negative BigInteger) -> Property | ||
prop_mod_neg_neg (NonPositive (biginteger -> a)) (Negative (biginteger -> b)) = | ||
evalOkTrue $ le0 (modInteger a b) | ||
|
||
test_integer_div_mod_properties :: TestTree | ||
test_integer_div_mod_properties = | ||
testGroup "Property tests for divideInteger and modInteger" | ||
[ testProp "divideInteger _ 0 always fails" prop_div_0_fails | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wasn't sure whether to put the test descriptions here or at the top of the tests themselves. This seems a bit more compact, but maybe putting the descriptions in the tests would have been better from a documentation point of view. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd say it's ok either way. |
||
, testProp "modInteger _ 0 always fails" prop_mod_0_fails | ||
, testProp "divideInteger and modInteger are compatible" prop_div_mod_compatible | ||
, testProp "divideInteger and modInteger behave sensibly on multiples of the divisor" prop_div_mod_multiple | ||
, testProp "mod is an additive homomorphism" prop_mod_additive | ||
, testProp "mod is a multiplicative homomorphism" prop_mod_multiplicative | ||
, testProp "modInteger size is correct for positive modulus" prop_mod_size_pos | ||
, testProp "modInteger size is correct for negative modulus" prop_mod_size_neg | ||
, testProp "divideInteger (>= 0) (> 0) >= 0" prop_div_pos_pos | ||
, testProp "divideInteger (<= 0) (> 0) <= 0" prop_div_neg_pos | ||
, testProp "divideInteger (>= 0) (< 0) <= 0" prop_div_pos_neg | ||
, testProp "divideInteger (<= 0) (< 0) >= 0" prop_div_neg_neg | ||
, testProp "modInteger (>= 0) (> 0) >= 0 " prop_mod_pos_pos | ||
, testProp "modInteger (>= 0) (< 0) >= 0" prop_mod_neg_pos | ||
, testProp "modInteger (<= 0) (> 0) <= 0" prop_mod_pos_neg | ||
, testProp "modInteger (<= 0) (< 0) <= 0" prop_mod_neg_neg | ||
] | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These are needed to make things like
Positive
andNonZero
work.