target-0.2.0.0: src/Test/Target/Testable.hs
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE DoAndIfThenElse #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverlappingInstances #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE ViewPatterns #-}
module Test.Target.Testable (test, Testable) where
import Control.Applicative
import Control.Exception (AsyncException, evaluate)
import Control.Monad
import Control.Monad.Catch
import Control.Monad.Reader
import Control.Monad.State
import qualified Data.HashMap.Strict as M
import qualified Data.HashSet as S
import Data.Proxy
import qualified Data.Text as T
import Data.Text.Format hiding (print)
import qualified Data.Text.Lazy as LT
import Text.Printf
import Language.Fixpoint.Smt.Interface
import Language.Fixpoint.Smt.Theories (theorySymbols)
import Language.Fixpoint.Types hiding (Result)
import Language.Haskell.Liquid.RefType
import Language.Haskell.Liquid.Types hiding (Result (..), env, var)
import Test.Target.Targetable hiding (apply)
-- import Test.Target.Eval
import Test.Target.Expr
import Test.Target.Monad
import Test.Target.Types
import Test.Target.Util
-- import Debug.Trace
-- | Test that a function inhabits the given refinement type by enumerating
-- valid inputs and calling the function on the inputs.
test :: Testable f => f -> SpecType -> Target Result
test f t
= do d <- asks depth
vs <- queryArgs f d t
setup
let (xs, tis, _, to) = bkArrowDeep $ stripQuals t
ctx <- gets smtContext
try (process f ctx vs (zip xs tis) to) >>= \case
Left (e :: TargetException) -> return $ Errored $ show e
Right r -> return r
process :: Testable f
=> f -> Context -> [Symbol] -> [(Symbol,SpecType)] -> SpecType
-> Target Result
process f ctx vs xts to = go 0 =<< io (command ctx CheckSat)
where
go !n Unsat = return $ Passed n
go _ (Error e)= throwM $ SmtError $ T.unpack e
go !n Sat = do
when (n `mod` 100 == 0) $ whenVerbose $ io $ printf "Checked %d inputs\n" n
let n' = n + 1
xs <- decodeArgs f vs (map snd xts)
whenVerbose $ io $ print xs
er <- io $ try $ evaluate (apply f xs)
-- whenVerbose $ io $ print er
case er of
Left (e :: SomeException)
-- DON'T catch AsyncExceptions since they are used by @timeout@
| Just (_ :: AsyncException) <- fromException e -> throwM e
| Just (SmtError _) <- fromException e -> throwM e
| Just (ExpectedValues _) <- fromException e -> throwM e
| otherwise -> do
real <- gets realized
modify $ \s@(TargetState {..}) -> s { realized = [] }
_ <- io $ command ctx $ Assert Nothing $ PNot $ pAnd
[ ESym (SL $ symbolText x) `eq` ESym (SL v) | (x,v) <- real ]
mbKeepGoing xs n'
Right r -> do
real <- gets realized
modify $ \s@(TargetState {..}) -> s { realized = [] }
let su = mkSubst $ mkExprs f (map fst xts) xs
(sat, _) <- check r (subst su to)
-- refute model *after* checking output in case we have HOFs, which
-- need to query the solver. if this is the last set of inputs, e.g.
-- refuting the current model forces the solver to return unsat next
-- time, the solver will return unsat when the HOF queries for an output,
-- causing us to return a spurious error
_ <- io $ command ctx $ Assert Nothing $ PNot $ pAnd
[ ESym (SL $ symbolText x) `eq` ESym (SL v) | (x,v) <- real ]
case sat of
False -> mbKeepGoing xs n'
True -> do
asks maxSuccess >>= \case
Nothing -> go n' =<< io (command ctx CheckSat)
Just m | m == n' -> return $ Passed m
| otherwise -> go n' =<< io (command ctx CheckSat)
go _ r = error $ "go _ " ++ show r
mbKeepGoing xs n = do
kg <- asks keepGoing
if kg
then go n =<< io (command ctx CheckSat)
else return (Failed $ show xs)
-- | A class of functions that Target can test. A function is @Testable@ /iff/
-- all of its component types are 'Targetable' and all of its argument types are
-- 'Show'able.
--
-- You should __never__ have to define a new 'Testable' instance.
class (AllHave Targetable (Args f), Targetable (Res f)
,AllHave Show (Args f)) => Testable f where
queryArgs :: f -> Int -> SpecType -> Target [Symbol]
decodeArgs :: f -> [Symbol] -> [SpecType] -> Target (HList (Args f))
apply :: f -> HList (Args f) -> Res f
mkExprs :: f -> [Symbol] -> HList (Args f) -> [(Symbol,Expr)]
instance (Show a, Targetable a, Testable b) => Testable (a -> b) where
queryArgs f d (stripQuals -> (RFun x i o _))
= do v <- query (Proxy :: Proxy a) d i
vs <- queryArgs (f undefined) d (subst (mkSubst [(x,var v)]) o)
return (v:vs)
queryArgs _ _ t = error $ "queryArgs called with non-function type: " ++ show t
decodeArgs f (v:vs) (t:ts)
= liftM2 (:::) (decode v t) (decodeArgs (f undefined) vs ts)
decodeArgs _ _ _ = error "decodeArgs called with empty list"
apply f (x ::: xs)
= apply (f x) xs
apply _ _ = error "apply called with empty list"
mkExprs f (v:vs) (x ::: xs)
= (v, toExpr x) : mkExprs (f undefined) vs xs
mkExprs _ _ _ = error "mkExprs called with empty list"
instance (Targetable a, Args a ~ '[], Res a ~ a) => Testable a where
queryArgs _ _ _ = return []
decodeArgs _ _ _ = return Nil
apply f _ = f
mkExprs _ _ _ = []
func :: Sort -> Bool
func (FFunc _ _) = True
func _ = False
setup :: Target ()
setup = {-# SCC "setup" #-} do
ctx <- gets smtContext
emb <- gets embEnv
-- declare sorts
ss <- S.toList <$> gets sorts
let defSort b e = io $ smtWrite ctx (format "(define-sort {} () {})" (b,e))
-- FIXME: combine this with the code in `fresh`
forM_ ss $ \case
FObj "Int" -> return ()
FInt -> return ()
FObj "GHC.Types.Bool" -> defSort ("GHC.Types.Bool" :: T.Text) ("Bool" :: T.Text)
FObj "CHOICE" -> defSort ("CHOICE" :: T.Text) ("Bool" :: T.Text)
s -> defSort (LT.toStrict $ smt2Sort s) ("Int" :: T.Text)
-- declare constructors
cts <- gets constructors
mapM_ (\ (c,t) -> io $ smtWrite ctx $ makeDecl (symbol c) t) cts
let nullary = [var c | (c,t) <- cts, not (func t)]
unless (null nullary) $
void $ io $ command ctx $ Distinct nullary
-- declare variables
vs <- gets variables
let defVar (x,t) = io $ smtWrite ctx (makeDecl x (arrowize t))
mapM_ defVar vs
-- declare measures
ms <- gets measEnv
let defFun x t = io $ smtWrite ctx (makeDecl x t)
forM_ ms $ \m -> do
let x = val (name m)
if x `M.member` theorySymbols
then return ()
else defFun x (rTypeSort emb (sort m))
-- assert constraints
cs <- gets constraints
--mapM_ (\c -> do {i <- gets seed; modify $ \s@(GS {..}) -> s { seed = seed + 1 };
-- io . command ctx $ Assert (Just i) c})
-- cs
mapM_ (\c -> io . command ctx $ Assert Nothing c) cs
-- deps <- V.fromList . map (symbol *** symbol) <$> gets deps
-- io $ generateDepGraph "deps" deps cs
-- return (ctx,vs,deps)
sortTys :: Sort -> [Sort]
sortTys (FFunc _ ts) = concatMap sortTys ts
sortTys t = [t]
arrowize :: Sort -> Sort
arrowize = FObj . symbol . LT.intercalate "->" . map (LT.fromStrict . symbolText . unObj) . sortTys
unObj :: Sort -> Symbol
unObj FInt = "Int"
unObj (FObj s) = s
unObj s = error $ "unObj: " ++ show s