diff options
| author | adelon <22380201+adelon@users.noreply.github.com> | 2026-08-01 15:43:14 +0200 |
|---|---|---|
| committer | adelon <22380201+adelon@users.noreply.github.com> | 2026-08-01 15:43:14 +0200 |
| commit | 40b2f57c06e5e15f02c769a38bf1c473e8f2e794 (patch) | |
| tree | eb499c896c33dcad6436b9bf9354149f6a3a39bf /source/Test | |
| parent | b06db93d3c01e73e3e5f027881eed42ad41e6d22 (diff) | |
Reuse exact parsed module artifacts
Diffstat (limited to 'source/Test')
| -rw-r--r-- | source/Test/Unit/Source.hs | 206 |
1 files changed, 206 insertions, 0 deletions
diff --git a/source/Test/Unit/Source.hs b/source/Test/Unit/Source.hs index f2bdd22..862ff87 100644 --- a/source/Test/Unit/Source.hs +++ b/source/Test/Unit/Source.hs @@ -11,6 +11,7 @@ import Checking.Backend.Reconstruction qualified as Reconstruction import Checking.Core qualified as Core import Checking.Facts qualified as Facts import Checking.Foundation qualified as Foundation +import Checking.Identity qualified as Identity import Checking.Kernel.Derivation qualified as Derivation import Checking.Legacy qualified as Legacy import Checking.Obligation qualified as Obligation @@ -18,10 +19,12 @@ import Checking.Transition qualified as Transition import Felix.Cache.Codec qualified as Cache import Felix.Module qualified as Module import Felix.Parse qualified as Parse +import Felix.Parsed.Identity qualified as ParsedIdentity import Felix.Parsed.Payload qualified as Parsed import Felix.Source import Felix.Source.Content qualified as Content import Felix.Source.Graph +import Felix.Store qualified as Store import Meaning qualified import Provers qualified import Report.Location @@ -56,6 +59,7 @@ import Data.Set qualified as Set import Data.Text qualified as Text import Data.Vector qualified as Vector import Data.Word (Word8, Word16) +import Database.SQLite.Simple qualified as SQLite import System.Directory qualified as Directory import System.FilePath.Posix qualified as Posix import System.Posix.Files qualified as PosixFiles @@ -100,6 +104,10 @@ unitTests = testGroup "Source resolution" identifiesOwnerIndependentParsedModules , testCase "keys effective direct syntax inputs" keysEffectiveDirectSyntaxInputs + , testCase "reuses exact parsed syntax on a warm pass" + reusesExactParsedSyntax + , testCase "rejects a corrupted cached declaration anchor" + rejectsCorruptedCachedDeclarationAnchor , testCase "parses source-local blocks in graph order" parsesSourceGraph , testCase "assigns and reserves dense legacy module positions" reservesLegacyModulePositions @@ -820,6 +828,204 @@ keysEffectiveDirectSyntaxInputs = "effective syntax changes parsed identity" (Parse.parsedModuleId first /= Parse.parsedModuleId second) +reusesExactParsedSyntax :: Assertion +reusesExactParsedSyntax = + withTemporaryDirectory "felix-parsed-warm" \temp -> do + let datatype = unlines + [ "\\begin{datatype}\\label{multi_item}" + , " Define $\\itemkind$ inductively as follows." + , " \\begin{enumerate}" + , " \\item $\\itemzero \\in \\itemkind$." + , " \\item $\\itemsucc{x} \\in \\itemkind$ for $x \\in \\itemkind$." + , " \\end{enumerate}" + , "\\end{datatype}" + ] + writeFile + (temp Posix.</> "entry.tex") + (builtinZeroDefinition "source_zero" <> datatype) + mounts <- oneMount "project" temp + request <- expectRight (searchedRoot "entry.tex") + foundation <- expectRight Foundation.checkedFoundation + store <- openTestStore + (temp Posix.</> "store.sqlite") + (Identity.theoryId foundation) + coldCallbacks <- newIORef (0 :: Int) + cold <- expectParseExecution + =<< Parse.parseSourceWorkspaceMeasuredWithStoreAndSyntaxInputsAndCallback + store mounts request (const []) + (\_source _block -> modifyIORef' coldCallbacks (+ 1)) + warmCallbacks <- newIORef (0 :: Int) + warm <- expectParseExecution + =<< Parse.parseSourceWorkspaceMeasuredWithStoreAndSyntaxInputsAndCallback + store mounts request (const []) + (\_source _block -> modifyIORef' warmCallbacks (+ 1)) + let coldRoot = Parse.parsedWorkspaceRootModule (fst cold) + warmRoot = Parse.parsedWorkspaceRootModule (fst warm) + coldMeasurements = snd cold + warmMeasurements = snd warm + assertEqual "cold parsed misses" 1 + (Parse.parseMeasurementParsedMissCount coldMeasurements) + assertEqual "cold parser tables" 1 + (Parse.parseMeasurementParserTableMaterializationCount + coldMeasurements) + assertEqual "warm parsed hits" 1 + (Parse.parseMeasurementParsedHitCount warmMeasurements) + assertEqual "warm parsed misses" 0 + (Parse.parseMeasurementParsedMissCount warmMeasurements) + assertEqual "warm tokenization" 0 + (Parse.parseMeasurementTokenizationNanoseconds warmMeasurements) + assertEqual "warm scanning" 0 + (Parse.parseMeasurementScanningNanoseconds warmMeasurements) + assertEqual "warm parsing" 0 + (Parse.parseMeasurementParsingNanoseconds warmMeasurements) + assertEqual "warm parser tables" 0 + (Parse.parseMeasurementParserTableMaterializationCount + warmMeasurements) + assertEqual "warm blocks" (Parse.parsedModuleBlocks coldRoot) + (Parse.parsedModuleBlocks warmRoot) + assertEqual "warm occurrences" + (Parse.parsedModuleSyntaxOccurrences coldRoot) + (Parse.parsedModuleSyntaxOccurrences warmRoot) + assertEqual "warm syntax interface" + (Parse.parsedModuleSyntaxInterface coldRoot) + (Parse.parsedModuleSyntaxInterface warmRoot) + assertEqual "warm parsed identity" + (Parse.parsedModuleId coldRoot) + (Parse.parsedModuleId warmRoot) + case Parse.parsedModuleSyntaxOccurrences warmRoot of + first : second : third : fourth : [] -> do + assertEqual "fixed source marker" "source_zero" + (Parse.parsedSyntaxOccurrenceMarker first) + case Parse.parsedSyntaxOccurrenceEntry first of + Interface.CanonicalExpressionFunction + _pattern marker _fixity -> + assertEqual "fixed authoritative marker" "zero" marker + entry -> + assertFailure + ("unexpected fixed cached entry: " <> show entry) + assertEqual "multi-item block order" [1, 1, 1] + (Parse.parsedSyntaxOccurrenceBlockIndex + <$> [second, third, fourth]) + assertEqual "multi-item scanner order" + ["multi_item", "itemzero", "itemsucc"] + (Parse.parsedSyntaxOccurrenceMarker + <$> [second, third, fourth]) + case drop 1 (Parse.parsedModuleBlocks warmRoot) of + block : _ -> + case block of + Raw.BlockData _location _title marker _datatype -> + assertEqual "cached declaration-head anchor" + marker + (Parse.parsedSyntaxOccurrenceMarker second) + other -> + assertFailure + ("expected cached datatype block, got " + <> show other) + [] -> + assertFailure "cached datatype block is absent" + occurrences -> + assertFailure + ("unexpected cached syntax occurrences: " + <> show occurrences) + assertEqual "cold callback projection" 2 + =<< readIORef coldCallbacks + assertEqual "warm callback projection" 2 + =<< readIORef warmCallbacks + Store.closeStore store + +rejectsCorruptedCachedDeclarationAnchor :: Assertion +rejectsCorruptedCachedDeclarationAnchor = + withTemporaryDirectory "felix-parsed-corrupt-anchor" \temp -> do + writeBuiltinZeroDefinition + (temp Posix.</> "entry.tex") + "source_zero" + mounts <- oneMount "project" temp + request <- expectRight (searchedRoot "entry.tex") + foundation <- expectRight Foundation.checkedFoundation + let storePath = temp Posix.</> "store.sqlite" + theory = Identity.theoryId foundation + store <- openTestStore storePath theory + (cold, _measurements) <- expectParseExecution + =<< Parse.parseSourceWorkspaceMeasuredWithStoreAndSyntaxInputs + store mounts request (const []) + let parsed = Parse.parsedWorkspaceRootModule cold + key = Parse.parsedModuleKey parsed + fileId <- case Parse.parsedModuleSyntaxOccurrences parsed of + occurrence : _ -> + expectJust + "parsed occurrence file id" + (locFileId + (Parse.parsedSyntaxOccurrenceLocation occurrence)) + [] -> + assertFailure "parsed fixed occurrence is absent" + >> fail "unreachable" + decoded <- expectRight + (Parsed.decodeCanonicalParsedPayload + fileId + (Parse.parsedModulePayload parsed)) + Store.closeStore store + let corruptedOccurrences = case Parsed.decodedParsedOccurrences decoded of + (blockIndex, location, _marker, entry) : rest -> + (blockIndex, location, "corrupted_anchor", entry) : rest + [] -> + [] + corruptedPayload = + Parsed.canonicalParsedPayload + (Parsed.decodedParsedImports decoded) + (Parsed.decodedParsedBlocks decoded) + corruptedOccurrences + (Parsed.decodedParsedSyntaxInterface decoded) + corruptedId = + ParsedIdentity.parsedModuleId + key + (Parsed.canonicalParsedPayloadBytes corruptedPayload) + connection <- SQLite.open storePath + SQLite.execute connection + "UPDATE parsed_artifacts \ + \SET parsed_module_id = ?, payload = ? \ + \WHERE parsed_module_key = ?" + ( Cache.cacheDigestBytes + (ParsedIdentity.parsedModuleIdDigest corruptedId) + , Parsed.canonicalParsedPayloadBytes corruptedPayload + , Cache.cacheDigestBytes + (ParsedIdentity.parsedModuleKeyDigest key) + ) + SQLite.close connection + current <- openTestStore storePath theory + callbacks <- newIORef (0 :: Int) + Parse.parseSourceWorkspaceMeasuredWithStoreAndSyntaxInputsAndCallback + current mounts request (const []) + (\_source _block -> modifyIORef' callbacks (+ 1)) >>= \case + Left + (Parse.ParseExecutionArtifactIntegrityFailure + _source + (Parse.ParsedArtifactAssociationFailure + Parse.SyntaxOccurrenceMarkerMismatch{})) -> + pure () + other -> + assertFailure + ("unexpected corrupted parsed result: " <> show other) + assertEqual "corrupt hit invokes no parse callback" 0 + =<< readIORef callbacks + Store.closeStore current + +openTestStore :: FilePath -> Identity.TheoryId -> IO Store.Store +openTestStore path theory = + Store.openStore path theory >>= \case + Left failure -> + assertFailure (show failure) >> fail "unreachable" + Right (_startup, store) -> + pure store + +expectParseExecution + :: Either Parse.ParseExecutionError value + -> IO value +expectParseExecution = \case + Left failure -> + assertFailure (show failure) >> fail "unreachable" + Right value -> + pure value + parsesSourceGraph :: Assertion parsesSourceGraph = withTemporaryDirectory "felix-source-parse" \temp -> do |
