packages feed

rest-rewrite-0.4.5: test/Test.hs

{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE ImplicitParams #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RankNTypes #-}

module Main where

import qualified Data.List as L
import Data.Hashable
#if MIN_VERSION_mtl(2,3,0)
import Control.Monad (guard)
#endif
import Control.Monad.Identity
import qualified Arith as A

import qualified Data.HashMap.Strict as M
import qualified ExploredTerms
import OpOrdering
import DSL
import WQO
import MultisetOrder
import Nat
import RPO
import KBO
import StrictOC
import LazyOC
import SMT
import qualified QuickCheckTests
import System.IO

import Language.REST.ExploredTerms
import Language.REST.OCAlgebra
import Language.REST.OCToAbstract
import Language.REST.Core
import Language.REST.LPO (lpo)
import Language.REST.KBO (kbo)
import Language.REST.WQOConstraints as OC
import Language.REST.Op
import Language.REST.RPO
import Language.REST.Internal.OpOrdering
import Language.REST.RewriteRule
import Language.REST.RuntimeTerm
import Language.REST.MetaTerm as MT
import Language.REST.Internal.Rewrite
import Language.REST.Internal.WQO
import Language.REST.Rest
import Language.REST.SMT
import qualified Language.REST.WQOConstraints.ADT as AC
import Language.REST.Internal.WorkStrategy
import qualified Data.Maybe as Mb
import qualified Data.HashSet as S


-- | 'canOrient' returns true iff the ordering constraint algebra permits an ordering
--   that orients, the path, i.e., the constraints generated by 'orient' are satisfiable.
canOrient :: forall oc m . Show oc
  => (?impl :: OCAlgebra oc RuntimeTerm m) => [RuntimeTerm] -> m Bool
canOrient terms = isSat ?impl (orient ?impl terms)

diverges :: (Show oc) => OCAlgebra oc RuntimeTerm IO -> [RuntimeTerm] -> IO Bool
diverges impl ts = not <$> isSat impl (orient impl ts)

rewrites :: (Show oc, Hashable oc, Eq oc)
  => OCAlgebra oc RuntimeTerm IO
  -> S.HashSet Rewrite -> S.HashSet Rewrite -> RuntimeTerm -> IO (S.HashSet RuntimeTerm)
rewrites impl evalRWs userRWs t0 =
  resultTerms . fst <$> rest
    RESTParams
      { re           = evalRWs
      , ru           = userRWs
      , target       = Nothing
      , workStrategy = notVisitedFirst
      , ocImpl       = ?impl
      , initRes      = termsResult
      , etStrategy   = ExploreWhenNeeded
      } t0
  where
    ?impl = impl

runTest :: (String, IO Bool) -> IO ()
runTest (name, test) = do
  putStr $ "Running " ++ name ++ "... "
  hFlush stdout
  ok <- test
  if ok
    then putStrLn "Passed"
    else error $ "Test " ++ name ++ " FAILED"

class Testable a where
  toTest :: a -> IO Bool

instance Testable Bool where
  toTest = return

instance Testable (IO Bool) where
  toTest = id

runTestSuite :: Testable a => String -> [(String, a)] -> IO ()
runTestSuite name tests1 = do
  putStrLn $ "Running test suite: " ++ name
  mapM_ (runTest . go) tests1
  where
    go (name1, test) = (name1, toTest test)


orderingTests :: (Hashable (oc Op), Show (oc Op), Ord (oc Op)) => (?impl :: WQOConstraints oc IO) => [(String, IO Bool)]
orderingTests =
  [
    ("simple1", return $ not $ rpoGTE "f(t1)" "g(t2)" `permits'` (t1Op =. t2Op))
  , ("simple2", return $ rpoGTE "f(t1)" "g(t2)" `permits'` Mb.fromJust (merge (f >. g) (t1Op =. t2Op)))
  , ("simple3", return $ rpoGTE "f(t1)" "g(t2)" `permits'` Mb.fromJust (merge (f >. g) (t1Op >. t2Op)))
  , ("subterm", return $ rpoGTE "f(g)" "f" == noConstraints ?impl)
  , ("intersect", OC.isUnsatisfiable ?impl $ OC.intersect ?impl (OC.singleton ?impl (f  >. g)) (OC.singleton ?impl (g >. f)))
  ]
  where
    permits' = permits ?impl

proveEQ :: (Show oc, Hashable oc, Eq oc) =>
     OCAlgebra oc RuntimeTerm IO
  -> S.HashSet Rewrite -> S.HashSet Rewrite
  -> RuntimeTerm -> RuntimeTerm -> IO Bool
proveEQ impl evalRWs userRWs have want =
  do
    rw1 <- rewrites impl evalRWs userRWs have
    rw2 <- rewrites impl evalRWs userRWs want
    return $ not $ disjoint rw1 rw2
  where
    disjoint s1 s2 = S.null $ s1 `S.intersection` s2

eval :: S.HashSet Rewrite -> RuntimeTerm -> IO RuntimeTerm
eval rws t0 =
  do
    result <- mapM (apply t0) (S.toList rws)
    case S.toList $ S.unions result of
      []      -> return t0
      (t : _) -> eval rws t

arithTests :: (Show oc, Hashable oc, Eq oc) => OCAlgebra oc RuntimeTerm IO -> [(String, IO Bool)]
arithTests impl =
  [
    ("Contains", return $ contains (intToTerm 2) (intToTerm 1))
  , ("Diverge", not <$> diverges impl [ intToTerm 2 .+ t1
                               , intToTerm 1 .+ t1
                               ])
  , ("Diverge3", not <$> diverges impl [ (t1 .+ t2) .+ t3
                               , t1 .+ (t2 .+ t3)
                               , (t2 .+ t3) .+ t1
                               ])
  , ("Eval1", arithEQ (intToTerm 2 .+ intToTerm 3) 5)
  , ("Eval2", arithEQ (ack (intToTerm 3) (intToTerm 2)) 29)
  , ("Subst1", return $ subst (M.fromList [("X", intToTerm 1), ("Y", intToTerm 2)]) (x #+ y) == (intToTerm 1 .+ intToTerm 2))
  , ("ArithTerm", termTest)
  , ("ArithTerm2", termTest2)
  , ("Arith0", eq (t1 .+ t2 .+ intToTerm 1) (t1 .+ (intToTerm 1 .+ t2)))
  , ("Arith1", eq (intToTerm 2 .+ intToTerm 3) (intToTerm 3 .+ intToTerm 2))
  , ("Arith2", eq (t1 .+ t2) (t2 .+ t1))
  , ("Arith3", not <$> eq (t2 .+ t1) (t2 .+ t2))
  , ("Arith4", eq ((t1 .+ t2) .+ t3) (t1 .+ (t2.+ t3)))
  , ("Arith4.1", eq (t1 .+ t2 .+ t3) (t3 .+ t2 .+ t1))
  , ("Arith5", eq (zero .+ t1) t1)
  , ("Arith5.1", eq (zero .+ zero .+ t1) t1)
  , ("Arith5.2", eq (zero .+ zero .+ zero .+ t1) t1)
  , ("Arith6", eq (((t1 .+ t1) .+ t3) .+ t4) (t1 .+ (t1 .+ (t3 .+ t4))))
  , ("Arith7", eq ((intToTerm 2 .+ intToTerm 1) .+ t1) (intToTerm 2 .+ (intToTerm 1 .+ t1)))
  ]
  where

    arithEQ t n = do
      t' <- eval A.evalRWs t
      return $ termToInt t' == Just n


    termTest = proveEQ impl evalRWs userRWs (App f1 [t1]) zero
      where
        evalRWs = S.union termEvalRWs  A.evalRWs
        userRWs = S.insert (MT.RWApp g1 [x] ~> MT.RWApp f1 [x]) A.userRWs
        termEvalRWs = S.fromList
          [  MT.RWApp f1 [x] ~> MT.RWApp g1 [suc' x]
          ,  MT.RWApp g1 [x] ~> zero'
          ]
        f1 = Op "f"
        g1 = Op "g"

    termTest2 = proveEQ impl evalRWs userRWs (App f1 [zero]) (App g1 [zero])
      where
        evalRWs = S.union termEvalRWs A.evalRWs
        userRWs = S.insert (MT.RWApp f1 [x] ~> MT.RWApp g1 [suc' (suc' x)]) A.userRWs
        termEvalRWs = S.fromList
          [  MT.RWApp f1 [suc' x] ~> MT.RWApp g1 [suc' x]
          ,  MT.RWApp f1 [zero']  ~> zero'
          ,  MT.RWApp g1 [suc' x] ~> MT.RWApp f1 [x]
          ,  MT.RWApp g1 [zero']  ~> zero'
          ]
        f1 = Op "f"
        g1 = Op "g"


    eq = proveEQ impl A.evalRWs A.userRWs

completeTests :: (Show oc, Hashable oc, Eq oc) => OCAlgebra oc RuntimeTerm IO -> [(String, IO Bool)]
completeTests impl =
  [ ("CompleteDiverges", not <$> diverges impl [App start [], App mid [], App finish []])
  , ("Complete1"     , eq (App start []) (App finish []))
  , ("EvalComplete2" , (== App finish []) <$> eval completeUserRWs (App start' [App s1 []]) )
  , ("Complete2"     , eq (App start' [App s1 []]) (App finish []))
  ]
  where

    completeUserRWs = S.fromList
      [ MT.RWApp start [] ~> MT.RWApp mid []
      , MT.RWApp mid []   ~> MT.RWApp finish []

      , MT.RWApp start' [MT.RWApp s2 []] ~> MT.RWApp mid' [MT.RWApp s1 []]
      , MT.RWApp s1 []                 ~> MT.RWApp s2 []
      , MT.RWApp mid' [MT.RWApp s2 []]   ~> MT.RWApp finish []
      ]

    eq :: RuntimeTerm -> RuntimeTerm -> IO Bool
    eq = proveEQ impl S.empty completeUserRWs

    start  = Op "start"
    mid    = Op "mid"
    finish = Op "finish"

    start'  = Op "start'"
    mid'    = Op "mid'"

    s1      = Op "s1"
    s2      = Op "s2"

ocTests :: (Handle, Handle) -> IO ()
ocTests z3 = do
  runTestSuite "LazyOC" LazyOC.tests
  runTestSuite "StrictOC" StrictOC.tests
  runTestSuite "RPO" RPO.tests
  runTestSuite "Ordering" orderingTests
  where
    ?impl = AC.adtOC z3

main :: IO ()
main = spawnZ3 >>= go where

  implTests implName impl toSkip = do
    runTestSuite ("Arith" ++ implName) (withSkips $ arithTests impl)
    runTestSuite ("Complete" ++ implName) (withSkips $ completeTests impl)
    where
      withSkips tests1 = do
        (name, test) <- tests1
        guard $ L.notElem name toSkip
        return (name, test)


  go :: SolverHandle -> IO ()
  go z3 =
    do
      putStrLn "Running REST Test Suite"
      runTestSuite "ExploredTerms" ExploredTerms.tests
      runTestSuite "SMT" SMT.tests
      runTestSuite "KBO" (KBO.tests z3)
      _ <- QuickCheckTests.tests
      runTestSuite "OpOrdering" OpOrdering.tests
      ocTests z3
      runTestSuite "MultisetOrder" MultisetOrder.tests
      runTestSuite "WQO" WQO.tests
      implTests "KBO" (kbo z3) []
      implTests "RPO" (lift (AC.adtOC z3) rpo) []
      implTests "LPO" (lift (AC.adtOC z3) lpo) ["Diverge3", "Arith4", "Arith4.1", "Arith6"]
      killZ3 z3