packages feed

stgi-1: test/Testsuite/Test/Machine/Evaluate/TestTemplates/MachineState.hs

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes       #-}

-- | Functions and types required to implement tests that check whether
-- a certain closure is reduced to the expected form by the STG.
module Test.Machine.Evaluate.TestTemplates.MachineState (
    MachineStateTestSpec(..),
    defSpec,
    machineStateTest,
) where



import qualified Data.List                    as L
import qualified Data.List.NonEmpty           as NE
import           Data.Monoid
import           Data.Text                    (Text)
import qualified Data.Text                    as T
import           Text.PrettyPrint.ANSI.Leijen hiding ((<>))

import Stg.Language
import Stg.Language.Prettyprint
import Stg.Machine
import Stg.Machine.Types
import Stg.Parser.QuasiQuoter   (stg)

import Test.Machine.Evaluate.TestTemplates.Util
import Test.Tasty
import Test.Tasty.HUnit
import Test.Tasty.Runners.Html



-- | Specify a test that is based on a certain predicate to hold in an
-- evaluation step.
data MachineStateTestSpec = MachineStateTestSpec
    { testName :: Text
        -- ^ Test name to display in the test overview.

    , successPredicate :: StgState -> Bool
        -- ^ Test predicate to determine whether the desired state has been
        -- reached.

    , failPredicate :: StgState -> Bool
        -- ^ Fail if this predicate holds. This can be used to constrain the
        -- heap size during the test, for example.

    , allSatisfied :: [StgState -> Bool]
        -- ^ All of these predicates have to hold for some (not necessarily the
        -- same) intermediate states. This is for example useful to check
        -- whether at some point rule 1 was applied, and at another rule 2.

    , source :: Program
        -- ^ STG program to run.

    , maxSteps :: Integer
        -- ^ Maximum number of steps to take

    , performGc :: PerformGc
        -- ^ Perform GC under which conditions?

    , failWithInfo :: Bool
        -- ^ Print program code and final state on test failure? Very useful for
        -- fixing tests.
    }

defSpec :: MachineStateTestSpec
defSpec = MachineStateTestSpec
    { testName             = "Default machine state test template"
    , successPredicate     = "main" `isLambdaForm` [stg| \ -> Success |]
    , failPredicate        = const False
    , allSatisfied         = []
    , source               = [stg| main = \ -> DummySource |]
    , maxSteps             = 1024
    , performGc            = PerformGc (const (Just triStateTracing))
    , failWithInfo         = False }

-- | Evaluate the @main@ closure of a STG program, and check whether the machine
-- state satisfies a predicate when it is evaluated.
machineStateTest :: MachineStateTestSpec -> TestTree
machineStateTest testSpec = askOption (\htmlOpt ->
    let pprDict = case htmlOpt of
            Just HtmlPath{} -> PrettyprinterDict prettyprintPlain (plain . pretty)
            Nothing         -> PrettyprinterDict prettyprint pretty
    in testCase (T.unpack (testName testSpec)) (test pprDict) )
  where
    program = initialState "main" (source testSpec)
    states = evalsUntil (RunForMaxSteps (maxSteps testSpec))
                        (HaltIf (successPredicate testSpec))
                        (performGc testSpec)
                        program
    finalState = NE.last states

    test :: PrettyprinterDict -> Assertion
    test pprDict = case L.find (failPredicate testSpec) states of
        Just badState -> fail_failPredicateTrue pprDict testSpec badState
        Nothing -> case NE.toList states `allSatisfyAtSomePoint` allSatisfied testSpec of
            True -> case stgInfo finalState of
                Info HaltedByPredicate _ -> pure ()
                _otherwise -> fail_successPredicateNotTrue pprDict testSpec finalState
            False -> (assertFailure . T.unpack)
                "No intermediate state satisfied the required predicate."

failWithInfoInfoText :: Doc
failWithInfoInfoText = "Run test case with failWithInfo to see the final state."

fail_successPredicateNotTrue
    :: PrettyprinterDict
    -> MachineStateTestSpec
    -> StgState
    -> Assertion
fail_successPredicateNotTrue
    (PrettyprinterDict pprText pprDoc)
    testSpec
    finalState
  = (assertFailure . T.unpack . pprText . vsep)
        [ "STG failed to satisfy predicate: "
            <> pprDoc (stgInfo finalState)
        , if failWithInfo testSpec
            then vsep
                [ hang 4 (vsep ["Program:", pprDoc (source testSpec)])
                , hang 4 (vsep ["Final state:", pprDoc finalState]) ]
            else failWithInfoInfoText ]

fail_failPredicateTrue
    :: PrettyprinterDict
    -> MachineStateTestSpec
    -> StgState
    -> Assertion
fail_failPredicateTrue
    (PrettyprinterDict pprText pprDoc)
    testSpec
    badState
  = (assertFailure . T.unpack . pprText . vsep)
        [ "Failure predicate held for an intemediate state"
        , if failWithInfo testSpec
            then vsep
                [ hang 4 (vsep ["Program:", pprDoc (source testSpec)])
                , hang 4 (vsep ["Bad state:", pprDoc badState]) ]
            else failWithInfoInfoText ]


allSatisfyAtSomePoint
    :: [StgState]
    -> [StgState -> Bool]
    -> Bool
allSatisfyAtSomePoint states (p:ps)
  = L.any p states && allSatisfyAtSomePoint states ps
allSatisfyAtSomePoint _ [] = True