packages feed

canontra-0.1.0.0: test/Canontra/BugfixSpec.hs

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

import Control.DeepSeq (rnf)
import Test.Hspec

import Canontra.Analysis.CFG (ControlFlowGraph (..), buildCFGs)
import Canontra.Analysis.DFG (DFGNode (..), DataFlowGraph (..), DefUseKind (..), buildDFGs)
import Canontra.Analysis.Scope (SymbolBinding (..), allBindings, analyzeProgramScope)
import Canontra.Cache.Inode (FileMetadata (..))
import Canontra.Cache.MerkleCache (emptyCache, insertCache, lookupCache)
import Canontra.Fingerprint.Bundle (computeBundleFromSource)
import Canontra.Fingerprint.Structural (computeF1)
import Canontra.Normalize.Normalize (normalizeProgram)
import Canontra.Parser.Go (parseGoSource)
import Canontra.Parser.JS (parseJSSource)
import Canontra.Parser.Polyglot (parsePolyglotSource)
import Canontra.Parser.Python (parsePythonSource)
import Canontra.Parser.Rust (parseRustSource)
import Canontra.Repository.Repository (computeRepositoryFingerprint)
import Canontra.Types

spec :: Spec
spec = do
  describe "Post-v0.0.7 Bug Remediation & Hardening Matrix" $ do

    it "BUG-07: Disambiguates TypeScript generic arrow functions from JSX tags" $ do
      let tsCode = "const identity = <T>(x: T): T => x;\nconst pair = <T, U>(a: T, b: U) => { return a; };"
      case parsePolyglotSource "generic.ts" tsCode of
        Left err -> expectationFailure ("TS generic arrow parse failed: " ++ show err)
        Right _ -> do
          let fps = computeBundleFromSource "generic.ts" tsCode
          case fps of
            Left err -> expectationFailure (show err)
            Right b  -> unFingerprint (f1Structural b) `shouldNotBe` ""

    it "BUG-08: Parses Rust macro calls with explicit lifetime parameters" $ do
      let rsCode = "pub fn query() {\n  query_as!(User, 'a, \"SELECT * FROM users\");\n  custom_macro!('static, String);\n}"
      case parseRustSource "db.rs" rsCode of
        Left err -> expectationFailure ("Rust macro lifetime parse failed: " ++ show err)
        Right _  -> pure ()

    it "BUG-09: Unrolls Go type switches with multi-type cases into distinct CFG branches" $ do
      let goCode = "package main\nfunc Check(val interface{}) {\n  switch val.(type) {\n  case int, int64, float64:\n    print(1)\n  case string:\n    print(2)\n  default:\n    print(0)\n  }\n}"
      case parseGoSource "typeswitch.go" goCode of
        Left err -> expectationFailure ("Go type switch parse failed: " ++ show err)
        Right prog -> do
          let cfgs = buildCFGs prog
          length cfgs `shouldBe` 1
          let cfg = head cfgs
          length (cfgEdges cfg) `shouldSatisfy` (>= 5)

    it "BUG-10: Cross-platform POSIX path normalization produces identical Merkle roots" $ do
      let b1 = FingerprintBundle (Fingerprint "s1") (Fingerprint "str1") (Fingerprint "d1") (Fingerprint "dp1") (Fingerprint "cg1") (Fingerprint "cf1") (Fingerprint "df1") (Fingerprint "t1") (Fingerprint "c1")
          eWin   = [FileEntry "src\\core\\main.py" b1, FileEntry "pkg\\util\\math.go" b1]
          ePosix = [FileEntry "src/core/main.py" b1, FileEntry "pkg/util/math.go" b1]
          fpWin   = computeRepositoryFingerprint eWin
          fpPosix = computeRepositoryFingerprint ePosix
      fpWin `shouldBe` fpPosix

    it "BUG-11: Deep strictness evaluation prevents memory thunk accumulation" $ do
      let tsCode = "function sum(a: number, b: number): number { return a + b; }"
      case parseJSSource "math.ts" tsCode of
        Left err -> expectationFailure (show err)
        Right prog -> do
          let scopes = analyzeProgramScope prog
          rnf scopes `shouldBe` ()

    it "BUG-12: Parses Python 3.12 PEP 701 nested f-strings with quote reuse" $ do
      let pyCode = "msg = f\"result: {f'inner: {value}'}\""
      case parsePythonSource "fstring.py" pyCode of
        Left err -> expectationFailure ("PEP 701 fstring parse failed: " ++ show err)
        Right prog -> do
          let f1 = computeF1 prog
          unFingerprint f1 `shouldNotBe` ""

    it "BUG-13: Ingests PEP 634 match/case with wildcard pattern and guards" $ do
      let pyCode = "def route(action):\n    match action:\n        case [\"get\", url] if len(url) > 0:\n            return 200\n        case _:\n            return 404\n"
      case parsePythonSource "match.py" pyCode of
        Left err -> expectationFailure ("PEP 634 match parse failed: " ++ show err)
        Right prog -> do
          let cfgs = buildCFGs prog
          length cfgs `shouldBe` 1

    it "BUG-14: Hoists PEP 572 walrus operator in comprehension to function scope" $ do
      let pyCode = "def process(data):\n    results = [y for x in data if (y := transform(x)) > 0]\n    return y\n"
      case parsePythonSource "walrus.py" pyCode of
        Left err -> expectationFailure ("Walrus scope parse failed: " ++ show err)
        Right prog -> do
          let scopes = analyzeProgramScope prog
          any (\b -> symName b == "y") (concatMap allBindings scopes) `shouldBe` True

    it "BUG-15: Disambiguates regex literal from division following return keyword in JS" $ do
      let jsCode = "function test() {\n    return /pattern[0-9]+/i;\n}"
      case parseJSSource "regex.js" jsCode of
        Left err -> expectationFailure ("Regex literal parse failed: " ++ show err)
        Right prog -> do
          let f1 = computeF1 prog
          unFingerprint f1 `shouldNotBe` ""

    it "BUG-16: Parses Go 1.18+ generic type parameters on struct declarations" $ do
      let goCode = "package generic\ntype Pair[T any, U comparable] struct {\n    first T\n    second U\n}"
      case parseGoSource "pair.go" goCode of
        Left err -> expectationFailure ("Go generic struct parse failed: " ++ show err)
        Right prog -> do
          let f1 = computeF1 prog
          unFingerprint f1 `shouldNotBe` ""

    it "BUG-17: Ingests Rust macro calls with nested bracket and brace delimiters" $ do
      let rsCode = "fn build_table() {\n    let table = matrix![[1, 2, { 3 + 4 }], [5, 6, 7]];\n}"
      case parseRustSource "matrix.rs" rsCode of
        Left err -> expectationFailure ("Rust nested macro parse failed: " ++ show err)
        Right prog -> do
          let f1 = computeF1 prog
          unFingerprint f1 `shouldNotBe` ""

    it "BUG-18: Decomposes short-circuit boolean conditions into decision CFG blocks" $ do
      let pyCode = "def validate(a, b, c):\n    if a > 0 and (b < 10 or c == 0):\n        return True\n    return False\n"
      case parsePythonSource "cond.py" pyCode of
        Left err -> expectationFailure ("Condition parse failed: " ++ show err)
        Right prog -> do
          let cfgs = buildCFGs prog
          length cfgs `shouldBe` 1
          let cfg = head cfgs
          length (cfgBlocks cfg) `shouldSatisfy` (>= 4)

    it "BUG-19: Inserts dominance-frontier SSA phi-nodes at branch convergence" $ do
      let pyCode = "def compute(flag, x):\n    if flag:\n        y = x * 2\n    else:\n        y = x + 10\n    return y\n"
      case parsePythonSource "ssa.py" pyCode of
        Left err -> expectationFailure ("SSA parse failed: " ++ show err)
        Right prog -> do
          let dfgs = buildDFGs prog
          length dfgs `shouldBe` 1
          let dfg = head dfgs
          let hasPhi = any (\node -> case dfgKind node of DefPhi _ -> True; _ -> False) (dfgNodes dfg)
          hasPhi `shouldBe` True

    it "BUG-20: Normalizer v4 canonically sorts pure functions while preserving reflection docstrings" $ do
      let py1 = "def beta():\n    \"\"\":preserve: Critical reflection API doc\"\"\"\n    return 2\ndef alpha():\n    return 1\n"
          py2 = "def alpha():\n    return 1\ndef beta():\n    \"\"\":preserve: Critical reflection API doc\"\"\"\n    return 2\n"
      case (parsePythonSource "m1.py" py1, parsePythonSource "m2.py" py2) of
        (Right p1, Right p2) -> do
          let n1 = normalizeProgram p1
              n2 = normalizeProgram p2
          computeF1 n1 `shouldBe` computeF1 n2
        _ -> expectationFailure "Python normalization parse failed"

    it "BUG-21: Case-folded Windows and POSIX paths resolve to identical cache entry" $ do
      let p = "src/core/engine.py"
          meta = FileMetadata p 500 1700000000
          bundle = FingerprintBundle (Fingerprint "s") (Fingerprint "str") (Fingerprint "d") (Fingerprint "dp") (Fingerprint "cg") (Fingerprint "cf") (Fingerprint "df") (Fingerprint "") (Fingerprint "c")
          cache = insertCache p meta bundle emptyCache
      lookupCache "src\\Core\\Engine.py" meta cache `shouldBe` Just bundle
      lookupCache "SRC/CORE/ENGINE.PY" meta cache `shouldBe` Just bundle