descript-lang-0.2.0.0: test/BuildSpec.hs
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE BangPatterns #-}
-- | Tests all parts of building.
module BuildSpec
( spec ) where
import Core.Test
import Descript
import qualified Descript.BasicInj.Data.Value.Reg as BasicInj.Reg
import qualified Descript.BasicInj as BasicInj
import qualified Descript.Sugar as Sugar
import System.FilePath hiding (isValid)
import Data.Map.Strict (Map)
import qualified Data.Map.Strict as Map
import Data.Text (Text)
import qualified Data.Text as Text
import qualified Data.Text.IO as Text
import Control.Monad
import Control.Concurrent.MVar
type VarMap k v = Map k (MVar (Maybe v))
examplesDir :: FilePath
examplesDir = "test-resources/examples/"
refactorsDir :: FilePath
refactorsDir = "test-resources/refactors/"
testDepResolver :: DepResolver IO
testDepResolver = defaultResolver examplesDir
getExampleFiles :: IO [TestFile]
getExampleFiles = loadFilesInDir examplesDir
getRefactorText :: String -> String -> IO Text
getRefactorText action = Text.readFile . getRefactorPath action
getRefactorPath :: String -> String -> String
getRefactorPath label' srcName
= refactorsDir </> srcName <.> label' <.> "dscr"
mkVarMap :: (Ord k) => [k] -> IO (VarMap k v)
mkVarMap = fmap Map.fromList . mapM (sequence . (, newMVar Nothing))
insertVarMap :: (Ord k) => VarMap k v -> k -> v -> IO ()
insertVarMap vars key x = do
Nothing <- swapMVar (vars Map.! key) $ Just x
pure ()
spec :: Spec
spec = do
exampleFiles <- runIO getExampleFiles
exampleParsedVars <- runIO $ mkVarMap exampleFiles
exampleResolvedVars <- runIO $ mkVarMap exampleFiles
exampleRefinedVars <- runIO $ mkVarMap exampleFiles
exampleInterpretedVars <- runIO $ mkVarMap exampleFiles
let forExampleFile :: (TestFile -> IO ()) -> IO ()
forExampleFile f =
forM_ exampleFiles $ \file ->
denoteFailIn ("file " ++ sfileName (srcFile file)) $ f file
forPhaseIn :: TestFile -> (String -> ParseResult (String, String) -> IO ()) -> IO ()
forPhaseIn file@(TestFile srcFile' _) f = do
result <- runResultT $ parseTest srcFile' $ \phaseName phaseRes ->
denoteFailIn ("phase " ++ phaseName) $ f phaseName phaseRes
case result of
Failure _ -> pure ()
Success val -> insertVarMap exampleParsedVars file val
forExampleIntermediateIn :: Map TestFile (MVar (Maybe a)) -> (TestFile -> a -> IO ()) -> IO ()
forExampleIntermediateIn xVars f =
forM_ (Map.toAscList xVars) $ \(file, xVar) ->
denoteFailIn ("file " ++ sfileName (srcFile file)) $ withMVar xVar $ \case
Nothing -> pure ()
Just val -> f file val
forExampleParsed :: (TestFile -> Sugar.Source SrcAnn -> IO ()) -> IO ()
forExampleParsed = forExampleIntermediateIn exampleParsedVars
forExampleResolved :: (TestFile -> BasicInj.DirtyDepd Sugar.Source SrcAnn -> IO ()) -> IO ()
forExampleResolved = forExampleIntermediateIn exampleResolvedVars
forExampleRefinedSrc :: (TestFile -> BasicInj.DirtyDepd BasicInj.Source SrcAnn -> IO ()) -> IO ()
forExampleRefinedSrc = forExampleIntermediateIn exampleRefinedVars
forExampleRefinedProg :: (TestFile -> BasicInj.DirtyDepd BasicInj.Program SrcAnn -> IO ()) -> IO ()
forExampleRefinedProg f = forExampleRefinedSrc $ \file -> \case
Depd _ (BasicInj.SourceModule _) -> pure ()
Depd ddep (BasicInj.SourceProgram prgm) -> f file $ Depd ddep prgm
forExampleInterpreted :: (TestFile -> BasicInj.Reg.Value () -> IO ()) -> IO ()
forExampleInterpreted = forExampleIntermediateIn exampleInterpretedVars
describe "Read" $ do
it "Parses" $
forExampleFile $ \file@(TestFile srcFile' testInfo') -> do
let shouldFailTo = shouldFailToSummaryIn srcFile'
fileStr = Text.unpack $ sfileContents srcFile'
if isParseable testInfo' then do
forPhaseIn file $ \phaseName phaseRes -> do
when (phaseName `elem` printParsedPhases testInfo') $ do
putStrLn $ sfileName srcFile' ++ " - " ++ phaseName ++ ":"
putStrLn $ summaryF srcFile' phaseRes
case phaseRes of
Failure phaseErr -> assertFailure $ summaryF srcFile' phaseErr
Success (phaseRp, _) -> phaseRp `shouldBeReducePrintOf` fileStr
else
unless (null $ errorMsg testInfo') $ do
parse srcFile' `shouldFailTo` errorMsg testInfo'
it "Resolves" $
-- Dependency failures are handled by the validation tests.
forExampleParsed $ \file@(TestFile srcFile' testInfo') psrc -> do
rsddsrc <- Sugar.resolve testDepResolver psrc
when (printDependency testInfo') $ do
let dep = dirtyVal $ depdDep rsddsrc
putStrLn $ sfileName srcFile' ++ ":"
Text.putStrLn $ pprint dep
insertVarMap exampleResolvedVars file rsddsrc
it "Refines" $
-- This test is only useful to check hanging and exceptions.
-- Not sure if it even does that because the source isn't forced.
forExampleResolved $ \file rsddsrc -> do
let !rfddsrc = Sugar.refineDDepd rsddsrc
insertVarMap exampleRefinedVars file rfddsrc
describe "Process" $ do
it "Validates" $
forExampleRefinedSrc $ \(TestFile _ testInfo') ddsrc -> do
let pmsgs = map summary $ BasicInj.validate' ddsrc
if isValid testInfo' then
pmsgs `shouldSatisfy` null
else
unless (null $ problemMsgs testInfo') $ do
pmsgs `shouldBe` problemMsgs testInfo'
it "Interprets" $
forExampleRefinedProg $ \file@(TestFile _ testInfo') ddprog ->
when (isValid testInfo' && isTerminating testInfo') $ do
let dprog = mapDep dirtyVal ddprog
!interpreted = BasicInj.interpret_ dprog
insertVarMap exampleInterpretedVars file interpreted
it "Refactors" $
forExampleFile $ \(TestFile srcFile' testInfo') -> do
let srcDFile = DFile testDepResolver srcFile'
srcText' = sfileContents srcFile'
forM_ (refactorCmds testInfo') $ \(RefactorInfo action args ewarnMsgs eerrMsg label') -> do
let shouldSucceed = null eerrMsg
Dirty awarnings res <- runDirtyResT $ parseRefactor action args srcDFile
let awarnMsgs = map summary awarnings
case res of
Failure err -> do
let aerrMsg = summaryF srcFile' err
if shouldSucceed then
assertFailure aerrMsg
else
aerrMsg `shouldBe` eerrMsg
Success patch -> do
let anewSrcText = apPatch patch srcText'
if shouldSucceed then do
enewSrcText <- getRefactorText label' $ sfileName srcFile'
anewSrcText `shouldBe` enewSrcText
else
assertFailure $ "Unexpected success\n" ++ Text.unpack anewSrcText
awarnMsgs `shouldBe` ewarnMsgs
describe "Write" $ do
it "Compiles" $
forExampleInterpreted $ \(TestFile srcFile' testInfo') interpreted ->
when (isFinal testInfo') $
case BasicInj.compile (sfileName srcFile') interpreted of
Failure err -> assertFailure $ summary err
Success package -> do
when (printCompiled testInfo') $ do
putStrLn $ sfileName srcFile' ++ ":"
Text.putStrLn $ pprintPackage package
unless (Text.null $ packagePr testInfo') $
pprintPackage package `shouldBe` packagePr testInfo'
it "Evaluates" $
forExampleInterpreted $ \(TestFile srcFile' testInfo') interpreted -> do
when (printEvaluated testInfo') $ do
putStr $ sfileName srcFile' ++ ": "
Text.putStrLn $ pprint interpreted
unless (Text.null $ evalPr testInfo') $
pprint interpreted `shouldBe` evalPr testInfo'
it "Reprints" $
forExampleRefinedSrc $ \(TestFile srcFile' testInfo') dsrc -> do
let source = depdVal dsrc
srcText' = sfileContents srcFile'
patch = ppatchThorough source
when (printReprinted testInfo') $ do
putStrLn $ sfileName srcFile' ++ ": "
Text.putStrLn $ reprint (sfileContents srcFile') source
reprint srcText' source `shouldSatisfy` (`Text.isInfixOf` srcText')
apPatch patch srcText' `shouldBe` srcText'
patchOffset patch `shouldBe` mempty