`agora-spec` and `agora-spec` is merged to be `agora-specs`; `agora-testlib` contains what previously was `Spec.Specification`.
226 lines
6.7 KiB
Haskell
226 lines
6.7 KiB
Haskell
{- |
|
|
Module : Spec.Utils
|
|
Maintainer : emi@haskell.fyi
|
|
Description: Tests for utility functions in 'Agora.Utils'.
|
|
|
|
Tests for utility functions in 'Agora.Utils'.
|
|
-}
|
|
module Spec.Utils (tests) where
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
import Agora.Utils (phalve, pisUniq, pmergeBy, pmsort, pnubSort, pupdate)
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
import Data.List (nub, sort)
|
|
import Data.Map qualified as M
|
|
import Data.Set qualified as S
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
import Control.Monad.Cont (cont, runCont)
|
|
import Test.Tasty (TestTree)
|
|
import Test.Tasty.QuickCheck (
|
|
Arbitrary (arbitrary),
|
|
Property,
|
|
Testable (property),
|
|
elements,
|
|
forAll,
|
|
suchThat,
|
|
testProperty,
|
|
(.&&.),
|
|
)
|
|
import Test.Util (updateMap)
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
import PlutusTx.AssocMap qualified as AssocMap
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
tests :: [TestTree]
|
|
tests =
|
|
[ testProperty "'pmsort' sorts a list properly" prop_msortCorrect
|
|
, testProperty "'pmerge' merges two sorted lists into one sorted list" prop_mergeCorrect
|
|
, testProperty "'phalve' splits a list in half as expected" prop_halveCorrect
|
|
, testProperty "'pnubSort' sorts a list and remove duplicate elements" prop_nubSortProperly
|
|
, testProperty "'pisUniq' can tell whether all elements in a list are unique" prop_uniqueList
|
|
, testProperty "'pupdate' updates assoc maps as 'updateMap' does" prop_updateAssocMapParity
|
|
]
|
|
|
|
--------------------------------------------------------------------------------
|
|
|
|
-- | Yield true if 'Agora.Utils.pmsort' sorts a given list correctly.
|
|
prop_msortCorrect :: [Integer] -> Bool
|
|
prop_msortCorrect l = sorted == expected
|
|
where
|
|
-- Expected sorted list, using 'Data.List.sort'.
|
|
expected :: [Integer]
|
|
expected = sort l
|
|
|
|
--
|
|
|
|
psorted :: Term _ (PBuiltinList PInteger)
|
|
psorted = pmsort # pconstant l
|
|
|
|
sorted :: [Integer]
|
|
sorted = plift psorted
|
|
|
|
-- | Yield true if 'Agora.Utils.pmerge' merges two list into a ordered list correctly.
|
|
prop_mergeCorrect :: [Integer] -> [Integer] -> Bool
|
|
prop_mergeCorrect a b = merged == expected
|
|
where
|
|
-- Sorted list a and b
|
|
sa = sort a
|
|
sb = sort b
|
|
|
|
-- Merge two lists which are assumed to be ordered.
|
|
merge :: [Integer] -> [Integer] -> [Integer]
|
|
merge xs [] = xs
|
|
merge [] ys = ys
|
|
merge sx@(x : xs) sy@(y : ys)
|
|
| x <= y = x : merge xs sy
|
|
| otherwise = y : merge sx ys
|
|
|
|
expected :: [Integer]
|
|
expected = merge sa sb
|
|
|
|
--
|
|
|
|
pmerged :: Term _ (PBuiltinList PInteger)
|
|
pmerged = pmergeBy # plam (#<) # pconstant sa # pconstant sb
|
|
|
|
merged :: [Integer]
|
|
merged = plift pmerged
|
|
|
|
{- | Yield true if Plutarch level 'Agora.Utils.phalve' splits a given list
|
|
as its Haskell level counterpart does.
|
|
-}
|
|
prop_halveCorrect :: [Integer] -> Bool
|
|
prop_halveCorrect l = halved == expected
|
|
where
|
|
-- Halve a list.
|
|
halve :: [Integer] -> ([Integer], [Integer])
|
|
halve xs = go xs xs
|
|
where
|
|
go xs [] = ([], xs)
|
|
go (x : xs) [_] = ([x], xs)
|
|
go (x : xs) (_ : _ : ys) =
|
|
let (first, last) =
|
|
go xs ys
|
|
in (x : first, last)
|
|
go [] _ = ([], [])
|
|
|
|
expected :: ([Integer], [Integer])
|
|
expected = halve l
|
|
|
|
--
|
|
|
|
phalved :: Term _ (PPair (PBuiltinList PInteger) (PBuiltinList PInteger))
|
|
phalved = phalve # pconstant l
|
|
|
|
halved :: ([Integer], [Integer])
|
|
halved =
|
|
let f = plift $ pmatch phalved $ \(PPair x _) -> x
|
|
s = plift $ pmatch phalved $ \(PPair _ x) -> x
|
|
in (f, s)
|
|
|
|
{- | Yield true if 'Agora.Utils.pnubSort' sorts and removes
|
|
duplicate elements from a given list.
|
|
-}
|
|
prop_nubSortProperly :: [Integer] -> Bool
|
|
prop_nubSortProperly l = nubbed == expected
|
|
where
|
|
-- Sort and list and then nub it.
|
|
expected :: [Integer]
|
|
expected = nub $ sort l
|
|
|
|
--
|
|
|
|
pnubbed :: Term _ (PBuiltinList PInteger)
|
|
pnubbed = pnubSort # pconstant l
|
|
|
|
nubbed :: [Integer]
|
|
nubbed = plift pnubbed
|
|
|
|
{- | Yield true if 'Agora.Utils.isUnique' can correctly determine
|
|
whether a given list only contains unique elements or not.
|
|
-}
|
|
prop_uniqueList :: [Integer] -> Bool
|
|
prop_uniqueList l = isUnique == expected
|
|
where
|
|
-- Convert input list to a set.
|
|
-- If the set's size equals to list's size,
|
|
-- the list only contains unique elements.
|
|
expected :: Bool
|
|
expected = S.size (S.fromList l) == length l
|
|
|
|
--
|
|
|
|
isUnique = plift $ pisUniq # pconstant l
|
|
|
|
{- | Test the parity between 'updateMap' and 'pupdate',
|
|
also ensure they both work correctly.
|
|
-}
|
|
prop_updateAssocMapParity :: Property
|
|
prop_updateAssocMapParity =
|
|
runCont
|
|
( do
|
|
-- Generate a bunch unique keys.
|
|
keys <-
|
|
cont $
|
|
forAll $
|
|
arbitrary @(S.Set Integer) `suchThat` (not . S.null)
|
|
|
|
-- Generate key-value pairs.
|
|
kvPairs <- cont $ forAll $ mapM (\k -> (k,) <$> (arbitrary @Integer)) $ S.toList keys
|
|
|
|
let initialMap = AssocMap.fromList kvPairs
|
|
|
|
pinitialMap :: Term _ _
|
|
pinitialMap = phoistAcyclic $ pconstant initialMap
|
|
|
|
referenceMap = M.fromList kvPairs
|
|
|
|
let pupdatedValue :: Maybe Integer -> Term _ (PMaybe PInteger)
|
|
pupdatedValue updatedValue = phoistAcyclic $ case updatedValue of
|
|
Nothing -> pcon PNothing
|
|
Just v -> pcon $ PJust $ pconstant v
|
|
|
|
-- Given the key and the updated value, test the parity
|
|
parity key updatedValue =
|
|
let native = updateMap (const updatedValue) key initialMap
|
|
|
|
plutarch :: AssocMap.Map Integer Integer
|
|
plutarch =
|
|
plift $
|
|
pupdate
|
|
# plam (\_ -> pupdatedValue updatedValue)
|
|
# pconstant key
|
|
# pinitialMap
|
|
|
|
expected =
|
|
AssocMap.fromList $
|
|
M.toList $
|
|
M.update (const updatedValue) key referenceMap
|
|
in expected == native
|
|
&& expected == plutarch
|
|
|
|
-- Select a key, generate a maybe value.
|
|
-- The value at the key should be set to the new value or removed.
|
|
(targetKey, _) <- cont $ forAll $ elements kvPairs
|
|
updatedValue <- cont $ forAll $ arbitrary @(Maybe Integer)
|
|
|
|
-- Now what if the key doesn't exist in our map?
|
|
nonexistentKey <-
|
|
cont $
|
|
forAll $
|
|
arbitrary @Integer `suchThat` (\k -> not $ S.member k keys)
|
|
|
|
pure
|
|
( property (parity targetKey updatedValue)
|
|
.&&. property (parity nonexistentKey updatedValue)
|
|
)
|
|
)
|
|
id
|