canontra-0.1.0.0: test/Canontra/OutlineSpec.hs
{-# LANGUAGE OverloadedStrings #-}
{- |
Module : Canontra.OutlineSpec
Description : Test suite for two-phase selective Outline parsing and accelerated F2/F3 fingerprinting.
-}
module Canontra.OutlineSpec (spec) where
import Test.Hspec
import Canontra.Fingerprint.Declaration (computeF2)
import Canontra.Fingerprint.Dependency (computeF3)
import Canontra.Parser.Ingest (IngestedOutline (..), ingestOutlineSource)
import Canontra.Parser.Outline
import Canontra.Parser.Polyglot (parsePolyglotSource)
import Canontra.Types
spec :: Spec
spec = do
describe "Selective Outline Mode" $ do
describe "Python Outline Ingestion" $ do
it "extracts Python declarations and imports with empty bodies" $ do
let pyCode = "import os\nfrom sys import path\n\n@decorator\ndef calculate(x: int, y: int = 10) -> int:\n total = x + y\n return total * 2\n"
case parseOutlinePython "calc.py" pyCode of
Left err -> expectationFailure (show err)
Right outline -> do
outPath outline `shouldBe` "calc.py"
outLanguage outline `shouldBe` "python"
length (outImports outline) `shouldBe` 2
length (outDeclarations outline) `shouldBe` 1
it "produces identical F2 & F3 fingerprints between full AST and Outline mode" $ do
let pyCode = "import math\n\ndef compute(a: float, b: float) -> float:\n # internal logic\n temp = a * 2.0\n return temp + b\n"
case (parsePolyglotSource "math.py" pyCode, parseOutlineSource "math.py" pyCode) of
(Right fullProg, Right outline) -> do
let f2Full = computeF2 fullProg
let f2Outline = computeF2Outline outline
unFingerprint f2Full `shouldBe` unFingerprint f2Outline
let f3Full = computeF3 fullProg
let f3Outline = computeF3Outline outline
unFingerprint f3Full `shouldBe` unFingerprint f3Outline
_ -> expectationFailure "Parse failed"
it "maintains F2/F3 invariance when internal function bodies change" $ do
let code1 = "import os\ndef run(x: int) -> int:\n return x + 1\n"
let code2 = "import os\ndef run(x: int) -> int:\n # completely different body\n temp = x * 100\n if temp > 0:\n return temp\n return 0\n"
case (parseOutlineSource "mod.py" code1, parseOutlineSource "mod.py" code2) of
(Right out1, Right out2) -> do
unFingerprint (computeF2Outline out1) `shouldBe` unFingerprint (computeF2Outline out2)
unFingerprint (computeF3Outline out1) `shouldBe` unFingerprint (computeF3Outline out2)
_ -> expectationFailure "Outline parse failed"
describe "TypeScript & JavaScript Outline Ingestion" $ do
it "extracts TS interfaces, classes, and imports" $ do
let tsCode = "import { User } from './models';\nexport interface UserService {\n findUser(id: string): User;\n}\nexport class ServiceImpl implements UserService {\n async findUser(id: string) {\n return fetch(id);\n }\n}"
case parseOutlineJS "service.ts" tsCode of
Left err -> expectationFailure (show err)
Right outline -> do
outLanguage outline `shouldBe` "typescript"
length (outDeclarations outline) `shouldSatisfy` (>= 2)
length (outImports outline) `shouldBe` 1
describe "Go Outline Ingestion" $ do
it "extracts Go receiver methods, structs, and imports" $ do
let goCode = "package server\nimport \"net/http\"\ntype Handler struct {\n port int\n}\nfunc (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {\n w.Write([]byte(\"ok\"))\n}"
case parseOutlineGo "server.go" goCode of
Left err -> expectationFailure (show err)
Right outline -> do
outLanguage outline `shouldBe` "go"
length (outImports outline) `shouldBe` 1
length (outDeclarations outline) `shouldSatisfy` (>= 2)
describe "Rust Outline Ingestion" $ do
it "extracts Rust structs, traits, and impl blocks" $ do
let rsCode = "use std::sync::Arc;\npub struct Config {\n workers: usize,\n}\npub trait Runner {\n fn start(&self) -> bool;\n}\nimpl Runner for Config {\n fn start(&self) -> bool {\n println!(\"running\");\n true\n }\n}"
case parseOutlineRust "app.rs" rsCode of
Left err -> expectationFailure (show err)
Right outline -> do
outLanguage outline `shouldBe` "rust"
length (outImports outline) `shouldBe` 1
length (outDeclarations outline) `shouldSatisfy` (>= 3)
describe "IngestedOutline Pipeline" $ do
it "ingests in-memory source directly into IngestedOutline" $ do
let code = "def ping():\n return 'pong'\n"
let ingested = ingestOutlineSource "ping.py" "def ping(): return 'pong'" code
ioLanguage ingested `shouldBe` LangPython
case ioOutline ingested of
Right out -> length (outDeclarations out) `shouldBe` 1
Left err -> expectationFailure (show err)