packages feed

canontra-0.1.0.0: test/Canontra/DFGSpec.hs

{-# LANGUAGE OverloadedStrings #-}
module Canontra.DFGSpec (spec) where

import Test.Hspec

import Canontra.Analysis.DFG
import Canontra.Fingerprint.DataFlow (computeFDF)
import Canontra.Parser.Python (parsePythonSource)
import Canontra.Types

spec :: Spec
spec = do
  describe "Data-Flow Graph Analysis" $ do
    it "extracts parameter definitions and variable assignment flows" $ do
      let pyCode = "def process(x, y):\n    z = x + y\n    return z\n"
      case parsePythonSource "test.py" pyCode of
        Left err -> expectationFailure (show err)
        Right prog -> do
          let dfgs = buildDFGs prog
          length dfgs `shouldBe` 1
          let dfg = head dfgs
          dfgFunction dfg `shouldBe` "process"
          null (dfgNodes dfg) `shouldBe` False
          length (dfgEdges dfg) `shouldSatisfy` (>= 2)

    it "tracks Def-Use chains across sequential operations" $ do
      let pyCode = "def transform(a):\n    b = a * 2\n    c = b + 10\n    return c\n"
      case parsePythonSource "test.py" pyCode of
        Left err -> expectationFailure (show err)
        Right prog -> do
          let dfgs = buildDFGs prog
          length dfgs `shouldBe` 1
          let dfg = head dfgs
          length (dfgEdges dfg) `shouldSatisfy` (>= 3)

    it "produces deterministic F_DF data-flow fingerprints" $ do
      let pyCode1 = "def calc(a, b):\n    return a + b\n"
      let pyCode2 = "# Different comment\ndef calc(a, b):\n    '''Docstring'''\n    return a + b\n"
      case (parsePythonSource "t1.py" pyCode1, parsePythonSource "t2.py" pyCode2) of
        (Right p1, Right p2) -> do
          let fdf1 = computeFDF p1
          let fdf2 = computeFDF p2
          unFingerprint fdf1 `shouldBe` unFingerprint fdf2
        _ -> expectationFailure "Parse failed"

    it "inserts SSA phi-nodes at branch convergence points" $ do
      let pyCode = "def branch_val(cond, x, y):\n    if cond:\n        res = x * 2\n    else:\n        res = y * 3\n    return res\n"
      case parsePythonSource "branch.py" pyCode of
        Left err -> expectationFailure (show err)
        Right prog -> case buildDFGs prog of
          [dfg] -> do
            -- Must contain a DefPhi node merging branch definitions
            let phiNodes = [n | n <- dfgNodes dfg, case dfgKind n of DefPhi _ -> True; _ -> False]
            length phiNodes `shouldSatisfy` (>= 1)
            case head phiNodes of
              DFGNode _ (DefPhi inDefs) _ -> length inDefs `shouldSatisfy` (>= 2)
              _                          -> expectationFailure "Expected DefPhi"
          _ -> expectationFailure "Expected 1 DFG"

    it "tracks block-scoped variable shadowing and resolves active definitions across branches" $ do
      let pyCode = "def shadow_test(x):\n    val = 1\n    if x > 0:\n        val = 10\n    return val\n"
      case parsePythonSource "shadow.py" pyCode of
        Left err -> expectationFailure (show err)
        Right prog -> case buildDFGs prog of
          [dfg] -> do
            -- Contains phi node merging val=1 and val=10
            let phiNodes = [n | n <- dfgNodes dfg, case dfgKind n of DefPhi _ -> True; _ -> False]
            length phiNodes `shouldSatisfy` (>= 1)
          _ -> expectationFailure "Expected 1 DFG"