Skip to content

Commit a9ecb70

Browse files
committed
Added tests
1 parent 78ef7fd commit a9ecb70

File tree

8 files changed

+251
-106
lines changed

8 files changed

+251
-106
lines changed

plutus-tx/plutus-tx.cabal

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,7 @@ test-suite plutus-tx-test
211211
Blueprint.Spec
212212
List.Spec
213213
Bool.Spec
214+
Enum.Spec
214215
Rational.Laws
215216
Rational.Laws.Additive
216217
Rational.Laws.Construction
@@ -240,6 +241,7 @@ test-suite plutus-tx-test
240241
, plutus-core ^>=1.55
241242
, plutus-core:plutus-core-testlib
242243
, plutus-tx ^>=1.55
244+
, plutus-tx:plutus-tx-testlib
243245
, pretty-show
244246
, serialise
245247
, tasty

plutus-tx/src/PlutusTx/Enum/TH.hs

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
{-# LANGUAGE DerivingStrategies #-}
33
module PlutusTx.Enum.TH (Enum(..), deriveEnum) where
44

5+
import PlutusTx.ErrorCodes
56
import PlutusTx.Enum.Class
67
import PlutusTx.Trace
78
import Prelude hiding (Eq, (==), (&&), Bool (True), Enum (..))
@@ -62,7 +63,7 @@ deriveEnum name = do
6263

6364
toEnumDefaultClause :: Clause
6465
toEnumDefaultClause = TH.Clause [WildP] (TH.NormalB $
65-
AppE (VarE 'traceError) (LitE $ StringL "tag is outside of enumeration's range")
66+
AppE (VarE 'traceError) (VarE 'toEnumBadArgumentError)
6667
) []
6768

6869
deriveToEnum :: (Lit, Name) -> Q Clause
@@ -75,12 +76,10 @@ deriveSuccPred :: SuccPred -> (ConstructorInfo, Maybe ConstructorInfo) -> Q Clau
7576
deriveSuccPred succPred ( ConstructorInfo {constructorName = nameL, constructorFields = [] }
7677
, Nothing)
7778
= pure (TH.Clause [ConP nameL [] []]
78-
(NormalB $ AppE (VarE 'traceError) (LitE $ StringL $ show succPred
79-
++ "{" ++ show nameL ++ "}: tried to take "
80-
++ show succPred
81-
++ " of "
82-
++ case succPred of { Succ -> "last" ; Pred -> "first"}
83-
++ " tag in enumeration" ))
79+
(NormalB $ AppE (VarE 'traceError) (VarE $ case succPred of
80+
Succ -> 'succBadArgumentError
81+
Pred -> 'predBadArgumentError
82+
))
8483
[])
8584

8685
deriveSuccPred _ ( ConstructorInfo {constructorName = nameL, constructorFields = [] }

plutus-tx/src/PlutusTx/ErrorCodes.hs

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,17 @@ plutusPreludeErrorCodes =
4646
, ("PT7", "PlutusTx.List.!!: index too large")
4747
, ("PT8", "PlutusTx.List.head: empty list")
4848
, ("PT9", "PlutusTx.List.tail: empty list")
49+
, ("PT19", "PlutusTx.List.last: empty list")
50+
, ("PT20", "PlutusTx.Ratio.recip: reciprocal of zero")
51+
, ("PT21", "PlutusTx.BuiltinList.!!: negative index")
52+
, ("PT22", "PlutusTx.BuiltinList.!!: index too large")
53+
, ("PT23", "PlutusTx.BuiltinList.head: empty list")
54+
, ("PT24", "PlutusTx.BuiltinList.tail: empty list")
55+
, ("PT25", "PlutusTx.BuiltinList.last: empty list")
56+
, ("PT26", "PlutusTx.Enum.succ: bad argument")
57+
, ("PT27", "PlutusTx.Enum.pred: bad argument")
58+
, ("PT28", "PlutusTx.Enum.toEnum: bad argument")
59+
-- the following are retired
4960
, ("PT10", "PlutusTx.Enum.().succ: bad argument")
5061
, ("PT11", "PlutusTx.Enum.().pred: bad argument")
5162
, ("PT12", "PlutusTx.Enum.().toEnum: bad argument")
@@ -55,13 +66,6 @@ plutusPreludeErrorCodes =
5566
, ("PT16", "PlutusTx.Enum.Ordering.succ: bad argument")
5667
, ("PT17", "PlutusTx.Enum.Ordering.pred: bad argument")
5768
, ("PT18", "PlutusTx.Enum.Ordering.toEnum: bad argument")
58-
, ("PT19", "PlutusTx.List.last: empty list")
59-
, ("PT20", "PlutusTx.Ratio.recip: reciprocal of zero")
60-
, ("PT21", "PlutusTx.BuiltinList.!!: negative index")
61-
, ("PT22", "PlutusTx.BuiltinList.!!: index too large")
62-
, ("PT23", "PlutusTx.BuiltinList.head: empty list")
63-
, ("PT24", "PlutusTx.BuiltinList.tail: empty list")
64-
, ("PT25", "PlutusTx.BuiltinList.last: empty list")
6569
]
6670

6771
-- | The error happens in TH generation of indexed data
@@ -104,6 +108,23 @@ tailEmptyListError :: Builtins.BuiltinString
104108
tailEmptyListError = "PT9"
105109
{-# INLINEABLE tailEmptyListError #-}
106110

111+
-- | PlutusTx.Enum.().succ: bad argument
112+
succBadArgumentError :: Builtins.BuiltinString
113+
succBadArgumentError = "PT26"
114+
{-# INLINEABLE succBadArgumentError #-}
115+
116+
-- | PlutusTx.Enum.().pred: bad argument
117+
predBadArgumentError :: Builtins.BuiltinString
118+
predBadArgumentError = "PT27"
119+
{-# INLINEABLE predBadArgumentError #-}
120+
121+
-- | PlutusTx.Enum.().toEnum: bad argument
122+
toEnumBadArgumentError :: Builtins.BuiltinString
123+
toEnumBadArgumentError = "PT28"
124+
{-# INLINEABLE toEnumBadArgumentError #-}
125+
126+
{-# DEPRECATED succVoidBadArgumentError, predVoidBadArgumentError, toEnumVoidBadArgumentError, succBoolBadArgumentError, predBoolBadArgumentError, toEnumBoolBadArgumentError, succOrderingBadArgumentError, predOrderingBadArgumentError, toEnumOrderingBadArgumentError "Use [succ|pred|toEnum]BadArgumentError instead." #-}
127+
107128
-- | PlutusTx.Enum.().succ: bad argument
108129
succVoidBadArgumentError :: Builtins.BuiltinString
109130
succVoidBadArgumentError = "PT10"
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
instance PlutusTx.Enum.Class.Enum GHC.Types.Bool
2+
where {PlutusTx.Enum.Class.succ (GHC.Types.False) = GHC.Types.True
3+
PlutusTx.Enum.Class.succ (GHC.Types.True) = PlutusTx.Trace.traceError PlutusTx.ErrorCodes.succBadArgumentError;
4+
{-# INLINABLE PlutusTx.Enum.Class.succ #-};
5+
PlutusTx.Enum.Class.pred (GHC.Types.False) = PlutusTx.Trace.traceError PlutusTx.ErrorCodes.predBadArgumentError
6+
PlutusTx.Enum.Class.pred (GHC.Types.True) = GHC.Types.False;
7+
{-# INLINABLE PlutusTx.Enum.Class.pred #-};
8+
PlutusTx.Enum.Class.toEnum 0 = GHC.Types.False
9+
PlutusTx.Enum.Class.toEnum 1 = GHC.Types.True
10+
PlutusTx.Enum.Class.toEnum _ = PlutusTx.Trace.traceError PlutusTx.ErrorCodes.toEnumBadArgumentError;
11+
{-# INLINABLE PlutusTx.Enum.Class.toEnum #-};
12+
PlutusTx.Enum.Class.fromEnum (GHC.Types.False) = 0
13+
PlutusTx.Enum.Class.fromEnum (GHC.Types.True) = 1;
14+
{-# INLINABLE PlutusTx.Enum.Class.fromEnum #-}}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
instance PlutusTx.Enum.Class.Enum Enum.Spec.SomeVeryLargeEnum
2+
where {PlutusTx.Enum.Class.succ (Enum.Spec.E1) = Enum.Spec.E2
3+
PlutusTx.Enum.Class.succ (Enum.Spec.E2) = Enum.Spec.E3
4+
PlutusTx.Enum.Class.succ (Enum.Spec.E3) = Enum.Spec.E4
5+
PlutusTx.Enum.Class.succ (Enum.Spec.E4) = Enum.Spec.E5
6+
PlutusTx.Enum.Class.succ (Enum.Spec.E5) = Enum.Spec.E6
7+
PlutusTx.Enum.Class.succ (Enum.Spec.E6) = Enum.Spec.E7
8+
PlutusTx.Enum.Class.succ (Enum.Spec.E7) = Enum.Spec.E8
9+
PlutusTx.Enum.Class.succ (Enum.Spec.E8) = Enum.Spec.E9
10+
PlutusTx.Enum.Class.succ (Enum.Spec.E9) = Enum.Spec.E10
11+
PlutusTx.Enum.Class.succ (Enum.Spec.E10) = PlutusTx.Trace.traceError PlutusTx.ErrorCodes.succBadArgumentError;
12+
{-# INLINABLE PlutusTx.Enum.Class.succ #-};
13+
PlutusTx.Enum.Class.pred (Enum.Spec.E1) = PlutusTx.Trace.traceError PlutusTx.ErrorCodes.predBadArgumentError
14+
PlutusTx.Enum.Class.pred (Enum.Spec.E2) = Enum.Spec.E1
15+
PlutusTx.Enum.Class.pred (Enum.Spec.E3) = Enum.Spec.E2
16+
PlutusTx.Enum.Class.pred (Enum.Spec.E4) = Enum.Spec.E3
17+
PlutusTx.Enum.Class.pred (Enum.Spec.E5) = Enum.Spec.E4
18+
PlutusTx.Enum.Class.pred (Enum.Spec.E6) = Enum.Spec.E5
19+
PlutusTx.Enum.Class.pred (Enum.Spec.E7) = Enum.Spec.E6
20+
PlutusTx.Enum.Class.pred (Enum.Spec.E8) = Enum.Spec.E7
21+
PlutusTx.Enum.Class.pred (Enum.Spec.E9) = Enum.Spec.E8
22+
PlutusTx.Enum.Class.pred (Enum.Spec.E10) = Enum.Spec.E9;
23+
{-# INLINABLE PlutusTx.Enum.Class.pred #-};
24+
PlutusTx.Enum.Class.toEnum 0 = Enum.Spec.E1
25+
PlutusTx.Enum.Class.toEnum 1 = Enum.Spec.E2
26+
PlutusTx.Enum.Class.toEnum 2 = Enum.Spec.E3
27+
PlutusTx.Enum.Class.toEnum 3 = Enum.Spec.E4
28+
PlutusTx.Enum.Class.toEnum 4 = Enum.Spec.E5
29+
PlutusTx.Enum.Class.toEnum 5 = Enum.Spec.E6
30+
PlutusTx.Enum.Class.toEnum 6 = Enum.Spec.E7
31+
PlutusTx.Enum.Class.toEnum 7 = Enum.Spec.E8
32+
PlutusTx.Enum.Class.toEnum 8 = Enum.Spec.E9
33+
PlutusTx.Enum.Class.toEnum 9 = Enum.Spec.E10
34+
PlutusTx.Enum.Class.toEnum _ = PlutusTx.Trace.traceError PlutusTx.ErrorCodes.toEnumBadArgumentError;
35+
{-# INLINABLE PlutusTx.Enum.Class.toEnum #-};
36+
PlutusTx.Enum.Class.fromEnum (Enum.Spec.E1) = 0
37+
PlutusTx.Enum.Class.fromEnum (Enum.Spec.E2) = 1
38+
PlutusTx.Enum.Class.fromEnum (Enum.Spec.E3) = 2
39+
PlutusTx.Enum.Class.fromEnum (Enum.Spec.E4) = 3
40+
PlutusTx.Enum.Class.fromEnum (Enum.Spec.E5) = 4
41+
PlutusTx.Enum.Class.fromEnum (Enum.Spec.E6) = 5
42+
PlutusTx.Enum.Class.fromEnum (Enum.Spec.E7) = 6
43+
PlutusTx.Enum.Class.fromEnum (Enum.Spec.E8) = 7
44+
PlutusTx.Enum.Class.fromEnum (Enum.Spec.E9) = 8
45+
PlutusTx.Enum.Class.fromEnum (Enum.Spec.E10) = 9;
46+
{-# INLINABLE PlutusTx.Enum.Class.fromEnum #-}}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
instance PlutusTx.Enum.Class.Enum GHC.Tuple.Prim.()
2+
where {PlutusTx.Enum.Class.succ (GHC.Tuple.Prim.()) = PlutusTx.Trace.traceError PlutusTx.ErrorCodes.succBadArgumentError;
3+
{-# INLINABLE PlutusTx.Enum.Class.succ #-};
4+
PlutusTx.Enum.Class.pred (GHC.Tuple.Prim.()) = PlutusTx.Trace.traceError PlutusTx.ErrorCodes.predBadArgumentError;
5+
{-# INLINABLE PlutusTx.Enum.Class.pred #-};
6+
PlutusTx.Enum.Class.toEnum 0 = GHC.Tuple.Prim.()
7+
PlutusTx.Enum.Class.toEnum _ = PlutusTx.Trace.traceError PlutusTx.ErrorCodes.toEnumBadArgumentError;
8+
{-# INLINABLE PlutusTx.Enum.Class.toEnum #-};
9+
PlutusTx.Enum.Class.fromEnum (GHC.Tuple.Prim.()) = 0;
10+
{-# INLINABLE PlutusTx.Enum.Class.fromEnum #-}}

plutus-tx/test/Enum/Spec.hs

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
{-# LANGUAGE DerivingStrategies #-}
2+
{-# LANGUAGE OverloadedStrings #-}
3+
{-# LANGUAGE TemplateHaskell #-}
4+
{-# LANGUAGE TypeApplications #-}
5+
6+
module Enum.Spec (enumTests) where
7+
8+
import PlutusTx.Eq qualified as Tx
9+
import PlutusTx.Enum as Tx
10+
import PlutusTx.Test.Golden
11+
import Test.Tasty.Extras
12+
import Hedgehog
13+
import Hedgehog.Gen
14+
import Test.Tasty.Hedgehog
15+
import Test.Tasty
16+
import Test.Tasty.HUnit
17+
import Prelude hiding (Eq (..), error)
18+
import Prelude qualified as HS (Eq (..), Enum (..), Bounded (..), Show (..))
19+
import Control.Monad as HS (unless)
20+
import PlutusTx.List qualified as Tx
21+
import Data.List qualified as HS (intercalate, nub)
22+
23+
data SomeVeryLargeEnum
24+
= E1
25+
| E2
26+
| E3
27+
| E4
28+
| E5
29+
| E6
30+
| E7
31+
| E8
32+
| E9
33+
| E10
34+
deriving stock (HS.Eq, HS.Enum, HS.Bounded, HS.Show)
35+
deriveEnum ''SomeVeryLargeEnum
36+
37+
-- we lack Tx.Bounded so we use Haskell's for the tests
38+
enumTests :: TestTree
39+
enumTests =
40+
let
41+
in testGroup
42+
"PlutusTx.Enum tests"
43+
[ testProperty "no dups" prop_nodups
44+
, testCase "full length" $ Tx.length (Tx.enumFromTo @SomeVeryLargeEnum HS.minBound HS.maxBound) @?= Tx.fromEnum @SomeVeryLargeEnum HS.maxBound + 1
45+
, runTestNested
46+
["test", "Enum", "Golden"]
47+
[ $(goldenCodeGen "SomeVeryLargeEnum" (deriveEnum ''SomeVeryLargeEnum))
48+
, $(goldenCodeGen "Bool" (deriveEnum ''Bool))
49+
, $(goldenCodeGen "Unit" (deriveEnum ''()))
50+
]
51+
, enumFromToTests
52+
, enumFromThenToTests
53+
]
54+
55+
-- we lack Tx.Bounded so we use Haskell's for the tests
56+
prop_nodups :: Property
57+
prop_nodups = property $ do
58+
from <- forAll enumBounded
59+
to <- forAll enumBounded
60+
let res = Tx.enumFromTo @SomeVeryLargeEnum from to
61+
HS.nub res === res
62+
63+
enumFromToTests :: TestTree
64+
enumFromToTests =
65+
testGroup
66+
"enumFromTo"
67+
[ testCase "enumFromTo (-2) 2 == [-2..2]" $ Tx.enumFromTo @Integer (-2) 2 @?= [-2 .. 2]
68+
, testCase "enumFromTo 2 (-2) == []" $ Tx.enumFromTo @Integer 2 (-2) @?= []
69+
, testCase "enumFromTo 42 42 == [42]" $ Tx.enumFromTo @Integer 42 42 @?= [42]
70+
]
71+
72+
enumFromThenToTests :: TestTree
73+
enumFromThenToTests =
74+
testGroup
75+
"enumFromThenTo"
76+
[ testCase "enumFromThenTo 1 2 100 == [1..100]" $
77+
Tx.enumFromThenTo @Integer 1 2 100 @?=* [1 .. 100]
78+
, testCase "enumFromThenTo 1 2 100 == [1,2..100]" $
79+
Tx.enumFromThenTo @Integer 1 2 100 @?=* [1, 2 .. 100]
80+
, testCase "enumFromThenTo 100 99 1 == [100,99..1]" $
81+
Tx.enumFromThenTo @Integer 100 99 1 @?=* [100, 99 .. 1]
82+
, testCase "enumFromThenTo 100 17 (-700) == [100,17..(-700)]" $
83+
Tx.enumFromThenTo @Integer 100 17 (-700) @?=* [100, 17 .. (-700)]
84+
, testCase "enumFromThenTo 0 5 99 == [0,5..99]" $
85+
Tx.enumFromThenTo @Integer 0 5 99 @?=* [0, 5 .. 99]
86+
, testCase "enumFromThenTo 0 5 100 == [0,5..100]" $
87+
Tx.enumFromThenTo @Integer 0 5 100 @?=* [0, 5 .. 100]
88+
, testCase "enumFromThenTo 0 5 101 == [0,5..101]" $
89+
Tx.enumFromThenTo @Integer 0 5 101 @?=* [0, 5 .. 101]
90+
, testCase "enumFromThenTo 100 95 0 == [100,95..0]" $
91+
Tx.enumFromThenTo @Integer 100 95 0 @?=* [100, 95 .. 0]
92+
, testCase "enumFromThenTo 100 95 (-9) == [100,95..(-9)]" $
93+
Tx.enumFromThenTo @Integer 100 95 (-9) @?=* [100, 95 .. (-9)]
94+
, testCase "enumFromThenTo 100 95 (-10) == [100,95..(-10)]" $
95+
Tx.enumFromThenTo @Integer 100 95 (-10) @?=* [100, 95 .. (-10)]
96+
, testCase "enumFromThenTo 100 95 (-11) == [100,95..(-11)]" $
97+
Tx.enumFromThenTo @Integer 100 95 (-11) @?=* [100, 95 .. (-11)]
98+
, testCase "enumFromThenTo 42 42 41 == []" $
99+
Tx.enumFromThenTo @Integer 42 42 41 @?=* []
100+
, testCase "enumFromThenTo 42 42 42 == [42*]" $
101+
Tx.enumFromThenTo @Integer 42 42 42 @?=* [42, 42 .. 42]
102+
, testCase "enumFromThenTo 42 42 43 == [42*]" $
103+
Tx.enumFromThenTo @Integer 42 42 43 @?=* [42, 42 .. 43]
104+
, testCase "enumFromThenTo False False False == [False*]" $
105+
Tx.enumFromThenTo False False False @?=* [False, False .. False]
106+
, testCase "enumFromThenTo False False True == [False*]" $
107+
Tx.enumFromThenTo False False True @?=* [False, False .. True]
108+
, testCase "enumFromThenTo False True False == [False]" $
109+
Tx.enumFromThenTo False True False @?=* [False, True .. False]
110+
, testCase "enumFromThenTo False True True == [False,True]" $
111+
Tx.enumFromThenTo False True True @?=* [False, True .. True]
112+
, testCase "enumFromThenTo True False False == [True,False]" $
113+
Tx.enumFromThenTo True False False @?=* [True, False .. False]
114+
, testCase "enumFromThenTo True False True == [True]" $
115+
Tx.enumFromThenTo True False True @?=* [True, False .. True]
116+
, testCase "enumFromThenTo True True False == []" $
117+
Tx.enumFromThenTo True True False @?=* [True, True .. False]
118+
, testCase "enumFromThenTo True True True == [True*]" $
119+
Tx.enumFromThenTo True True True @?=* [True, True .. True]
120+
, testCase "enumFromThenTo () () () == [()*]" $
121+
Tx.enumFromThenTo () () () @?=* [(), () .. ()]
122+
]
123+
where
124+
{- Check (approximately) that two possibly infinite lists are equal. We can get infinite lists from
125+
`enumFromThenTo`, both legitimately and because of implementation errors (which are exactly
126+
what we're testing for here). If we just use @?= then (a) it won't terminate if we give it
127+
two equal infinite lists, and (b) if it fails and one of the lists is infinite then it'll try
128+
to generate an infinite error message, again leading to non-termination. To deal with this,
129+
if an argument has more than 1000 elements then we assume it's infinite and just include an
130+
initial segment in any error message, and when we're comparing two such "infinite" lists we
131+
just compare the first 1000 elements. The only infinite lists that enumFromThenTo can
132+
generate are of the form [x,x,x,...], so this is definitely a safe strategy in this context.
133+
-}
134+
l1 @?=* l2 =
135+
case (possiblyInfinite l1, possiblyInfinite l2) of
136+
(False, False) -> l1 @?= l2
137+
(True, False) -> failWith (showInit l1) (show l2)
138+
(False, True) -> failWith (show l1) (showInit l2)
139+
(True, True) -> HS.unless (take 1000 l1 Tx.== take 1000 l2) (failWith (showInit l1) (showInit l2))
140+
where
141+
possiblyInfinite l = Tx.drop 1000 l Tx./= []
142+
showInit l = "[" ++ HS.intercalate "," (fmap show (take 5 l)) ++ ",...]"
143+
failWith expected actual = assertFailure ("expected: " ++ expected ++ "\n but got: " ++ actual)

0 commit comments

Comments
 (0)