1
1
mirror of https://github.com/github/semantic.git synced 2024-12-21 22:01:46 +03:00
semantic/semantic-python/test/Test.hs

129 lines
5.7 KiB
Haskell
Raw Normal View History

{-# LANGUAGE DeriveAnyClass, DerivingStrategies, GeneralizedNewtypeDeriving, OverloadedStrings, TypeApplications, TypeOperators, ScopedTypeVariables #-}
2019-08-13 21:01:50 +03:00
module Main (main) where
2019-08-16 20:20:08 +03:00
import qualified Analysis.Eval as Eval
2019-08-13 21:01:50 +03:00
import Control.Effect
2019-08-16 19:34:56 +03:00
import Control.Effect.Fail
2019-09-18 21:41:58 +03:00
import Control.Effect.Reader
2019-08-16 19:34:56 +03:00
import Control.Monad hiding (fail)
2019-08-16 20:20:08 +03:00
import Control.Monad.Catch
2019-08-13 21:01:50 +03:00
import Control.Monad.IO.Class
import Control.Monad.Trans.Resource (ResourceT, runResourceT)
2019-08-16 20:20:08 +03:00
import qualified Data.Aeson as Aeson
import qualified Data.Aeson.Encode.Pretty as Aeson
2019-08-16 19:34:56 +03:00
import qualified Data.ByteString.Char8 as ByteString
import qualified Data.ByteString.Lazy.Char8 as ByteString.Lazy
2019-08-16 20:20:08 +03:00
import qualified Data.ByteString.Streaming.Char8 as ByteStream
2019-08-13 21:01:50 +03:00
import Data.Core
2019-08-16 20:20:08 +03:00
import Data.Core.Pretty
2019-08-16 19:34:56 +03:00
import Data.File
2019-08-13 21:01:50 +03:00
import Data.Foldable
2019-08-16 20:20:08 +03:00
import Data.Function
2019-08-29 16:32:39 +03:00
import Data.List (sort)
2019-08-16 19:34:56 +03:00
import Data.Loc
2019-08-16 20:20:08 +03:00
import Data.Maybe
2019-08-13 21:01:50 +03:00
import Data.Name
import Data.Term
import Data.String (fromString)
2019-08-13 21:01:50 +03:00
import GHC.Stack
import qualified Language.Python.Core as Py
2019-08-16 20:20:08 +03:00
import Prelude hiding (fail)
2019-09-27 22:15:37 +03:00
import qualified Source.Span as Source (Span)
2019-08-16 20:20:08 +03:00
import Streaming
import qualified Streaming.Prelude as Stream
2019-08-16 20:20:08 +03:00
import qualified Streaming.Process
2019-08-13 21:01:50 +03:00
import System.Directory
2019-08-16 20:20:08 +03:00
import System.Exit
2019-08-13 21:01:50 +03:00
import qualified TreeSitter.Python as TSP
import qualified TreeSitter.Python.AST as TSP
import qualified TreeSitter.Unmarshal as TS
import Text.Show.Pretty (ppShow)
2019-09-18 21:41:58 +03:00
import qualified System.Path as Path
import qualified System.Path.Directory as Path
import System.Path ((</>))
2019-08-13 21:01:50 +03:00
import qualified Test.Tasty as Tasty
import qualified Test.Tasty.HUnit as HUnit
import Analysis.ScopeGraph
2019-09-03 20:34:00 +03:00
import qualified Directive
import Instances ()
2019-08-16 19:34:56 +03:00
assertJQExpressionSucceeds :: Show a => Directive.Directive -> a -> Term (Ann :+: Core) Name -> HUnit.Assertion
assertJQExpressionSucceeds directive tree core = do
bod <- case scopeGraph Eval.eval [File interactive core] of
(heap, [File _ (Right result)]) -> pure $ Aeson.object
[ "scope" Aeson..= heap
, "heap" Aeson..= result
, "tree" Aeson..= Aeson.toJSON1 core
]
2019-09-03 20:34:00 +03:00
_other -> HUnit.assertFailure "Couldn't run scope dumping mechanism; this shouldn't happen"
2019-08-16 20:20:08 +03:00
let ignore = ByteStream.effects . hoist ByteStream.effects
sgJSON = ByteStream.fromLazy $ Aeson.encode bod
jqPipeline = Streaming.Process.withStreamingProcess (Directive.toProcess directive) sgJSON ignore
2019-08-16 20:20:08 +03:00
errorMsg = "jq(1) returned non-zero exit code"
dirMsg = "jq expression: " <> show directive
jsonMsg = "JSON value: " <> ByteString.Lazy.unpack (Aeson.encodePretty bod)
astMsg = "AST (pretty): " <> ppShow tree
treeMsg = "Core expr (pretty): " <> showCore (stripAnnotations core)
treeMsg' = "Core expr (Show): " <> ppShow (stripAnnotations core)
2019-08-16 20:20:08 +03:00
catch @_ @Streaming.Process.ProcessExitedUnsuccessfully jqPipeline $ \err -> do
HUnit.assertFailure (unlines [errorMsg, dirMsg, jsonMsg, astMsg, treeMsg, treeMsg', show err])
2019-08-16 20:20:08 +03:00
2019-09-18 21:41:58 +03:00
fixtureTestTreeForFile :: HasCallStack => Path.RelFile -> Tasty.TestTree
fixtureTestTreeForFile fp = HUnit.testCaseSteps (Path.toString fp) $ \step -> withFrozenCallStack $ do
2019-09-24 07:57:22 +03:00
let fullPath = Path.relDir "semantic-python/test/fixtures" </> fp
perish s = liftIO (HUnit.assertFailure ("Directive parsing error: " <> s))
isComment = (== Just '#') . fmap fst . ByteString.uncons
2019-09-18 21:41:58 +03:00
-- Slurp the input file, taking lines from the beginning until we
-- encounter a line that doesn't have a '#'. For each line, parse
-- a directive out of it, failing if the directive can't be parsed.
directives <-
runResourceT
. Stream.toList_
. Stream.mapM (either perish pure . Directive.parseDirective)
2019-09-24 07:57:22 +03:00
. Stream.takeWhile isComment
. Stream.mapped ByteStream.toStrict
. ByteStream.lines
. ByteStream.readFile @(ResourceT IO)
$ Path.toString fullPath
2019-08-27 18:29:12 +03:00
result <- ByteString.readFile (Path.toString fullPath) >>= TS.parseByteString TSP.tree_sitter_python
2019-09-19 22:35:25 +03:00
let coreResult = Control.Effect.run
. runFail
. runReader (fromString @Py.SourcePath . Path.toString $ fp)
2019-09-23 18:06:10 +03:00
. runReader @Py.Bindings mempty
2019-09-27 22:15:37 +03:00
. Py.compile @(TSP.Module Source.Span) @_ @(Term (Ann :+: Core))
2019-09-19 22:35:25 +03:00
<$> result
for_ directives $ \directive -> do
step (Directive.describe directive)
case (coreResult, directive) of
(Right (Left _), Directive.Fails) -> pure ()
(Left err, _) -> HUnit.assertFailure ("Parsing failed: " <> err)
(Right (Left err), _) -> HUnit.assertFailure ("Compilation failed: " <> err)
(Right (Right _), Directive.Fails) -> HUnit.assertFailure ("Expected translation to fail")
(Right (Right item), Directive.JQ _) -> assertJQExpressionSucceeds directive result item
2019-09-23 20:02:50 +03:00
(Right (Right item), Directive.Tree t) -> let msg = "got (pretty): " <> showCore item'
item' = stripAnnotations item
in HUnit.assertEqual msg t item' where
2019-08-13 21:01:50 +03:00
milestoneFixtures :: IO Tasty.TestTree
milestoneFixtures = do
2019-09-18 21:41:58 +03:00
files <- liftIO (Path.filesInDir (Path.relDir "semantic-python/test/fixtures"))
let pythons = sort (filter (Path.hasExtension ".py") files)
2019-08-29 16:30:58 +03:00
pure $ Tasty.testGroup "Translation" (fmap fixtureTestTreeForFile pythons)
2019-08-13 21:01:50 +03:00
main :: IO ()
2019-08-16 19:34:56 +03:00
main = do
jq <- findExecutable "jq"
when (isNothing jq) (die "Error: jq(1) not found in $PATH.")
milestoneFixtures >>= Tasty.defaultMain