mirror of
https://github.com/github/semantic.git
synced 2024-12-27 17:05:33 +03:00
Merge pull request #370 from github/concrete-python-tests
Add Check-Result directive for querying Python concrete evaluation.
This commit is contained in:
commit
ab3bace35d
@ -23,6 +23,7 @@ common haskell
|
|||||||
build-depends: base ^>=4.12
|
build-depends: base ^>=4.12
|
||||||
, fused-effects ^>= 0.5
|
, fused-effects ^>= 0.5
|
||||||
, fused-syntax
|
, fused-syntax
|
||||||
|
, parsers ^>= 0.12.10
|
||||||
, semantic-core ^>= 0.0
|
, semantic-core ^>= 0.0
|
||||||
, semantic-source ^>= 0.0
|
, semantic-source ^>= 0.0
|
||||||
, semantic-tags ^>= 0.0
|
, semantic-tags ^>= 0.0
|
||||||
|
@ -363,7 +363,7 @@ instance Compile Py.String where
|
|||||||
Prj Py.EscapeSequence { text } -> pure text
|
Prj Py.EscapeSequence { text } -> pure text
|
||||||
other -> fail ("Couldn't string-desugar " <> show other)
|
other -> fail ("Couldn't string-desugar " <> show other)
|
||||||
|
|
||||||
let new = pure "__semantic_prelude" ... "str" ... "__class" ... "__new__"
|
let new = pure "__semantic_prelude" ... "str" ... "__slots" ... "__new__"
|
||||||
cc $ locate it (new $$ Core.string (mconcat contents))
|
cc $ locate it (new $$ Core.string (mconcat contents))
|
||||||
|
|
||||||
instance Compile Py.Subscript
|
instance Compile Py.Subscript
|
||||||
|
@ -8,15 +8,10 @@
|
|||||||
// object's superclass is type itself
|
// object's superclass is type itself
|
||||||
object <- type "object" type #record{};
|
object <- type "object" type #record{};
|
||||||
|
|
||||||
str <- type "str" object #record { };
|
str <- type "str" object #record { __new__: \prim -> instance #unit prim #record{} };
|
||||||
str.__new__ = (\contents -> instance str contents #record{});
|
|
||||||
|
|
||||||
getitem <- \super -> \item -> \attr ->
|
getitem <- \super -> \item -> \attr ->
|
||||||
if item.slots.?attr then item.slots.attr else #unit;
|
if item.slots.?attr then item.slots.attr else #unit;
|
||||||
|
|
||||||
new <- \class -> class.__new__;
|
#record { type: type, object: object, str: str, getitem: getitem}
|
||||||
|
|
||||||
example <- new str "hello";
|
|
||||||
|
|
||||||
#record { type: type, object: object, str: str, getitem: getitem, new: new, example: example }
|
|
||||||
}
|
}
|
||||||
|
@ -1,19 +1,35 @@
|
|||||||
|
{-# LANGUAGE TypeApplications, TypeOperators #-}
|
||||||
|
|
||||||
|
-- | FileCheck-style directives for testing Core compilers.
|
||||||
module Directive ( Directive (..)
|
module Directive ( Directive (..)
|
||||||
, parseDirective
|
, readDirectivesFromFile
|
||||||
, describe
|
, describe
|
||||||
, toProcess
|
, toProcess
|
||||||
) where
|
) where
|
||||||
|
|
||||||
|
import Analysis.Concrete (Concrete (..))
|
||||||
import Control.Applicative
|
import Control.Applicative
|
||||||
|
import Control.Effect
|
||||||
import Control.Monad
|
import Control.Monad
|
||||||
|
import Control.Monad.Trans.Resource (ResourceT, runResourceT)
|
||||||
import Core.Core (Core)
|
import Core.Core (Core)
|
||||||
|
import qualified Core.Core as Core
|
||||||
|
import Core.Name (Name)
|
||||||
import qualified Core.Parser
|
import qualified Core.Parser
|
||||||
import qualified Core.Pretty
|
import qualified Core.Pretty
|
||||||
import Core.Name (Name)
|
|
||||||
import Data.ByteString.Char8 (ByteString)
|
import Data.ByteString.Char8 (ByteString)
|
||||||
import qualified Data.ByteString.Char8 as ByteString
|
import qualified Data.ByteString.Char8 as ByteString
|
||||||
|
import qualified Data.ByteString.Streaming.Char8 as ByteStream
|
||||||
|
import Data.Text (Text)
|
||||||
|
import qualified Data.Text as T
|
||||||
|
import qualified Source.Span as Source
|
||||||
|
import qualified Streaming.Prelude as Stream
|
||||||
import Syntax.Term (Term)
|
import Syntax.Term (Term)
|
||||||
|
import qualified System.Path as Path
|
||||||
|
import qualified System.Path.PartClass as Path.Class
|
||||||
import System.Process
|
import System.Process
|
||||||
|
import qualified Text.Parser.Token.Style as Style
|
||||||
|
import Text.Trifecta (CharParsing, TokenParsing (..))
|
||||||
import qualified Text.Trifecta as Trifecta
|
import qualified Text.Trifecta as Trifecta
|
||||||
|
|
||||||
{- |
|
{- |
|
||||||
@ -42,29 +58,62 @@ projects.
|
|||||||
-}
|
-}
|
||||||
data Directive = JQ ByteString -- | @# CHECK-JQ: expr@
|
data Directive = JQ ByteString -- | @# CHECK-JQ: expr@
|
||||||
| Tree (Term Core Name) -- | @# CHECK-TREE: core@
|
| Tree (Term Core Name) -- | @# CHECK-TREE: core@
|
||||||
|
| Result Text (Concrete (Term (Core.Ann Source.Span :+: Core)) Name) -- | @# CHECK-RESULT key: expected
|
||||||
| Fails -- | @# CHECK-FAILS@ fails unless translation fails.
|
| Fails -- | @# CHECK-FAILS@ fails unless translation fails.
|
||||||
deriving (Eq, Show)
|
deriving (Eq, Show)
|
||||||
|
|
||||||
describe :: Directive -> String
|
-- | Extract all directives from a file.
|
||||||
describe Fails = "<expect failure>"
|
readDirectivesFromFile :: Path.Class.AbsRel ar => Path.File ar -> IO [Directive]
|
||||||
describe (Tree t) = Core.Pretty.showCore t
|
readDirectivesFromFile
|
||||||
describe (JQ b) = ByteString.unpack b
|
= runResourceT
|
||||||
|
. Stream.toList_
|
||||||
|
. Stream.mapM (either perish pure . parseDirective)
|
||||||
|
. Stream.takeWhile isComment
|
||||||
|
. Stream.mapped ByteStream.toStrict
|
||||||
|
. ByteStream.lines
|
||||||
|
. ByteStream.readFile @(ResourceT IO)
|
||||||
|
. Path.toString
|
||||||
|
where
|
||||||
|
perish s = fail ("Directive parsing error: " <> s)
|
||||||
|
isComment = (== Just '#') . fmap fst . ByteString.uncons
|
||||||
|
|
||||||
fails :: Trifecta.Parser Directive
|
|
||||||
|
describe :: Directive -> String
|
||||||
|
describe Fails = "<expect failure>"
|
||||||
|
describe (Tree t) = Core.Pretty.showCore t
|
||||||
|
describe (JQ b) = ByteString.unpack b
|
||||||
|
describe (Result t e) = T.unpack t <> ": " <> show e
|
||||||
|
|
||||||
|
fails :: CharParsing m => m Directive
|
||||||
fails = Fails <$ Trifecta.string "# CHECK-FAILS"
|
fails = Fails <$ Trifecta.string "# CHECK-FAILS"
|
||||||
|
|
||||||
jq :: Trifecta.Parser Directive
|
jq :: (Monad m, CharParsing m) => m Directive
|
||||||
jq = do
|
jq = do
|
||||||
void $ Trifecta.string "# CHECK-JQ: "
|
void $ Trifecta.string "# CHECK-JQ: "
|
||||||
JQ . ByteString.pack <$> many (Trifecta.noneOf "\n")
|
JQ . ByteString.pack <$> many (Trifecta.noneOf "\n")
|
||||||
|
|
||||||
tree :: Trifecta.Parser Directive
|
tree :: (Monad m, TokenParsing m) => m Directive
|
||||||
tree = do
|
tree = do
|
||||||
void $ Trifecta.string "# CHECK-TREE: "
|
void $ Trifecta.string "# CHECK-TREE: "
|
||||||
Tree <$> Core.Parser.core
|
Tree <$> Core.Parser.core
|
||||||
|
|
||||||
directive :: Trifecta.Parser Directive
|
result :: (Monad m, TokenParsing m) => m Directive
|
||||||
directive = Trifecta.choice [ fails, jq, tree ]
|
result = do
|
||||||
|
void $ Trifecta.string "# CHECK-RESULT "
|
||||||
|
key <- Trifecta.ident Style.haskellIdents
|
||||||
|
void $ Trifecta.symbolic ':'
|
||||||
|
Result key <$> concrete
|
||||||
|
|
||||||
|
concrete :: TokenParsing m => m (Concrete term Name)
|
||||||
|
concrete = Trifecta.choice
|
||||||
|
[ String <$> Trifecta.stringLiteral
|
||||||
|
, Bool True <$ Trifecta.symbol "#true"
|
||||||
|
, Bool False <$ Trifecta.symbol "#false"
|
||||||
|
, Unit <$ Trifecta.symbol "#unit"
|
||||||
|
]
|
||||||
|
|
||||||
|
directive :: (Monad m, TokenParsing m) => m Directive
|
||||||
|
directive = Trifecta.choice [ fails, result, jq, tree ]
|
||||||
|
|
||||||
parseDirective :: ByteString -> Either String Directive
|
parseDirective :: ByteString -> Either String Directive
|
||||||
parseDirective = Trifecta.foldResult (Left . show) Right
|
parseDirective = Trifecta.foldResult (Left . show) Right
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
{-# LANGUAGE DeriveAnyClass, DerivingStrategies, GeneralizedNewtypeDeriving, OverloadedStrings, TypeApplications, TypeOperators, ScopedTypeVariables #-}
|
{-# LANGUAGE OverloadedStrings, ScopedTypeVariables, TypeApplications, TypeOperators #-}
|
||||||
|
|
||||||
module Main (main) where
|
module Main (main) where
|
||||||
|
|
||||||
|
import Analysis.Concrete (Concrete)
|
||||||
|
import qualified Analysis.Concrete as Concrete
|
||||||
import Analysis.File
|
import Analysis.File
|
||||||
import Analysis.ScopeGraph
|
import Analysis.ScopeGraph
|
||||||
import Control.Effect
|
import Control.Effect
|
||||||
@ -10,12 +12,11 @@ import Control.Effect.Reader
|
|||||||
import Control.Monad hiding (fail)
|
import Control.Monad hiding (fail)
|
||||||
import Control.Monad.Catch
|
import Control.Monad.Catch
|
||||||
import Control.Monad.IO.Class
|
import Control.Monad.IO.Class
|
||||||
import Control.Monad.Trans.Resource (ResourceT, runResourceT)
|
|
||||||
import Core.Core
|
import Core.Core
|
||||||
import qualified Core.Parser
|
|
||||||
import Core.Pretty
|
|
||||||
import qualified Core.Eval as Eval
|
import qualified Core.Eval as Eval
|
||||||
import Core.Name
|
import Core.Name
|
||||||
|
import qualified Core.Parser
|
||||||
|
import Core.Pretty
|
||||||
import qualified Data.Aeson as Aeson
|
import qualified Data.Aeson as Aeson
|
||||||
import qualified Data.Aeson.Encode.Pretty as Aeson
|
import qualified Data.Aeson.Encode.Pretty as Aeson
|
||||||
import qualified Data.ByteString.Char8 as ByteString
|
import qualified Data.ByteString.Char8 as ByteString
|
||||||
@ -23,21 +24,23 @@ import qualified Data.ByteString.Lazy.Char8 as ByteString.Lazy
|
|||||||
import qualified Data.ByteString.Streaming.Char8 as ByteStream
|
import qualified Data.ByteString.Streaming.Char8 as ByteStream
|
||||||
import Data.Foldable
|
import Data.Foldable
|
||||||
import Data.Function
|
import Data.Function
|
||||||
|
import qualified Data.IntMap as IntMap
|
||||||
import Data.List (sort)
|
import Data.List (sort)
|
||||||
|
import qualified Data.Map as Map
|
||||||
import Data.Maybe
|
import Data.Maybe
|
||||||
|
import Data.Text (Text)
|
||||||
import GHC.Stack
|
import GHC.Stack
|
||||||
import qualified Language.Python.Core as Py
|
import qualified Language.Python.Core as Py
|
||||||
import Prelude hiding (fail)
|
import Prelude hiding (fail)
|
||||||
import Source.Span
|
import Source.Span
|
||||||
import Streaming
|
import Streaming
|
||||||
import qualified Streaming.Prelude as Stream
|
|
||||||
import qualified Streaming.Process
|
import qualified Streaming.Process
|
||||||
import Syntax.Term
|
import Syntax.Term
|
||||||
import System.Directory
|
import System.Directory
|
||||||
import System.Exit
|
import System.Exit
|
||||||
|
import System.Path ((</>))
|
||||||
import qualified System.Path as Path
|
import qualified System.Path as Path
|
||||||
import qualified System.Path.Directory as Path
|
import qualified System.Path.Directory as Path
|
||||||
import System.Path ((</>))
|
|
||||||
import Text.Show.Pretty (ppShow)
|
import Text.Show.Pretty (ppShow)
|
||||||
import qualified Text.Trifecta as Trifecta
|
import qualified Text.Trifecta as Trifecta
|
||||||
import qualified TreeSitter.Python as TSP
|
import qualified TreeSitter.Python as TSP
|
||||||
@ -49,15 +52,17 @@ import qualified Test.Tasty.HUnit as HUnit
|
|||||||
import qualified Directive
|
import qualified Directive
|
||||||
import Instances ()
|
import Instances ()
|
||||||
|
|
||||||
|
parsePrelude :: IO (Term (Ann Span :+: Core) Name)
|
||||||
|
parsePrelude = do
|
||||||
|
preludesrc <- ByteString.readFile "semantic-python/src/Prelude.score"
|
||||||
|
let ePrelude = Trifecta.parseByteString (Core.Parser.core <* Trifecta.eof) mempty preludesrc
|
||||||
|
case Trifecta.foldResult (Left . show) Right ePrelude of
|
||||||
|
Right r -> pure r
|
||||||
|
Left s -> HUnit.assertFailure ("Couldn't parse prelude: " <> s)
|
||||||
|
|
||||||
assertJQExpressionSucceeds :: Show a => Directive.Directive -> a -> Term (Ann Span :+: Core) Name -> HUnit.Assertion
|
assertJQExpressionSucceeds :: Show a => Directive.Directive -> a -> Term (Ann Span :+: Core) Name -> HUnit.Assertion
|
||||||
assertJQExpressionSucceeds directive tree core = do
|
assertJQExpressionSucceeds directive tree core = do
|
||||||
preludesrc <- ByteString.readFile "semantic-python/src/Prelude.score"
|
prelude <- parsePrelude
|
||||||
let ePrelude = Trifecta.parseByteString (Core.Parser.core <* Trifecta.eof) mempty preludesrc
|
|
||||||
prelude <- case Trifecta.foldResult (Left . show) Right ePrelude of
|
|
||||||
Right r -> pure r
|
|
||||||
Left s -> HUnit.assertFailure ("Couldn't parse prelude: " <> s)
|
|
||||||
|
|
||||||
let allTogether = (named' "__semantic_prelude" :<- prelude) >>>= core
|
let allTogether = (named' "__semantic_prelude" :<- prelude) >>>= core
|
||||||
|
|
||||||
bod <- case scopeGraph Eval.eval [File (Path.absRel "<interactive>") (Span (Pos 1 1) (Pos 1 1)) allTogether] of
|
bod <- case scopeGraph Eval.eval [File (Path.absRel "<interactive>") (Span (Pos 1 1) (Pos 1 1)) allTogether] of
|
||||||
@ -67,64 +72,73 @@ assertJQExpressionSucceeds directive tree core = do
|
|||||||
]
|
]
|
||||||
other -> HUnit.assertFailure ("Couldn't run scope dumping mechanism: " <> showCore (stripAnnotations allTogether) <> "\n" <> show other)
|
other -> HUnit.assertFailure ("Couldn't run scope dumping mechanism: " <> showCore (stripAnnotations allTogether) <> "\n" <> show other)
|
||||||
|
|
||||||
let ignore = ByteStream.effects . hoist ByteStream.effects
|
let ignore = ByteStream.effects . hoist ByteStream.effects
|
||||||
sgJSON = ByteStream.fromLazy $ Aeson.encode bod
|
sgJSON = ByteStream.fromLazy $ Aeson.encode bod
|
||||||
jqPipeline = Streaming.Process.withStreamingProcess (Directive.toProcess directive) sgJSON ignore
|
jqPipeline = Streaming.Process.withStreamingProcess (Directive.toProcess directive) sgJSON ignore
|
||||||
errorMsg = "jq(1) returned non-zero exit code"
|
errorMsg = "jq(1) returned non-zero exit code"
|
||||||
dirMsg = "jq expression: " <> show directive
|
dirMsg = "jq expression: " <> show directive
|
||||||
jsonMsg = "JSON value: " <> ByteString.Lazy.unpack (Aeson.encodePretty bod)
|
jsonMsg = "JSON value: " <> ByteString.Lazy.unpack (Aeson.encodePretty bod)
|
||||||
astMsg = "AST (pretty): " <> ppShow tree
|
astMsg = "AST (pretty): " <> ppShow tree
|
||||||
treeMsg = "Core expr (pretty): " <> showCore (stripAnnotations core)
|
treeMsg = "Core expr (pretty): " <> showCore (stripAnnotations core)
|
||||||
treeMsg' = "Core expr (Show): " <> ppShow (stripAnnotations core)
|
treeMsg' = "Core expr (Show): " <> ppShow (stripAnnotations core)
|
||||||
|
|
||||||
|
|
||||||
catch @_ @Streaming.Process.ProcessExitedUnsuccessfully jqPipeline $ \err -> do
|
catch @_ @Streaming.Process.ProcessExitedUnsuccessfully jqPipeline $ \err -> do
|
||||||
HUnit.assertFailure (unlines [errorMsg, dirMsg, jsonMsg, astMsg, treeMsg, treeMsg', show err])
|
HUnit.assertFailure (unlines [errorMsg, dirMsg, jsonMsg, astMsg, treeMsg, treeMsg', show err])
|
||||||
|
|
||||||
fixtureTestTreeForFile :: HasCallStack => Path.RelFile -> Tasty.TestTree
|
-- handles CHECK-RESULT directives
|
||||||
fixtureTestTreeForFile fp = HUnit.testCaseSteps (Path.toString fp) $ \step -> withFrozenCallStack $ do
|
assertEvaluatesTo :: Term (Ann Span :+: Core) Name -> Text -> Concrete (Term (Ann Span :+: Core)) Name -> HUnit.Assertion
|
||||||
|
assertEvaluatesTo core k val = do
|
||||||
|
prelude <- parsePrelude
|
||||||
|
let allTogether = (named' "__semantic_prelude" :<- prelude) >>>= core
|
||||||
|
let filius = [File (Path.absRel "<interactive>") (Span (Pos 1 1) (Pos 1 1)) allTogether]
|
||||||
|
|
||||||
|
(heap, env) <- case Concrete.concrete Eval.eval filius of
|
||||||
|
(heap, [File _ _ (Right (Concrete.Record env))]) ->
|
||||||
|
pure (heap, env)
|
||||||
|
(_, [File _ _ (Left (_, span, err))]) ->
|
||||||
|
HUnit.assertFailure ("Failed evaluation (" <> show span <> "): " <> err)
|
||||||
|
(_, files) ->
|
||||||
|
HUnit.assertFailure ("Unexpected number of files: " <> show (length files))
|
||||||
|
|
||||||
|
let found = Map.lookup (Name k) env >>= flip IntMap.lookup heap
|
||||||
|
found HUnit.@?= Just val
|
||||||
|
|
||||||
|
-- handles CHECK-TREE directives
|
||||||
|
assertTreeEqual :: Term Core Name -> Term Core Name -> HUnit.Assertion
|
||||||
|
assertTreeEqual t item = HUnit.assertEqual ("got (pretty)" <> showCore item) t item
|
||||||
|
|
||||||
|
checkPythonFile :: HasCallStack => Path.RelFile -> Tasty.TestTree
|
||||||
|
checkPythonFile fp = HUnit.testCaseSteps (Path.toString fp) $ \step -> withFrozenCallStack $ do
|
||||||
|
-- Extract the directives and the core associated with the provided file
|
||||||
let fullPath = Path.relDir "semantic-python/test/fixtures" </> fp
|
let fullPath = Path.relDir "semantic-python/test/fixtures" </> fp
|
||||||
perish s = liftIO (HUnit.assertFailure ("Directive parsing error: " <> s))
|
directives <- Directive.readDirectivesFromFile fullPath
|
||||||
isComment = (== Just '#') . fmap fst . ByteString.uncons
|
|
||||||
|
|
||||||
|
|
||||||
-- 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)
|
|
||||||
. Stream.takeWhile isComment
|
|
||||||
. Stream.mapped ByteStream.toStrict
|
|
||||||
. ByteStream.lines
|
|
||||||
. ByteStream.readFile @(ResourceT IO)
|
|
||||||
$ Path.toString fullPath
|
|
||||||
|
|
||||||
result <- ByteString.readFile (Path.toString fullPath) >>= TS.parseByteString TSP.tree_sitter_python
|
result <- ByteString.readFile (Path.toString fullPath) >>= TS.parseByteString TSP.tree_sitter_python
|
||||||
|
|
||||||
|
-- Run the compiler
|
||||||
let coreResult = Control.Effect.run
|
let coreResult = Control.Effect.run
|
||||||
. runFail
|
. runFail
|
||||||
. runReader @Py.Bindings mempty
|
. runReader @Py.Bindings mempty
|
||||||
. Py.toplevelCompile
|
. Py.toplevelCompile
|
||||||
<$> result
|
<$> result
|
||||||
|
|
||||||
|
-- Dispatch based on the result-directive pair
|
||||||
for_ directives $ \directive -> do
|
for_ directives $ \directive -> do
|
||||||
step (Directive.describe directive)
|
step (Directive.describe directive)
|
||||||
case (coreResult, directive) of
|
case (coreResult, directive) of
|
||||||
(Right (Left _), Directive.Fails) -> pure ()
|
(Right (Left _), Directive.Fails) -> pure ()
|
||||||
(Left err, _) -> HUnit.assertFailure ("Parsing failed: " <> err)
|
(Left err, _) -> HUnit.assertFailure ("Parsing failed: " <> err)
|
||||||
(Right (Left err), _) -> HUnit.assertFailure ("Compilation failed: " <> err)
|
(Right (Left err), _) -> HUnit.assertFailure ("Compilation failed: " <> err)
|
||||||
(Right (Right _), Directive.Fails) -> HUnit.assertFailure ("Expected translation to fail")
|
(Right (Right _), Directive.Fails) -> HUnit.assertFailure "Expected translation to fail"
|
||||||
(Right (Right item), Directive.JQ _) -> assertJQExpressionSucceeds directive result item
|
(Right (Right item), Directive.Result k v) -> assertEvaluatesTo item k v
|
||||||
(Right (Right item), Directive.Tree t) -> let msg = "got (pretty): " <> showCore item'
|
(Right (Right item), Directive.JQ _) -> assertJQExpressionSucceeds directive result item
|
||||||
item' = stripAnnotations item
|
(Right (Right item), Directive.Tree t) -> assertTreeEqual (stripAnnotations item) t
|
||||||
in HUnit.assertEqual msg t item' where
|
|
||||||
|
|
||||||
milestoneFixtures :: IO Tasty.TestTree
|
milestoneFixtures :: IO Tasty.TestTree
|
||||||
milestoneFixtures = do
|
milestoneFixtures = buildTests <$> readFiles
|
||||||
files <- liftIO (Path.filesInDir (Path.relDir "semantic-python/test/fixtures"))
|
where
|
||||||
let pythons = sort (filter (Path.hasExtension ".py") files)
|
readFiles = liftIO . Path.filesInDir . Path.relDir $ "semantic-python/test/fixtures"
|
||||||
pure $ Tasty.testGroup "Translation" (fmap fixtureTestTreeForFile pythons)
|
buildTests = Tasty.testGroup "Python" . fmap checkPythonFile . sort . filter (Path.hasExtension ".py")
|
||||||
|
|
||||||
main :: IO ()
|
main :: IO ()
|
||||||
main = do
|
main = do
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
# CHECK-JQ: .scope | has("hello") and has("goodbye")
|
# CHECK-JQ: .scope | has("hello") and has("goodbye")
|
||||||
# CHECK-TREE: { hello <- #unit; goodbye <- #unit; #record { hello: hello, goodbye: goodbye }}
|
# CHECK-TREE: { hello <- #unit; goodbye <- #unit; #record { hello: hello, goodbye: goodbye }}
|
||||||
|
# CHECK-RESULT hello: #unit
|
||||||
hello = ()
|
hello = ()
|
||||||
goodbye = ()
|
goodbye = ()
|
||||||
|
Loading…
Reference in New Issue
Block a user