{-# LANGUAGE DataKinds #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TypeApplications #-} module Graphing.Calls.Spec ( spec ) where import Prelude hiding (readFile) import SpecHelpers import Algebra.Graph import qualified Analysis.File as File import Control.Effect.Parse import Data.Graph.Algebraic (Graph (..), topologicalSort) import Data.Graph.ControlFlowVertex import qualified Data.Language as Language import Semantic.Graph import qualified System.Path as Path callGraphPythonProject :: Path.RelFile -> IO (Semantic.Graph.Graph ControlFlowVertex) callGraphPythonProject path = runTaskOrDie $ do let proxy = Proxy @'Language.Python lang = Language.Python SomeParser parser <- pure . fromJust $! parserForLanguage analysisParsers Language.Python blob <- readBlobFromFile' (File.fromPath path) package <- fmap snd <$> parsePackage parser (Project (Path.toString (Path.takeDirectory path)) [blob] lang []) modules <- topologicalSort <$> runImportGraphToModules proxy package runCallGraph proxy False modules package spec :: Spec spec = describe "call graphing" $ do let needs r v = unGraph r `shouldSatisfy` hasVertex v it "should work for a simple example" $ do res <- callGraphPythonProject (Path.relFile "test/fixtures/python/graphing/simple/simple.py") res `needs` Variable "magnus" "simple.py" (Span (Pos 4 1) (Pos 4 7)) it "should evaluate both sides of an if-statement" $ do res <- callGraphPythonProject (Path.relFile "test/fixtures/python/graphing/conditional/conditional.py") res `needs` Variable "merle" "conditional.py" (Span (Pos 5 5) (Pos 5 10)) res `needs` Variable "taako" "conditional.py" (Span (Pos 8 5) (Pos 8 10)) it "should continue even when a type error is encountered" $ do res <- callGraphPythonProject (Path.relFile "test/fixtures/python/graphing/typeerror/typeerror.py") res `needs` Variable "lup" "typeerror.py" (Span (Pos 5 1) (Pos 5 4)) it "should continue when an unbound variable is encountered" $ do res <- callGraphPythonProject (Path.relFile "test/fixtures/python/graphing/unbound/unbound.py") res `needs` Variable "lucretia" "unbound.py" (Span (Pos 5 1) (Pos 5 9))