stgi-1: test/Testsuite/Test/Machine/Evaluate/Rules.hs
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NumDecimals #-}
{-# LANGUAGE OverloadedLists #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE ScopedTypeVariables #-}
-- | Tests for each of the STG rules. The scope should be as small as possible
-- around the actual test subject as possible. (To test return-after-case
-- you have to combine case evaluation and return handling, but you do not
-- have to introduce new bindings with @let@, for example.)
module Test.Machine.Evaluate.Rules (tests) where
import Stg.Language
import Stg.Machine
import Stg.Machine.Types
import Stg.Parser.QuasiQuoter (stg)
import Test.Machine.Evaluate.TestTemplates.MachineState hiding
(defSpec)
import qualified Test.Machine.Evaluate.TestTemplates.MachineState as MachineTest
import qualified Test.Machine.Evaluate.TestTemplates.MarshalledValue as MVal
import Test.Orphans ()
import Test.QuickCheck.Modifiers
import Test.Tasty
tests :: TestTree
tests = testGroup "Rules"
[ nonUpdatableFunctionApplication
, testGroup "Let (rule 3)"
[ testGroup "Non-recursive"
[ letBinding
, letMultiBinding
, letNestedBinding ]
, testGroup "Recursive"
[ letrecBinding
, letrecMultiBinding ]
]
, testGroup "Case evaluation (rule 4)"
[ testGroup "Default-only"
[ defaultOnlyCase_unboundAlgebraic
, defaultOnlyCase_boundAlgebraic
, defaultOnlyCase_unboundPrimitive
, defaultOnlyCase_boundPrimitive ]
, testGroup "Algebraic alternatives"
[ algebraicCase_normalMatch
, algebraicCase_defaultUnboundMatch
, algebraicCase_defaultBoundMatch ]
, testGroup "Primitive alternatives"
[ primitiveCase_normalMatch
, primitiveCase_defaultUnboundMatch
, primitiveCase_defaultBoundMatch ]
]
, constructorApplication
, literalEvaluation
, literalApplication
, primops
, enterUpdatableClosure
, algebraicReturnUpdate
, missingArgsUpdate
, testGroup "Primitive case evaluation shortcuts"
[ primopShortcut_defaultBound
, primopShortcut_normalMatch ]
]
defSpec :: MachineStateTestSpec
defSpec = MachineTest.defSpec
{ maxSteps = 32
, performGc = PerformGc (const Nothing) }
nonUpdatableFunctionApplication :: TestTree
nonUpdatableFunctionApplication = machineStateTest defSpec
{ testName = "Function application, enter non-updatable closure (rule 1 and 2)"
, source = [stg|
main = \ => case id unit of
Unit -> Success;
default -> TestFail;
id = \x -> x;
unit = \ -> Unit
|] }
letBinding :: TestTree
letBinding = machineStateTest defSpec
{ testName = "Single binding"
, source = [stg|
main = \ =>
let x = \ -> Success
in x
|] }
letMultiBinding :: TestTree
letMultiBinding = machineStateTest defSpec
{ testName = "Multiple bindings"
, source = [stg|
main = \ =>
let id = \x -> x;
one = \ -> Int# 1#
in case id one of
Int# y -> case y of
1# -> Success;
wrong -> TestFail wrong;
default -> Error
|] }
letNestedBinding :: TestTree
letNestedBinding = machineStateTest defSpec
{ testName = "Nested bindings"
, source = [stg|
main = \ =>
let id = \x -> x;
one = \ -> Int# 1#
in let idOne = \(id one) -> case id one of v -> v
in case idOne of
Int# y -> case y of
1# -> Success;
wrong -> TestFail wrong;
default -> Error
|] }
letrecBinding :: TestTree
letrecBinding = machineStateTest defSpec
{ testName = "Single binding"
, source = [stg| main = \ => letrec x = \ -> Success in x |] }
letrecMultiBinding :: TestTree
letrecMultiBinding = machineStateTest defSpec
{ testName = "Cross-referencing bindings"
, source = [stg|
main = \ =>
letrec id = \x -> x;
idOne = \(id one) -> case id one of
v -> v;
one = \ -> Int# 1#
in case idOne of
Int# y -> case y of
1# -> Success;
default -> TestFail;
default -> Error
|] }
defaultOnlyCase_unboundAlgebraic :: TestTree
defaultOnlyCase_unboundAlgebraic = machineStateTest defSpec
{ testName = "Unbound, algebraic scrutinee (rule 7)"
, source = [stg|
main = \ => case x of
default -> x;
x = \ -> Success
|] }
defaultOnlyCase_boundAlgebraic :: TestTree
defaultOnlyCase_boundAlgebraic = machineStateTest defSpec
{ testName = "Bound, algebraic scrutinee (rule 8)"
, source = [stg|
main = \ => case x of
x -> x;
x = \ -> Success
|] }
defaultOnlyCase_unboundPrimitive :: TestTree
defaultOnlyCase_unboundPrimitive = machineStateTest defSpec
{ testName = "Unbound, primitive scrutinee (rule 13)"
, source = [stg|
main = \ => case 1# of
default -> Success
|] }
defaultOnlyCase_boundPrimitive :: TestTree
defaultOnlyCase_boundPrimitive = machineStateTest defSpec
{ testName = "Bound, primitive scrutinee (rule 12)"
, source = [stg|
main = \ => case 1# of
x -> Success
|] }
algebraicCase_normalMatch :: TestTree
algebraicCase_normalMatch = machineStateTest defSpec
{ testName = "Algebraic, normal match (rule 6)"
, source = [stg|
main = \ => case Nothing of
Nothing -> Success;
default -> TestFail
|] }
algebraicCase_defaultUnboundMatch :: TestTree
algebraicCase_defaultUnboundMatch = machineStateTest defSpec
{ testName = "Algebraic, unbound default match (rule 7)"
, source = [stg|
main = \ => case Nothing of
Just x -> TestFail x;
default -> Success
|] }
algebraicCase_defaultBoundMatch :: TestTree
algebraicCase_defaultBoundMatch = machineStateTest defSpec
{ testName = "Algebraic, bound default match (rule 8)"
, source = [stg|
main = \ => case Nothing of
Just x -> TestFail;
v -> Success
|] }
primitiveCase_normalMatch :: TestTree
primitiveCase_normalMatch = machineStateTest defSpec
{ testName = "Primitive, normal match (rule 11)"
, source = [stg|
main = \ => case 1# of
1# -> Success;
default -> TestFail
|] }
primitiveCase_defaultUnboundMatch :: TestTree
primitiveCase_defaultUnboundMatch = machineStateTest defSpec
{ testName = "Primitive, unbound default match (rule 13)"
, source = [stg|
main = \ => case 1# of
0# -> TestFail;
123# -> TestFail;
default -> Success
|] }
primitiveCase_defaultBoundMatch :: TestTree
primitiveCase_defaultBoundMatch = machineStateTest defSpec
{ testName = "Primitive, unbound default match (rule 12)"
, source = [stg|
main = \ => case 1# of
0# -> TestFail;
123# -> TestFail;
-1# -> TestFail;
x -> Success
|] }
constructorApplication :: TestTree
constructorApplication = machineStateTest defSpec
{ testName = "Constructor application (rule 5)"
, source = [stg|
main = \ => case Just 1# of
Just v -> Success;
x -> TestFail x
|] }
literalEvaluation :: TestTree
literalEvaluation = machineStateTest defSpec
{ testName = "Literal evaluation (rule 9)"
, source = [stg|
main = \ => case 1# of
1# -> Success;
x -> TestFail x
|] }
literalApplication :: TestTree
literalApplication = machineStateTest defSpec
{ testName = "Literal application (rule 10)"
, source = [stg|
main = \ => case 1# of
v1 -> case v1 of
1# -> Success;
x -> TestFail x
|] }
primops :: TestTree
primops = MVal.marshalledValueTest MVal.MarshalledValueTestSpec
{ MVal.testName = "Primops"
, MVal.maxSteps = 1024
, MVal.failWithInfo = True
, MVal.failPredicate = const False
, MVal.sourceSpec = \(op, arg1 :: Integer, NonZero arg2) -> MVal.MarshalSourceSpec
-- arg2 is nonzero or the div/mod tests fail. Having their own tests
-- is probably not worth the code duplication, so we just throw out
-- the baby with the bathwater here.
{ MVal.resultVar = "main"
, MVal.expectedValue = haskell op arg1 arg2
, MVal.source = Program (Binds
[(Var "main", LambdaForm [] Update []
(Case (AppP op (AtomLit (Literal arg1)) (AtomLit (Literal arg2))) (Alts NoNonDefaultAlts
(DefaultBound (Var "x") (AppC (Constr "Int#") [AtomVar (Var "x")])) )))])}}
where
boolToPrim op x y = if op x y then 1 else 0
haskell = \case
Add -> (+)
Sub -> (-)
Mul -> (*)
Div -> div
Mod -> mod
Eq -> boolToPrim (==)
Lt -> boolToPrim (<)
Leq -> boolToPrim (<=)
Gt -> boolToPrim (>)
Geq -> boolToPrim (>=)
Neq -> boolToPrim (/=)
enterUpdatableClosure :: TestTree
enterUpdatableClosure = machineStateTest defSpec
{ testName = "Enter updatable closure (rule 15)"
, source = [stg|
main = \ => case Unit of
default -> Success
|]
, allSatisfied =
[ \state -> case stgInfo state of
Info (StateTransition Enter_UpdatableClosure) _ -> True
_otherwise -> False ]
}
algebraicReturnUpdate :: TestTree
algebraicReturnUpdate = machineStateTest defSpec
{ testName = "Update because of missing return frame (rule 16)"
, source = [stg|
main = \ => case updateMe of
default -> Success;
updateMe = \ => case Unit of default -> Unit
|]
, allSatisfied =
[ \state -> case stgInfo state of
Info (StateTransition ReturnCon_Update) _ -> True
_otherwise -> False ]
}
missingArgsUpdate :: TestTree
missingArgsUpdate = machineStateTest defSpec
{ testName = "Update because of missing argument frame (rule 17a)"
, source = [stg|
main = \ =>
case flipTuple 1# 2# of
Tuple a b -> case a of
2# -> case b of
1# -> Success;
bad -> TestFail bad;
bad -> TestFail bad;
badTuple -> Error_badTuple badTuple;
tuple = \x y -> Tuple x y;
flip = \f x y -> f y x;
flipTuple = \ => flip tuple
|]
, allSatisfied =
[ \state -> case stgInfo state of
Info (StateTransition Enter_PartiallyAppliedUpdate) _ -> True
_otherwise -> False ]
}
primopShortcut_defaultBound :: TestTree
primopShortcut_defaultBound = machineStateTest defSpec
{ testName = "Default bound match shortcut (rule 18)"
, source = [stg|
main = \ => case +# 1# 2# of
1# -> TestFail 1#;
2# -> TestFail 2#;
v -> Success
|]
, failPredicate = \state -> case stgCode state of
Eval AppP{} _ -> True -- The point of the shortcut is to never reach
-- the AppP rule itself.
_otherwise -> False
}
primopShortcut_normalMatch :: TestTree
primopShortcut_normalMatch = machineStateTest defSpec
{ testName = "Standard match shortcut (rule 19)"
, source = [stg|
main = \ => case 1# of
one -> case +# one 2# of
3# -> Success;
wrong -> TestFail wrong
|]
, failPredicate = \state -> case stgCode state of
Eval AppP{} _ -> True -- The point of the shortcut is to never reach
-- the AppP rule itself.
_otherwise -> False
}