packages feed

canontra-0.1.0.0: test/Canontra/CallGraphSpec.hs

{- |
Module      : Canontra.CallGraphSpec
Description : Unit test specification for the static intra-module call graph engine.

Tests call graph edge extraction, caller-callee categorization,
method dispatch analysis, and F_CG fingerprint determinism.
-}
{-# LANGUAGE OverloadedStrings #-}
module Canontra.CallGraphSpec (spec) where

import qualified Data.Text as T
import Test.Hspec

import Canontra.Analysis.CallGraph
import Canontra.Fingerprint.CallGraph (computeFCG)
import Canontra.Parser.Python (parsePythonSource)

spec :: Spec
spec = do
  describe "Call Graph Analysis" $ do
    it "extracts intra-module caller-callee edges" $ do
      let code = T.unlines
            [ "def helper(x):"
            , "    return x * 2"
            , ""
            , "def main_func():"
            , "    a = helper(10)"
            , "    b = helper(20)"
            , "    return a + b"
            ]
      case parsePythonSource "test.py" code of
        Left err -> expectationFailure (show err)
        Right prog -> do
          let cg = buildCallGraph prog
          CallFunction "main_func" `elem` cgNodes cg `shouldBe` True
          CallFunction "helper" `elem` cgNodes cg `shouldBe` True
          let hasMainToHelper = any (\e -> edgeCaller e == CallFunction "main_func" && edgeCallee e == TargetLocal "helper") (cgEdges cg)
          hasMainToHelper `shouldBe` True

    it "identifies imported module calls" $ do
      let code = T.unlines
            [ "import math"
            , "from os import path"
            , ""
            , "def compute(x):"
            , "    r = math.sqrt(x)"
            , "    p = path.exists('/tmp')"
            , "    return r"
            ]
      case parsePythonSource "test.py" code of
        Left err -> expectationFailure (show err)
        Right prog -> do
          let cg = buildCallGraph prog
          let hasMathSqrt = any (\e -> edgeCallee e == TargetImported "math" "sqrt") (cgEdges cg)
          hasMathSqrt `shouldBe` True

    it "produces deterministic F_CG call graph fingerprints" $ do
      let code = T.unlines
            [ "def alpha(): beta()"
            , "def beta(): gamma()"
            , "def gamma(): pass"
            ]
      case (parsePythonSource "1.py" code, parsePythonSource "2.py" code) of
        (Right p1, Right p2) -> do
          computeFCG p1 `shouldBe` computeFCG p2
        _ -> expectationFailure "Parse failed"