Spec.hs (170289B)
1 module Main where 2 3 import WorkspaceTests (workspaceTests) 4 import IOBoundaryTests (ioBoundaryTests) 5 import PortableCompilerTests (portableCompilerTests) 6 import ExecutableLoadingTests (executableLoadingTests) 7 import IODriverTests (ioDriverTests) 8 import IOUTF8Tests (ioUTF8Tests) 9 import IOByteStreamTests (ioByteStreamTests) 10 import IOSyncTests (ioSyncTests) 11 import IndexedWriterTests (indexedWriterTests) 12 import SnapshotTests (snapshotTests) 13 import IOResolverTests (ioResolverTests) 14 import IONetworkTests (ioNetworkTests) 15 import IOFilesystemTests (ioFilesystemTests) 16 import IOPositionTests (ioPositionTests) 17 import IOStandardTests (ioStandardTests) 18 import IOStorageTests (ioStorageTests) 19 import IOTaskTests (ioTaskTests) 20 import IOClockTests (ioClockTests) 21 import IOProcessTests (ioProcessTests) 22 import IOContextTests (ioContextTests) 23 import IOProtocolTests (ioProtocolTests) 24 import IOTests (ioTests) 25 import LibraryProgramTests (maybeTests, providedLibraries, contractsTests, arithmetic) 26 import qualified ParserValueTests 27 import qualified LibraryValueTests 28 import PortableTestCompiler (compileFixtureFile, compileFixtureDefinitions) 29 import ReferenceFixtures 30 import PackageTests (packageTests) 31 import InteractionInspectionTests (interactionInspectionTests) 32 import REPL 33 import Runtime 34 import TreeObservation (forceTreeShared) 35 import qualified NodeTable as Nodes 36 import Wire 37 import SupportFixture (canonicalEnvs, verifyReuseProviders, verifySupportSources, verifyRelocatedSupportSources, verifyIOSupportSources, verifyCompilerIOSupportSources, verifyCanonicalIOSupportSources, verifyBinary64SupportSources, 38 verifySupportExports, verifySupportDependencies, readFixtureSourceBytes, readTestSource, readPinnedSource) 39 import ContentStore 40 import ContentStore.Bundle 41 import Module.Manifest 42 import IODriver (runIO) 43 44 import Control.Exception (bracket, evaluate, try, SomeException) 45 import Control.Concurrent (forkIO, newEmptyMVar, putMVar, takeMVar) 46 import System.IO.Unsafe (unsafePerformIO) 47 import System.Mem.StableName (makeStableName) 48 import Data.IORef (IORef, newIORef, modifyIORef', readIORef, atomicModifyIORef') 49 import Control.Monad (foldM, forM, forM_, unless) 50 import Control.Monad.IO.Class (liftIO) 51 import System.IO.Temp (withSystemTempDirectory) 52 import qualified System.Timeout as Timeout 53 import System.Directory (createDirectory, createFileLink, doesFileExist, doesDirectoryExist, listDirectory) 54 import System.FilePath ((</>)) 55 import Data.Bits (xor) 56 import Data.List (find, isInfixOf, isPrefixOf, stripPrefix) 57 import Data.Text (Text, unpack, pack) 58 import Data.Text.Encoding (decodeUtf8, encodeUtf8) 59 import Data.Word (Word8) 60 import Test.Tasty 61 import Test.Tasty.HUnit 62 63 import Data.ByteString (ByteString) 64 import qualified Data.Foldable as Foldable 65 import qualified Data.ByteString as BS 66 import qualified Data.Map as Map 67 import qualified Data.Sequence as Seq 68 import qualified Data.Set as Set 69 import qualified Data.Vector as V 70 71 main :: IO () 72 main = do 73 putStrLn "Reference-artifact tests: authenticated compiler/support snapshots, not working-tree candidate coverage." 74 defaultMain tests 75 76 tests :: TestTree 77 tests = testGroup "Tricu Tests" 78 [ workspaceTests 79 , ioBoundaryTests 80 , portableCompilerTests 81 , executableLoadingTests 82 , runtimePresentationTests 83 , interactionInspectionTests 84 , packageTests 85 , evaluationDemandTests 86 , arithmetic 87 , providedLibraries 88 , contractsTests 89 , maybeTests 90 , fileEval 91 , demos 92 , decoding 93 , byteMarshallingTests 94 , sourceObjectCodecTests 95 , sharedTreeDecodingTests 96 , indexedTreeTransportTests 97 , verifiedTreePublicationTests 98 , wireTests 99 , tricuReaderTests 100 , LibraryValueTests.byteListUtilities (fixtureExports "bytes") (fixtureExports "base") 101 , ParserValueTests.binaryParserTests (fixtureExports "binary") (fixtureExports "parser") (fixtureExports "base") 102 , ParserValueTests.portableParserTests (fixtureExports "parser") (fixtureExports "base") 103 , locatedLexerTests 104 , compilerFixtureTests 105 , digestHexTests 106 , gateLexerTests 107 , gateParserTests 108 , gateProgramTests 109 , equationHelperTests 110 , sourceGraphTests 111 , gateOrderingTests 112 , gateUnitTests 113 , gateResolutionTests 114 , gateCompilationTests 115 , LibraryValueTests.httpParsingTests (fixtureExports "http") (fixtureExports "io_http") 116 , registryContractTests 117 , localControlTests 118 , contentStoreTests 119 , artifactSafetyTests 120 , ioDriverTests 121 , ioTests 122 , ioClockTests 123 , ioProcessTests 124 , ioContextTests 125 , ioProtocolTests 126 , ioTaskTests 127 , ioUTF8Tests 128 , ioByteStreamTests 129 , ioSyncTests 130 , indexedWriterTests 131 , snapshotTests 132 , ioResolverTests 133 , ioNetworkTests 134 , ioFilesystemTests 135 , ioPositionTests 136 , ioStandardTests 137 , ioStorageTests 138 ] 139 140 141 -- Constructor display is a runtime presentation format, not a frontend AST. 142 runtimePresentationTests :: TestTree 143 runtimePresentationTests = testGroup "Runtime presentation" 144 [ testCase "AST leaf constructor golden" $ formatT AST Leaf @?= "TLeaf" 145 , testCase "AST stem constructor golden" $ formatT AST (Stem Leaf) @?= "TStem TLeaf" 146 , testCase "AST nested constructor golden" $ 147 formatT AST (Fork (Stem Leaf) (Fork Leaf Leaf)) @?= 148 "TFork (TStem TLeaf) (TFork TLeaf TLeaf)" 149 ] 150 151 -- Demand uses exceptions rather than unbounded divergent reductions. 152 -- Physical sharing checks the Haskell implementation, not a tree ABI promise. 153 evaluationDemandTests :: TestTree 154 evaluationDemandTests = localOption (mkTimeout 30000000) $ testGroup "Evaluation demand boundaries" 155 [ testCase "constant application discards its argument" $ 156 apply (Fork Leaf (ofNumber 23)) poison @?= ofNumber 23 157 , testCase "constructor application exposes only the outer constructor" $ do 158 case apply Leaf poison of 159 Stem _ -> pure () 160 _ -> assertFailure "expected Stem" 161 case apply (Stem poison) poison of 162 Fork _ _ -> pure () 163 _ -> assertFailure "expected Fork" 164 , testCase "triage demands shape but not unused handlers or children" $ do 165 let value = ofNumber 23 166 constant = Fork Leaf value 167 apply (Fork (Fork value poison) poison) Leaf @?= value 168 apply (Fork (Fork poison constant) poison) (Stem poison) @?= value 169 apply (Fork (Fork poison poison) (Fork Leaf constant)) 170 (Fork poison poison) @?= value 171 demanded (evaluate (apply (Fork (Fork Leaf Leaf) Leaf) poison)) 172 , testCase "distribution retains the duplicated argument's physical sharing" $ do 173 -- t (t t) t c -> t c (t c). Both occurrences reference c. 174 input <- newIORef (Fork (ofNumber 23) (ofNumber 24)) >>= readIORef 175 case apply (Fork (Stem Leaf) Leaf) input of 176 Fork left (Stem right) -> do 177 a <- evaluate left >>= makeStableName 178 b <- evaluate right >>= makeStableName 179 assertBool "distribution copied the argument" (a == b) 180 _ -> assertFailure "unexpected distribution result" 181 , testCase "host list decoding checks the whole spine but not elements" $ do 182 fmap length (toList (Fork poison Leaf)) @?= Right 1 183 demanded (evaluate (toList (Fork Leaf poison))) 184 , testCase "complete flat serialization demands hidden children" $ 185 demanded (evaluate (BS.length (encodeTreeTerm (Fork Leaf poison)))) 186 , testCase "complete indexed serialization demands hidden children" $ 187 demanded (buildBundleShared [("root", Fork Leaf poison)]) 188 , testCase "full observation visits a shared depth-80 DAG within a bounded interval" $ do 189 let tower 0 = Leaf 190 tower n = let child = tower (n - 1) in Fork child child 191 outcome <- Timeout.timeout 2000000 (forceTreeShared (tower (80 :: Int))) 192 outcome @?= Just () 193 , testCase "full observation demands hidden children and does not retain failures" $ do 194 demanded (forceTreeShared (Fork Leaf poison)) 195 forceTreeShared (Fork Leaf (Stem Leaf)) 196 , testCase "full observation handles deep trees without expanding a recursive call stack" $ do 197 let chain = foldr (\_ child -> Stem child) Leaf [1..65536 :: Int] 198 outcome <- Timeout.timeout 2000000 (forceTreeShared chain) 199 outcome @?= Just () 200 , testCase "stable local bindings and ordinary applications discard unused inputs" $ do 201 forM_ ["main input = (ignored : 23) input", 202 "main input = let unused = input in 23"] $ \source -> do 203 function <- compileLocalControl [] source 204 apply function poison @?= ofNumber 23 205 , testCase "stable function consumes a finite prefix of an infinite producer" $ do 206 function <- compileLocalControl [] $ unlines 207 [ "main input = (let rec ones x = t x (ones x) in case (ones input) of" 208 , " [head | _] = head" 209 , " _ = 0)" ] 210 apply function (ofNumber 23) @?= ofNumber 23 211 ] 212 where 213 poison = error "evaluation-demand sentinel" :: T 214 demanded :: IO a -> Assertion 215 demanded action = do 216 outcome <- try (action >> pure ()) :: IO (Either SomeException ()) 217 case outcome of 218 Left err -> assertBool (show err) ("evaluation-demand sentinel" `isInfixOf` show err) 219 Right () -> assertFailure "expected the observer to demand the poisoned subtree" 220 221 -- Host-only negative controls: no source compiler or retained toolchain needed. 222 artifactSafetyTests :: TestTree 223 artifactSafetyTests = testGroup "artifact safety" $ 224 [ testGroup "alias components" 225 [ testCase (show name) $ withSystemTempDirectory "tricu-alias-safety" $ \dir -> do 226 let store = StorePath (dir </> "store") 227 ref = treeTermRef (hashObject treeTermDomain (encodeTreeTerm Leaf)) 228 forM_ [NameAlias, ModuleAlias, PackageAlias] $ \kind -> do 229 blocked "invalid alias name" (writeAlias store kind name ref) 230 blocked "invalid alias name" (readAlias store kind name) 231 doesDirectoryExist (unStorePath store) >>= (@?= False) 232 | name <- ["", ".", "..", "../escape", "a/../../escape", "/absolute", 233 "a\\b", "C:escape", "C:\\escape", "a\NULb", "a\nb", "a\rb"] 234 ] 235 , testCase "flat import counts exponential DAGs without expansion" $ do 236 let base = buildBundle [("root", Leaf)] 237 graph = base { bundleNodes = Seq.fromList (BNLeaf : [BNFork (i-1) (i-1) | i <- [1..10000]]) 238 , bundleRoots = [10000] 239 , bundleManifest = (bundleManifest base) { manifestRoots = [BundleRoot 10000 "default"], 240 manifestExports = [BundleExport "root" 10000 "term" "arboricx.abi.tree.v1"] } } 241 case checkFlatImportSize defaultFlatImportLimit graph of 242 Left err -> assertBool err ("flat import byte limit exceeded" `isInfixOf` err) 243 Right () -> assertFailure "expanded size was not bounded" 244 , testCase "flat byte accounting matches exact small serializations" $ 245 forM_ [Leaf, Stem Leaf, Fork (Stem Leaf) (Stem Leaf)] $ \tree -> do 246 let bundle = buildBundle [("a", tree), ("b", tree)] 247 size = 2 * fromIntegral (BS.length (encodeTreeTerm tree)) 248 checkFlatImportSize size bundle @?= Right () 249 assertBool "one byte under budget must fail" (either (const True) (const False) (checkFlatImportSize (size-1) bundle)) 250 , testCase "duplicate export names rejected by bundle and stored manifest readers" $ do 251 let bundle = buildBundle [("same", Leaf), ("same", Stem Leaf)] 252 manifest = ModuleManifest [] [ModuleExport "same" (treeTermRef (nodeHash NLeaf)) "arboricx.abi.tree.v1", 253 ModuleExport "same" (treeTermRef (nodeHash NLeaf)) "arboricx.abi.tree.v1"] 254 verifyBundle bundle @?= Left "duplicate export name" 255 decodeManifest (encodeManifest manifest) @?= Left "duplicate export name" 256 , testCase "qualified and symbolic aliases remain usable" $ 257 withSystemTempDirectory "tricu-alias-valid" $ \dir -> do 258 let store = StorePath dir 259 ref = treeTermRef (hashObject treeTermDomain (encodeTreeTerm Leaf)) 260 forM_ ["native_math.isNat?", "++", "hello world", "λ", ".hidden", "a..b"] $ \name -> do 261 writeAlias store NameAlias name ref 262 readAlias store NameAlias name >>= (@?= Just ref) 263 , testCase "alias absolute paths cannot read or overwrite an outside sentinel" $ 264 withSystemTempDirectory "tricu-alias-sentinel" $ \dir -> do 265 let store = StorePath (dir </> "store") 266 outside = dir </> "sentinel" 267 name = pack outside 268 ref = treeTermRef (hashObject treeTermDomain (encodeTreeTerm Leaf)) 269 BS.writeFile outside "unchanged" 270 blocked "invalid alias name" (writeAlias store NameAlias name ref) 271 blocked "invalid alias name" (readAlias store NameAlias name) 272 BS.readFile outside >>= (@?= "unchanged") 273 , testGroup "object path hashes" 274 [ testCase (show h) $ withSystemTempDirectory "tricu-hash-safety" $ \dir -> 275 do 276 blocked "invalid object hash" (getObject (StorePath dir) h) 277 blocked "invalid object hash" (evaluate (objectPath (StorePath dir) h)) 278 | h <- ["", "../escape", "../../escape", "/absolute", "abc", pack (replicate 64 'A'), 279 pack (replicate 63 'a'), pack (replicate 65 'a'), pack (replicate 64 'g')] 280 ] 281 , testCase "object paths cannot read an absolute outside sentinel" $ 282 withSystemTempDirectory "tricu-hash-sentinel" $ \dir -> do 283 let outside = dir </> "sentinel" 284 BS.writeFile outside "not a CAS object" 285 blocked "invalid object hash" (getObject (StorePath (dir </> "store")) (pack outside)) 286 , testCase "cached authenticated objects are immutable snapshots" $ fresh $ \store -> do 287 h <- putTreeTerm store Leaf 288 resolver <- cachedFilesystemResolver store 289 resolverObject resolver (treeTermRef h) >>= (@?= Just (encodeTreeTerm Leaf)) 290 BS.writeFile (objectPath store h) (encodeTreeTerm (Stem Leaf)) 291 resolverObject resolver (treeTermRef h) >>= (@?= Just (encodeTreeTerm Leaf)) 292 blocked "digest mismatch" (resolverObject (filesystemResolver store) (treeTermRef h)) 293 , testCase "object resolution verifies bytes from custom resolver callbacks" $ fresh $ \store -> do 294 h <- putTreeTerm store Leaf 295 mh <- putManifest store (ModuleManifest [] [ModuleExport "x" (treeTermRef h) "arboricx.abi.tree.v1"]) 296 let resolver = (filesystemResolver store) { resolverObject = \_ -> pure (Just (encodeTreeTerm (Stem Leaf))) } 297 blocked "digest mismatch" (resolveObjectByHash resolver (unDomain treeTermDomain) h) 298 , testGroup "execution contract" 299 [ testCase label $ do 300 let base = buildBundle [("first", Leaf), ("second", Stem Leaf)] 301 bundle = base { bundleManifest = change (bundleManifest base), bundleManifestBytes = BS.empty } 302 case verifyBundle bundle of 303 Left err -> assertBool err (needle `isInfixOf` err) 304 Right () -> assertFailure "unsupported execution contract accepted" 305 | (label, needle, change) <- 306 [("evaluation", "runtime evaluation", \m -> m { manifestRuntime = (manifestRuntime m) { runtimeEvaluation = "call-by-value" } }), 307 ("export kind", "export kind", \m -> m { manifestExports = map (\e -> e { exportKind = "foreign" }) (manifestExports m) }), 308 ("second export ABI", "export ABI", \m -> m { manifestExports = case manifestExports m of 309 [a,b] -> [a,b { exportAbi = "foreign.abi" }]; es -> es })] 310 ] 311 ] ++ 312 [ testGroup label 313 [ testCase "tampered manifest" $ fresh $ \store -> do 314 h <- putManifest store (ModuleManifest [] []) 315 BS.writeFile (objectPath store h) (encodeManifest (ModuleManifest [] [ModuleExport "x" (treeTermRef h) "arboricx.abi.tree.v1"])) 316 resolver <- makeResolver store 317 blocked "digest mismatch" (resolveManifest resolver h) 318 blocked "digest mismatch" (getManifest store h) 319 , testCase "tampered exported term" $ fresh $ \store -> do 320 h <- putTreeTerm store Leaf 321 mh <- putManifest store (ModuleManifest [] [ModuleExport "x" (treeTermRef h) "arboricx.abi.tree.v1"]) 322 BS.writeFile (objectPath store h) (encodeTreeTerm (Stem Leaf)) 323 resolver <- makeResolver store 324 blocked "digest mismatch" (resolveObjectByHash resolver (unDomain treeTermDomain) h) 325 blocked "digest mismatch" (getTreeTerm store h) 326 , testCase "tampered Merkle child" $ fresh $ \store -> do 327 h <- putTree store (Stem Leaf) 328 BS.writeFile (objectPath store (nodeHash NLeaf)) (serializeNode (NStem (pack (replicate 64 '0')))) 329 resolver <- makeResolver store 330 blocked "digest mismatch" (resolveTree resolver h) 331 blocked "digest mismatch" (getTree store h) 332 , testCase "tampered self-cycle rejected before hydration" $ fresh $ \store -> do 333 h <- putTree store Leaf 334 BS.writeFile (objectPath store h) (serializeNode (NStem h)) 335 resolver <- makeResolver store 336 completed <- Timeout.timeout 1000000 (blocked "digest mismatch" (resolveTree resolver h)) 337 completed @?= Just () 338 , testCase "failed verification never poisons the cache" $ fresh $ \store -> do 339 h <- putTreeTerm store Leaf 340 resolver <- makeResolver store 341 BS.writeFile (objectPath store h) (encodeTreeTerm (Stem Leaf)) 342 blocked "digest mismatch" (resolverObject resolver (treeTermRef h)) 343 BS.writeFile (objectPath store h) (encodeTreeTerm Leaf) 344 resolverObject resolver (treeTermRef h) >>= (@?= Just (encodeTreeTerm Leaf)) 345 , testCase "wrong object domain" $ fresh $ \store -> do 346 h <- putTreeTerm store Leaf 347 resolver <- makeResolver store 348 blocked "digest mismatch" (resolverObject resolver (ObjectRef (unDomain manifestDomain) h)) 349 -- Export ABI rejection is exercised against the production executable in 350 -- executable_arguments.py, not the retired source-module resolver. 351 , testCase "valid objects and genuine missing objects" $ fresh $ \store -> do 352 h <- putTreeTerm store (Stem Leaf) 353 mh <- putManifest store (ModuleManifest [] [ModuleExport "x" (treeTermRef h) "arboricx.abi.tree.v1"]) 354 nh <- putTree store (Stem Leaf) 355 resolver <- makeResolver store 356 resolveManifest resolver mh >>= (@?= Just (ModuleManifest [] [ModuleExport "x" (treeTermRef h) "arboricx.abi.tree.v1"])) 357 resolveTree resolver nh >>= (@?= Just (Stem Leaf)) 358 resolveObjectByHash resolver (unDomain treeTermDomain) h >>= (@?= Just (encodeTreeTerm (Stem Leaf))) 359 loadExecutableObject store (treeTermRef h) >>= (@?= Stem Leaf) 360 resolverObject resolver (treeTermRef (pack (replicate 64 '0'))) >>= (@?= Nothing) 361 ] 362 | (label, makeResolver) <- [("filesystem integrity", pure . filesystemResolver), ("cached integrity", cachedFilesystemResolver)] 363 ] 364 where 365 treeTermRef = ObjectRef (unDomain treeTermDomain) 366 fresh f = withSystemTempDirectory "tricu-integrity" (f . StorePath) 367 blocked needle action = do 368 outcome <- try (action >> pure ()) :: IO (Either SomeException ()) 369 case outcome of 370 Left err -> assertBool (show err) (needle `isInfixOf` show err) 371 Right () -> assertFailure ("expected rejection: " ++ needle) 372 373 sharedTreeDecodingTests :: TestTree 374 sharedTreeDecodingTests = testGroup "shared inert tree decoding" 375 [ testCase "small trees preserve exact structure and canonical bytes" $ 376 forM_ (trees 3) $ \tree -> do 377 let bytes = encodeTreeTerm tree 378 decodeTreeTermShared bytes @?= Right tree 379 fmap encodeTreeTerm (decodeTreeTermShared bytes) @?= Right bytes 380 , testCase "integer interning distinguishes constructors, pair order and wide IDs" $ do 381 let keys = [Nodes.LeafKey] ++ map Nodes.StemKey values ++ 382 [Nodes.ForkKey a b | a <- values, b <- values] 383 values = [minBound, -1, 0, 1, 65535, maxBound] ++ 384 map fromInteger [2147483647, 2147483648, 4294967295, 4294967296] 385 entries = zip keys [(0 :: Int)..] 386 table = foldl (\acc (key, value) -> Nodes.insert key value acc) Nodes.empty entries 387 expected = Map.fromList entries 388 forM_ (Map.toList expected) $ \(key, value) -> 389 Nodes.lookup key table @?= Just value 390 Nodes.lookup (Nodes.ForkKey 17 19) table @?= Nothing 391 Nodes.lookup (Nodes.StemKey 17) table @?= Nothing 392 let updated = Nodes.insert (Nodes.ForkKey 0 1) 999 table 393 Nodes.lookup (Nodes.ForkKey 0 1) updated @?= Just 999 394 Nodes.lookup (Nodes.ForkKey 1 0) updated @?= Map.lookup (Nodes.ForkKey 1 0) expected 395 , testCase "deep shared decoding preserves bytes without recursive reference decoding" $ do 396 let bytes = BS.replicate 65536 1 <> BS.singleton 0 397 decoded <- either assertFailure pure (decodeTreeTermShared bytes) 398 encodeTreeTerm decoded @?= bytes 399 , testCase "malformed bytes retain ordinary decoder errors" $ 400 forM_ (concat [sequence (replicate n [0, 1, 2, 255]) | n <- [0..4]]) $ \tags -> 401 decodeTreeTermShared (BS.pack tags) @?= decodeTreeTerm (BS.pack tags) 402 , testCase "repeated branches regain physical sharing without application" $ do 403 let tower 0 = Fork (Stem Leaf) Leaf 404 tower n = let child = tower (n - 1) in Fork child child 405 value = tower (14 :: Int) 406 decoded <- either assertFailure pure (decodeTreeTermShared (encodeTreeTerm value)) 407 let check (Fork left right) = do 408 a <- evaluate left >>= makeStableName 409 b <- evaluate right >>= makeStableName 410 assertBool "equal branches were separately allocated" (a == b) 411 check _ = assertFailure "missing fork" 412 check decoded 413 case decoded of 414 Fork left _ -> check left 415 _ -> assertFailure "missing root" 416 ] 417 where 418 trees 0 = [Leaf] 419 trees n = let children = trees (n - 1) 420 in Leaf : map Stem children ++ [Fork a b | a <- children, b <- children] 421 422 -- Indexed IO transports inert normal forms. The tiny exponential logical tree 423 -- is deliberately never unfolded for equality/encoding by these controls. 424 indexedTreeTransportTests :: TestTree 425 indexedTreeTransportTests = testGroup "indexed tree transport" 426 [ testCase "canonical bytes agree with independent ordinary writer" $ 427 forM_ (trees 2) $ \tree -> do 428 encoded <- encodeIndexedTree tree 429 encoded @?= encodeBundle (buildBundle [("root", tree)]) 430 decodeIndexedTree encoded @?= Right tree 431 , testCase "shared depth-80 tree encodes within a bounded interval" $ do 432 let tower 0 = Leaf 433 tower n = let child = tower (n - 1) in Fork child child 434 measured <- Timeout.timeout 2000000 $ do 435 payload <- encodeIndexedTree (tower (80 :: Int)) 436 size <- evaluate (BS.length payload) 437 assertBool "DAG unexpectedly expanded" (size < 2048) 438 bundle <- either assertFailure pure (decodeBundle payload) 439 Seq.length (bundleNodes bundle) @?= 81 440 value <- either assertFailure pure (decodeIndexedTree payload) 441 case value of 442 Fork left right -> do 443 a <- evaluate left >>= makeStableName 444 b <- evaluate right >>= makeStableName 445 assertBool "decoded branches lost sharing" (a == b) 446 _ -> assertFailure "missing fork" 447 measured @?= Just () 448 , testCase "shared writer is name-neutral and byte-identical to indexed import" $ 449 withSystemTempDirectory "tricu-indexed-import" $ \dir -> do 450 let tree = Fork (Stem Leaf) (Stem Leaf) 451 encoded <- encodeIndexedTree tree 452 [(name, pin)] <- unpackIndexedBundleToStore (StorePath dir) 453 (encodeBundle (buildBundle [("publicName", tree)])) 454 name @?= "publicName" 455 pin @?= hashObject indexedTreeDomain encoded 456 ] 457 where 458 trees 0 = [Leaf] 459 trees n = let xs = trees (n - 1) in Leaf : map Stem xs ++ [Fork a b | a <- xs, b <- xs] 460 461 -- Indexed snapshot/writer/staging suites cover current native transport. Flat 462 -- v1 file actions and grant-based publication are deliberately retired. 463 verifiedTreePublicationTests :: TestTree 464 verifiedTreePublicationTests = testGroup "Source-owned indexed publication" 465 [ testCase "sourceObjectPutTree creates canonical shards and reuses objects" $ 466 withSystemTempDirectory "tricu-tree-library" $ \dir -> do 467 env <- evaluateCompilerFile "./lib/source/object.tri" 468 let root = dir </> "cas" 469 tree = Fork Leaf (Stem Leaf) 470 payload = encodeBundle (buildBundle [("root", tree)]) 471 expected = unpack (hashObject indexedTreeDomain payload) 472 action = Fork (ofString "tricuIO") (Fork (ofNumber 2) 473 (apply (apply (env Map.! "sourceObjectPutTree") (ofString root)) tree)) 474 path = root </> "objects" </> take 3 expected </> expected 475 runIO action >>= (@?= Right (ioOkResult (ofString expected))) 476 runIO action >>= (@?= Right (ioOkResult (ofString expected))) 477 BS.readFile path >>= (@?= payload) 478 ] 479 sourceObjectCodecTests :: TestTree 480 sourceObjectCodecTests = testGroup "Portable tree object codec" 481 [ testCase "canonical preorder bytes, suffix law and complete round trip" $ 482 withSystemTempDirectory "source-object-codec" $ \dir -> do 483 env <- evaluateCompilerFile "./lib/source/object.tri" 484 let encoder = env Map.! "sourceObjectEncode" 485 fixed = env Map.! "sourceObjectEncodeOnto" 486 decoder = env Map.! "sourceObjectDecodeComplete" 487 suffix = BS.pack [7, 0, 255] 488 seeds = [Leaf, Stem Leaf, Fork Leaf Leaf] 489 small = seeds ++ [Fork l r | l <- seeds, r <- seeds] 490 leftHeavy = iterate (\tree -> Fork tree Leaf) Leaf !! 64 491 rightHeavy = iterate (Fork Leaf) Leaf !! 64 492 balanced = iterate (\tree -> Fork tree tree) Leaf !! 8 493 forM_ (small ++ [leftHeavy, rightHeavy, balanced]) $ \tree -> do 494 let bytes = encodeTreeTerm tree 495 encoded = apply encoder tree 496 toBytes encoded @?= Right bytes 497 toBytes (apply (apply fixed tree) (ofBytes suffix)) @?= 498 Right (bytes <> suffix) 499 apply decoder encoded @?= Fork (Stem Leaf) (Fork tree Leaf) 500 ] 501 502 fileEval :: TestTree 503 fileEval = testGroup "Portable source file tests" 504 [ testCase "Forks" $ check "./test/fork.tri" (Fork Leaf Leaf) 505 , testCase "File ends with comment" $ check "./test/comments-1.tri" (Fork (Stem Leaf) Leaf) 506 , testCase "Mapping and Equality" $ check "./test/map.tri" (Stem Leaf) 507 , testCase "Eval and decoding string" $ check "./test/string.tri" (ofString "String test!") 508 ] 509 where 510 check path expected = do 511 source <- readFile path 512 let external = ofList [Fork (ofString name) value | (name,value) <- Map.toList (fixtureExports "prelude")] 513 foldl apply (fixtureExports "source.unit" Map.! "compileGateProgram") 514 [external, ofString source] @?= parserOk expected Leaf 515 516 -- All of our demo tests are also module tests 517 demos :: TestTree 518 demos = testGroup "Test provided demo functionality" 519 [ testCase "Constructor-safe lowering matches the authored Boolean not tree" $ do 520 env <- evaluateCompilerFile "./demos/equality.tri" 521 -- Constructor-only K pruning intentionally changes inspectable function 522 -- trees. This specimen now equals its authored TC form; that is not a 523 -- structural-equivalence promise for arbitrary extensional functions. 524 env Map.! "main" @?= Stem Leaf 525 env Map.! "not_TC?" @?= 526 Fork (Fork (Stem Leaf) (Fork Leaf Leaf)) (Fork Leaf (Fork Leaf Leaf)) 527 env Map.! "not_Lambda?" @?= env Map.! "not_TC?" 528 forM_ ["not_TC?", "not_Lambda?"] $ \name -> do 529 apply (env Map.! name) Leaf @?= Stem Leaf 530 apply (env Map.! name) (Stem Leaf) @?= Leaf 531 env Map.! "bothTrueEqual?" @?= Stem Leaf 532 env Map.! "bothFalseEqual?" @?= Stem Leaf 533 , testCase "Convert values back to source code demo" $ do 534 res <- liftIO $ ((Map.! "main") <$> evaluateCompilerFile "./demos/toSource.tri") 535 -- Native portable foundation trees, not the Haskell recovery cohort. 536 decodeResult res @?= "\"(t (t (t t) (t t t)) (t t (t t t)))\"" 537 , testCase "Determining the size of stable-compiled functions" $ do 538 env <- evaluateCompilerFile "./demos/size.tri" 539 -- Count the actual executable independently of the compiled size worker. 540 -- This is a representation check of the retained stable artifact, 541 -- not a parity requirement across compiler backends. 542 let nodes Leaf = 1 :: Integer 543 nodes (Stem child) = 1 + nodes child 544 nodes (Fork left right) = 1 + nodes left + nodes right 545 actual = nodes (env Map.! "size") 546 -- Constructor-K publication: the independent host count changes from 547 -- 1845 to 1233; the compiled worker must still count its actual tree. 548 actual @?= 1233 549 env Map.! "main" @?= ofNumber actual 550 , testCase "Level Order Traversal demo" $ do 551 res <- liftIO $ ((Map.! "main") <$> evaluateCompilerFile "./demos/levelOrderTraversal.tri") 552 decodeResult res @?= "\"\n1 \n2 3 \n4 5 6 7 \n8 11 10 9 12 \"" 553 , testCase "Contract effect demo with do notation" $ do 554 res <- liftIO $ ((Map.! "main") <$> evaluateCompilerFile "./demos/contractEffects.tri") 555 decodeResult res @?= "[t t, 10]" 556 , testCase "Safe base wrappers demo" $ do 557 res <- liftIO $ ((Map.! "main") <$> evaluateCompilerFile "./demos/contractBasics.tri") 558 decodeResult res @?= "[3, t t, t, t t]" 559 ] 560 561 decoding :: TestTree 562 decoding = testGroup "Decoding Tests" 563 [ testCase "Decode Leaf" $ do 564 decodeResult Leaf @?= "t" 565 566 , testCase "Decode list of non-ASCII numbers" $ do 567 let input = ofList [ofNumber 1, ofNumber 14, ofNumber 6] 568 decodeResult input @?= "[1, 14, 6]" 569 570 , testCase "Decode list of ASCII numbers as a string" $ do 571 let input = ofList [ofNumber 97, ofNumber 98, ofNumber 99] 572 decodeResult input @?= "\"abc\"" 573 574 , testCase "Decode small number" $ do 575 decodeResult (ofNumber 42) @?= "42" 576 577 , testCase "Decode large number" $ do 578 decodeResult (ofNumber 9999) @?= "9999" 579 580 , testCase "Decode string in list" $ do 581 let input = ofList [ofString "hello", ofString "world"] 582 decodeResult input @?= "[\"hello\", \"world\"]" 583 584 , testCase "Decode mixed list with strings" $ do 585 let input = ofList [ofString "hello", ofNumber 42, ofString "world"] 586 decodeResult input @?= "[\"hello\", 42, \"world\"]" 587 588 , testCase "Decode nested lists with strings" $ do 589 let input = ofList [ofList [ofString "nested"], ofString "string"] 590 decodeResult input @?= "[[\"nested\"], \"string\"]" 591 ] 592 593 -- -------------------------------------------------------------------------- 594 -- Byte marshalling tests 595 -- -------------------------------------------------------------------------- 596 597 byteMarshallingTests :: TestTree 598 byteMarshallingTests = testGroup "Byte Marshalling Tests" 599 [ testCase "shared byte encodings equal canonical numerals for all octets" $ do 600 mapM_ (\n -> ofByte (fromIntegral n) @?= ofNumber n) [0..255] 601 602 , testCase "byte buffer preserves order and multiplicity across repeated octets" $ do 603 let values = concat (replicate 17 [0..255]) :: [Word8] 604 bytes = BS.pack values 605 ofBytes bytes @?= ofList (map (ofNumber . toInteger) values) 606 toBytes (ofBytes bytes) @?= Right bytes 607 608 , testCase "ofByte / toByte round-trip: 0" $ do 609 let w8 = (0 :: Word8) 610 toByte (ofByte w8) @?= Right w8 611 612 , testCase "ofByte / toByte round-trip: 1" $ do 613 let w8 = (1 :: Word8) 614 toByte (ofByte w8) @?= Right w8 615 616 , testCase "ofByte / toByte round-trip: 127" $ do 617 let w8 = (127 :: Word8) 618 toByte (ofByte w8) @?= Right w8 619 620 , testCase "ofByte / toByte round-trip: 128" $ do 621 let w8 = (128 :: Word8) 622 toByte (ofByte w8) @?= Right w8 623 624 , testCase "ofByte / toByte round-trip: 255" $ do 625 let w8 = (255 :: Word8) 626 toByte (ofByte w8) @?= Right w8 627 628 , testCase "toByte rejects value > 255" $ do 629 -- ofNumber 256 = Fork Leaf (Fork Leaf Leaf) — value 256 630 toByte (ofNumber 256) @?= Left "Byte value out of range: 256" 631 632 , testCase "toByte accepts Leaf" $ do 633 toByte (Leaf) @?= Right 0 634 635 , testCase "toByte rejects non-number tree" $ do 636 toByte (Stem Leaf) @?= Left "Invalid Tree Calculus number" 637 toByte (Stem (Stem Leaf)) @?= Left "Invalid Tree Calculus number" 638 639 , testCase "ofBytes / toBytes round-trip: empty ByteString" $ do 640 toBytes (ofBytes BS.empty) @?= Right BS.empty 641 642 , testCase "ofBytes / toBytes round-trip: [0x00]" $ do 643 toBytes (ofBytes (BS.pack [0x00])) @?= Right (BS.pack [0x00]) 644 645 , testCase "ofBytes / toBytes round-trip: [0xff]" $ do 646 toBytes (ofBytes (BS.pack [0xff])) @?= Right (BS.pack [0xff]) 647 648 , testCase "ofBytes / toBytes round-trip: mixed bytes" $ do 649 let bytes = BS.pack [0x00, 0x01, 0x7f, 0x80, 0xff, 0x41, 0x42, 0x43] 650 toBytes (ofBytes bytes) @?= Right bytes 651 652 , testCase "toBytes rejects non-list tree" $ do 653 -- Leaf is a valid list (empty), so this won't work. 654 -- Stem Leaf is not a list. 655 toBytes (Stem Leaf) @?= Left "Invalid Tree Calculus list" 656 657 , testCase "toBytes rejects list containing invalid byte (>255)" $ do 658 -- [ofNumber 256, ofNumber 1] — first element is > 255 659 let badList = ofList [ofNumber 256, ofNumber 1] 660 toBytes badList @?= Left "Byte value out of range: 256" 661 662 , testCase "nodePayloadToTreeBytes / treeBytesToNodePayload: Leaf payload" $ do 663 -- Leaf payload is 0x00 (1 byte) 664 let payload = BS.pack [0x00] 665 treeBytesToNodePayload (nodePayloadToTreeBytes payload) @?= Right payload 666 667 , testCase "nodePayloadToTreeBytes / treeBytesToNodePayload: Stem payload" $ do 668 -- Stem payload: 0x01 || 32-byte hash = 33 bytes 669 let payload = BS.pack (0x01 : replicate 32 0x42) 670 treeBytesToNodePayload (nodePayloadToTreeBytes payload) @?= Right payload 671 672 , testCase "nodePayloadToTreeBytes / treeBytesToNodePayload: Fork payload" $ do 673 -- Fork payload: 0x02 || 32-byte hash || 32-byte hash = 65 bytes 674 let payload = BS.pack (0x02 : replicate 64 0x42) 675 treeBytesToNodePayload (nodePayloadToTreeBytes payload) @?= Right payload 676 677 , testCase "hashToTreeBytes / treeBytesToHash round-trip" $ do 678 -- Use a known 32-byte hash (SHA256 of "") 679 let hashStr :: MerkleHash 680 hashStr = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" 681 case hashToTreeBytes hashStr of 682 Left err -> assertFailure $ "hashToTreeBytes failed: " ++ err 683 Right tree -> treeBytesToHash tree @?= Right hashStr 684 685 , testCase "hashToTreeBytes rejects invalid hex hash" $ do 686 hashToTreeBytes "not-a-hash" @?= Left "Invalid hex MerkleHash" 687 688 , testCase "hashToTreeBytes rejects non-32-byte hash" $ do 689 -- "00" decodes to 1 byte, not 32 690 hashToTreeBytes "00" @?= Left "Hash raw bytes must be 32 bytes" 691 692 , testCase "treeBytesToHash rejects wrong byte count" $ do 693 -- Only 16 bytes, not 32 694 let t16 = ofBytes (BS.pack [0x41 | _ <- [1..16]]) 695 treeBytesToHash t16 @?= Left "Expected exactly 32 byte elements for hash" 696 ] 697 698 -- -------------------------------------------------------------------------- 699 -- Content store tests 700 -- -------------------------------------------------------------------------- 701 702 contentStoreTests :: TestTree 703 contentStoreTests = testGroup "Content Store Tests" 704 [ testCase "Filesystem CAS: put/get object and sharded path" $ 705 withSystemTempDirectory "tricu-store" $ \dir -> do 706 let store = StorePath dir 707 domain = Domain "test.object.v1" 708 payload = BS.pack [1, 2, 3, 4] 709 h <- putObject store domain payload 710 shardForHash h @?= take 3 (unpack h) 711 objectPath store h @?= dir </> "objects" </> take 3 (unpack h) </> unpack h 712 doesFileExist (objectPath store h) >>= (@?= True) 713 getObject store h >>= (@?= Just payload) 714 715 , testCase "Filesystem CAS: idempotent object writes" $ 716 withSystemTempDirectory "tricu-store" $ \dir -> do 717 let store = StorePath dir 718 domain = Domain "test.object.v1" 719 payload = BS.pack [9, 8, 7] 720 h1 <- putObject store domain payload 721 h2 <- putObject store domain payload 722 h1 @?= h2 723 countStoredObjects store >>= (@?= 1) 724 725 , testCase "Filesystem CAS: putTree/getTree round trip" $ 726 withSystemTempDirectory "tricu-store" $ \dir -> do 727 let store = StorePath dir 728 term = Fork (Stem Leaf) (Fork Leaf (Stem Leaf)) 729 leafHash = nodeHash NLeaf 730 stemHash = nodeHash (NStem leafHash) 731 rightHash = nodeHash (NFork leafHash stemHash) 732 expectedRoot = nodeHash (NFork stemHash rightHash) 733 root <- putTree store term 734 root @?= expectedRoot 735 getTree store root >>= (@?= Just term) 736 737 , testCase "Filesystem CAS: shared subtrees are deduplicated" $ 738 withSystemTempDirectory "tricu-store" $ \dir -> do 739 let store = StorePath dir 740 shared = Stem Leaf 741 term = Fork shared shared 742 _ <- putTree store term 743 countStoredObjects store >>= (@?= 3) 744 745 , testCase "Workspace aliases: write/read/list object refs" $ 746 withSystemTempDirectory "tricu-store" $ \dir -> do 747 let store = StorePath dir 748 ref = ObjectRef "arboricx.tree-root.v1" "abc123" 749 writeAlias store NameAlias "main" ref 750 readAlias store NameAlias "main" >>= (@?= Just ref) 751 listAliases store NameAlias >>= (@?= [("main", ref)]) 752 753 , testCase "Module manifests: put/get round trip through CAS" $ 754 withSystemTempDirectory "tricu-store" $ \dir -> do 755 let store = StorePath dir 756 term = Fork Leaf (Stem Leaf) 757 manifestFor root = ModuleManifest [] 758 [ ModuleExport 759 "main" 760 (ObjectRef (unDomain treeTermDomain) root) 761 "arboricx.abi.tree.v1" 762 ] 763 root <- putTreeTerm store term 764 h <- putManifest store (manifestFor root) 765 getManifest store h >>= (@?= Just (manifestFor root)) 766 767 , testCase "ObjectResolver: resolves manifests and trees without filesystem coupling" $ do 768 let term = Fork Leaf (Stem Leaf) 769 leafH = nodeHash NLeaf 770 stemH = nodeHash (NStem leafH) 771 rootH = nodeHash (NFork leafH stemH) 772 termH = hashObject treeTermDomain (encodeTreeTerm term) 773 manifest = ModuleManifest [] 774 [ ModuleExport 775 "value" 776 (ObjectRef (unDomain treeTermDomain) termH) 777 "arboricx.abi.tree.v1" 778 ] 779 manifestBytes = encodeManifest manifest 780 manifestH = hashObject manifestDomain manifestBytes 781 objects = Map.fromList 782 [ (("arboricx.merkle.node.v1", leafH), serializeNode NLeaf) 783 , (("arboricx.merkle.node.v1", stemH), serializeNode (NStem leafH)) 784 , (("arboricx.merkle.node.v1", rootH), serializeNode (NFork leafH stemH)) 785 , ((unDomain treeTermDomain, termH), encodeTreeTerm term) 786 , ((unDomain manifestDomain, manifestH), manifestBytes) 787 ] 788 hydrate objs h = case deserializeNode <$> Map.lookup ("arboricx.merkle.node.v1", h) objs of 789 Nothing -> return Nothing 790 Just NLeaf -> return (Just Leaf) 791 Just (NStem child) -> fmap Stem <$> hydrate objs child 792 Just (NFork left right) -> do 793 l <- hydrate objs left 794 r <- hydrate objs right 795 return $ Fork <$> l <*> r 796 resolver = ObjectResolver 797 { resolverAlias = \kind name -> return $ if kind == ModuleAlias && name == "demo" 798 then Just (ObjectRef (unDomain manifestDomain) manifestH) 799 else Nothing 800 , resolverObject = \ref -> return $ Map.lookup (objectRefKind ref, objectRefHash ref) objects 801 , resolverManifest = \h -> return $ do 802 bytes <- Map.lookup (unDomain manifestDomain, h) objects 803 either (const Nothing) Just (decodeManifest bytes) 804 , resolverTree = hydrate objects 805 } 806 resolverAlias resolver ModuleAlias "demo" >>= (@?= Just (ObjectRef (unDomain manifestDomain) manifestH)) 807 resolveManifest resolver manifestH >>= (@?= Just manifest) 808 resolveTree resolver rootH >>= (@?= Just term) 809 810 , testCase "Arboricx bundle: unpack transport bundle into CAS tree terms" $ 811 withSystemTempDirectory "tricu-store" $ \dir -> do 812 let store = StorePath dir 813 term = Fork (Stem Leaf) Leaf 814 bundle = buildBundle [("main", term)] 815 exports <- unpackBundleToStore store (encodeBundle bundle) 816 case exports of 817 [("main", root)] -> getTreeTerm store root >>= (@?= Just term) 818 other -> assertFailure $ "unexpected exports: " ++ show other 819 820 , testCase "Indexed CAS: extract roots without labels or unrelated nodes affecting identity" $ do 821 let term = Fork (Stem Leaf) (Stem Leaf) 822 first = buildBundle [("alpha", term)] 823 second = buildBundle [("unrelated", ofNumber 99), ("beta", term)] 824 [ex] = manifestExports (bundleManifest first) 825 [_, other] = manifestExports (bundleManifest second) 826 a <- either assertFailure pure (extractBundleRoot first $ exportRoot ex) 827 b <- either assertFailure pure (extractBundleRoot second $ exportRoot other) 828 encodeBundle a @?= encodeBundle b 829 decodeIndexedTree (encodeBundle a) @?= Right term 830 hashObject indexedTreeDomain (encodeBundle a) /= hashObject treeTermDomain (encodeTreeTerm term) @? "distinct object identities" 831 832 , testCase "Indexed CAS: exponentially shared logical tree stays an indexed graph" $ do 833 let base = buildBundle [("unused", Leaf)] 834 graph = base { bundleNodes = Seq.fromList 835 (BNLeaf : [BNFork (i - 1) (i - 1) | i <- [1..80]]) } 836 root <- either assertFailure pure (extractBundleRoot graph 80) 837 Seq.length (bundleNodes root) @?= 81 838 BS.length (encodeBundle root) < 2048 @? "graph storage must not expand 2^81-1 logical nodes" 839 case decodeIndexedTree (encodeBundle root) of 840 Right (Fork _ _) -> pure () 841 other -> assertFailure $ "expected shared fork, got " ++ either id (const "non-fork") other 842 843 , testCase "Indexed CAS: multiple exports become independently loadable objects" $ 844 withSystemTempDirectory "tricu-indexed-store" $ \dir -> do 845 let store = StorePath dir 846 terms = [("left", ofNumber 23), ("right", ofNumber 24)] 847 roots <- unpackIndexedBundleToStore store (encodeBundle $ buildBundle terms) 848 map fst roots @?= map fst terms 849 forM_ (zip roots terms) $ \((_, digest), (_, term)) -> do 850 payload <- getObject store digest >>= maybe (assertFailure "missing indexed object" >> pure BS.empty) pure 851 hashObject indexedTreeDomain payload @?= digest 852 decodeIndexedTree payload @?= Right term 853 854 , testCase "Indexed CAS: reject malformed graphs and ambiguous root containers" $ do 855 let leaf = buildBundle [("root", Leaf)] 856 forward = leaf { bundleNodes = Seq.singleton (BNStem 0) } 857 multiple = buildBundle [("a", Leaf), ("b", Stem Leaf)] 858 case extractBundleRoot forward 0 of 859 Left _ -> pure () 860 Right _ -> assertFailure "accepted cyclic node" 861 case extractBundleRoot leaf 1 of 862 Left _ -> pure () 863 Right _ -> assertFailure "accepted out-of-range root" 864 case decodeIndexedTree (encodeBundle multiple) of 865 Left _ -> pure () 866 Right _ -> assertFailure "accepted multiple root container" 867 868 , testCase "Streaming pack: exact legacy bundle bytes across chunk boundaries" $ do 869 let terms = [(pack ("value" ++ show n), ofNumber n) | n <- [0..128]] ++ 870 [("alias", ofNumber 97), ("tree", Fork (Stem Leaf) (Fork Leaf Leaf))] 871 packed chunkSize = do 872 builder <- foldM (appendTerm chunkSize) emptyTreeTermBundle terms 873 pure (finishTreeTermBundle builder) 874 appendTerm chunkSize builder (name, term) = do 875 let chunks bytes | BS.null bytes = [] 876 | otherwise = let (first, rest) = BS.splitAt chunkSize bytes 877 in first : chunks rest 878 cursor <- foldM (flip feedTreeTerm) (startTreeTerm builder) (chunks (encodeTreeTerm term)) 879 endTreeTerm name cursor 880 forM_ [1, 2, 3, 7, 65536] $ \chunkSize -> do 881 actual <- either assertFailure pure (packed chunkSize) 882 encodeBundle actual @?= encodeBundle (buildBundle terms) 883 verifyBundle actual @?= Right () 884 encodeBundle (finishTreeTermBundle emptyTreeTermBundle) @?= encodeBundle (buildBundle []) 885 886 , testCase "Streaming pack: rejects truncated, unknown and trailing tree bytes" $ do 887 let parse bytes = feedTreeTerm bytes (startTreeTerm emptyTreeTermBundle) >>= endTreeTerm "main" 888 failure bytes expected = case parse bytes of 889 Left problem -> problem @?= expected 890 Right _ -> assertFailure "accepted malformed canonical tree" 891 forM_ [[], [1], [2,0], [2,1,0]] $ \bytes -> 892 failure (BS.pack bytes) "unexpected end of tree term" 893 failure (BS.pack [3]) "unknown tree term tag: 3" 894 failure (BS.pack [0,0]) "trailing bytes after tree term" 895 failure (BS.pack [1,0,3]) "trailing bytes after tree term" 896 897 , testCase "Streaming pack: deep input does not need recursive tree construction" $ do 898 let depth = 16384 899 cursor <- either assertFailure pure $ 900 feedTreeTerm (BS.replicate depth 1 <> BS.singleton 0) (startTreeTerm emptyTreeTermBundle) 901 builder <- either assertFailure pure (endTreeTerm "main" cursor) 902 let bundle = finishTreeTermBundle builder 903 Seq.length (bundleNodes bundle) @?= depth + 1 904 bundleRoots bundle @?= [fromIntegral depth] 905 verifyBundle bundle @?= Right () 906 907 , testCase "Streaming pack: CAS digest verification and byte-equivalent shared exports" $ 908 withSystemTempDirectory "tricu-streaming-pack" $ \dir -> do 909 let store = StorePath dir 910 terms = [("a", Fork (Stem Leaf) (Stem Leaf)), ("b", Stem Leaf), 911 ("alias", Fork (Stem Leaf) (Stem Leaf))] 912 roots <- forM terms $ \(name, term) -> (,) name <$> putTreeTerm store term 913 bundle <- packBundleFromStore store roots 914 encodeBundle bundle @?= encodeBundle (buildBundle terms) 915 -- Cross the actual file reader's 64 KiB boundary with unfinished frames. 916 let depth = 65537 917 deep <- putObject store treeTermDomain (BS.replicate depth 1 <> BS.singleton 0) 918 deepBundle <- packBundleFromStore store [("deep", deep), ("alias", deep)] 919 Seq.length (bundleNodes deepBundle) @?= depth + 1 920 bundleRoots deepBundle @?= replicate 2 (fromIntegral depth) 921 verifyBundle deepBundle @?= Right () 922 let root = snd (head roots) 923 BS.writeFile (objectPath store root) (encodeTreeTerm Leaf) 924 outcome <- try (packBundleFromStore store roots) :: IO (Either SomeException Bundle) 925 case outcome of 926 Left problem -> show problem `containsAll` ["Executable tree digest mismatch", unpack root] 927 Right _ -> assertFailure "packed corrupted CAS bytes" 928 forM_ [BS.empty, BS.pack [2,0], BS.pack [3], BS.pack [0,0]] $ \bytes -> do 929 malformed <- putObject store treeTermDomain bytes 930 rejected <- try (packBundleFromStore store [("bad", malformed)]) :: IO (Either SomeException Bundle) 931 case rejected of 932 Left _ -> pure () 933 Right _ -> assertFailure "packed malformed but correctly hashed CAS bytes" 934 935 , testCase "Arboricx bundle: pack CAS tree terms into transport bundle" $ 936 withSystemTempDirectory "tricu-store" $ \dir -> do 937 let store = StorePath dir 938 term = Fork Leaf (Stem Leaf) 939 root <- putTreeTerm store term 940 bundle <- packBundleFromStore store [("main", root)] 941 bundleRoots bundle @?= [2] 942 let terms = reconstructBundleTermsForTest (bundleNodes bundle) 943 case manifestExports (bundleManifest bundle) of 944 [exported] -> do 945 exportName exported @?= "main" 946 terms V.! fromIntegral (exportRoot exported) @?= term 947 other -> assertFailure $ "unexpected exports: " ++ show other 948 ] 949 950 reconstructBundleTermsForTest :: Seq.Seq BundleNode -> V.Vector T 951 reconstructBundleTermsForTest nodes = V.fromList (go <$> Foldable.toList nodes) 952 where 953 built = V.fromList (go <$> Foldable.toList nodes) 954 go BNLeaf = Leaf 955 go (BNStem child) = Stem (built V.! fromIntegral child) 956 go (BNFork left right) = Fork (built V.! fromIntegral left) (built V.! fromIntegral right) 957 958 countStoredObjects :: StorePath -> IO Int 959 countStoredObjects store@(StorePath root) = do 960 ensureStore store 961 shards <- listDirectory (root </> "objects") 962 fmap sum $ forM shards $ \shard -> do 963 files <- listDirectory (root </> "objects" </> shard) 964 return (length files) 965 966 -- -------------------------------------------------------------------------- 967 -- Wire module tests 968 -- -------------------------------------------------------------------------- 969 970 -- | Helper: create a temporary file-backed DB, store a term, return the 971 972 wireTests :: TestTree 973 wireTests = testGroup "Wire Tests" 974 [ testCase "Indexed bundle: header and manifest declare indexed format" $ do 975 let term = Leaf 976 bundle = buildBundle [("main", term)] 977 wireData = encodeBundle bundle 978 BS.take 8 wireData @?= BS.pack [0x41, 0x52, 0x42, 0x4f, 0x52, 0x49, 0x43, 0x58] 979 case decodeBundle wireData of 980 Left err -> assertFailure $ "decodeBundle failed: " ++ err 981 Right decoded -> do 982 let manifest = bundleManifest decoded 983 tree = manifestTree manifest 984 hashSpec = treeNodeHash tree 985 manifestSchema manifest @?= "arboricx.bundle.manifest.v1" 986 manifestBundleType manifest @?= "tree-calculus-executable-object" 987 manifestClosure manifest @?= ClosureComplete 988 treeCalculus tree @?= "tree-calculus.v1" 989 treeNodePayload tree @?= "arboricx.indexed.payload.v1" 990 nodeHashAlgorithm hashSpec @?= "indexed" 991 nodeHashDomain hashSpec @?= "arboricx.indexed.node.v1" 992 bundleRoots decoded @?= bundleRoots bundle 993 case manifestExports manifest of 994 [exported] -> do 995 exportName exported @?= "main" 996 exportRoot exported @?= head (bundleRoots bundle) 997 exportKind exported @?= "term" 998 exportAbi exported @?= "arboricx.abi.tree.v1" 999 exports -> assertFailure $ "Expected one export, got: " ++ show exports 1000 1001 , testCase "Indexed bundle: deterministic encoding" $ do 1002 let term = Stem (Stem Leaf) 1003 bundle1 = buildBundle [("main", term)] 1004 bundle2 = buildBundle [("main", term)] 1005 encodeBundle bundle1 @?= encodeBundle bundle2 1006 1007 , testCase "Indexed bundle: renaming export changes bytes" $ do 1008 let term = Leaf 1009 mainBundle = buildBundle [("main", term)] 1010 renamedBundle = buildBundle [("validate", term)] 1011 encodeBundle mainBundle /= encodeBundle renamedBundle @? "different export names should produce different bytes" 1012 -- But nodes are identical 1013 bundleNodes mainBundle @?= bundleNodes renamedBundle 1014 1015 , testCase "Indexed bundle: verify rejects out-of-bounds root" $ do 1016 let term = Leaf 1017 bundle = buildBundle [("main", term)] 1018 badBundle = bundle { bundleRoots = [99] } 1019 case verifyBundle badBundle of 1020 Left err -> assertBool ("Expected bounds error, got: " ++ err) ("out of bounds" `isInfixOf` err) 1021 Right () -> assertFailure "Expected out-of-bounds root to be rejected" 1022 1023 , testCase "Indexed bundle: verify rejects out-of-bounds child index" $ do 1024 let bundle = Bundle 1025 { bundleVersion = 1000 1026 , bundleRoots = [1] 1027 , bundleNodes = Seq.fromList [BNLeaf, BNStem 99] 1028 , bundleManifest = (bundleManifest $ buildBundle [("main", Leaf)]) 1029 { manifestRoots = [BundleRoot 1 "default"] 1030 , manifestExports = [BundleExport "main" 1 "term" "arboricx.abi.tree.v1"] 1031 } 1032 , bundleManifestBytes = BS.empty 1033 } 1034 case verifyBundle bundle of 1035 Left err -> assertBool ("Expected bounds error, got: " ++ err) ("references child 99" `isInfixOf` err) 1036 Right () -> assertFailure "Expected out-of-bounds child to be rejected" 1037 1038 , testCase "Indexed bundle: verify rejects acyclic (forward reference)" $ do 1039 let bundle = Bundle 1040 { bundleVersion = 1000 1041 , bundleRoots = [1] 1042 , bundleNodes = Seq.fromList [BNStem 1, BNLeaf] -- index 0 refers to 1 (forward) 1043 , bundleManifest = (bundleManifest $ buildBundle [("main", Leaf)]) 1044 { manifestRoots = [BundleRoot 1 "default"] 1045 , manifestExports = [BundleExport "main" 1 "term" "arboricx.abi.tree.v1"] 1046 } 1047 , bundleManifestBytes = BS.empty 1048 } 1049 case verifyBundle bundle of 1050 Left err -> assertBool ("Expected acyclicity error, got: " ++ err) ("references child 1" `isInfixOf` err) 1051 Right () -> assertFailure "Expected forward reference to be rejected" 1052 1053 , testCase "Indexed bundle: verify rejects duplicate nodes" $ do 1054 let bundle = Bundle 1055 { bundleVersion = 1000 1056 , bundleRoots = [0] 1057 , bundleNodes = Seq.fromList [BNLeaf, BNLeaf] 1058 , bundleManifest = (bundleManifest $ buildBundle [("main", Leaf)]) 1059 { manifestRoots = [BundleRoot 0 "default"] 1060 , manifestExports = [BundleExport "main" 0 "term" "arboricx.abi.tree.v1"] 1061 } 1062 , bundleManifestBytes = BS.empty 1063 } 1064 case verifyBundle bundle of 1065 Left err -> assertBool ("Expected duplicate error, got: " ++ err) ("duplicate" `isInfixOf` err) 1066 Right () -> assertFailure "Expected duplicate nodes to be rejected" 1067 1068 , testCase "Indexed bundle: unpack into filesystem CAS" $ 1069 withSystemTempDirectory "tricu-store" $ \dir -> do 1070 let term = Leaf 1071 bundle = buildBundle [("validateEmail", term)] 1072 wireData = encodeBundle bundle 1073 store = StorePath dir 1074 roots <- unpackBundleToStore store wireData 1075 case roots of 1076 [("validateEmail", root)] -> getTreeTerm store root >>= (@?= Just term) 1077 other -> assertFailure $ "unexpected roots: " ++ show other 1078 1079 , testCase "Indexed bundle: round-trip decode and verify" $ do 1080 let term = Stem (Stem Leaf) 1081 bundle = buildBundle [("main", term)] 1082 wireData = encodeBundle bundle 1083 case decodeBundle wireData of 1084 Left err -> assertFailure $ "decodeBundle failed: " ++ err 1085 Right decoded -> case verifyBundle decoded of 1086 Left err -> assertFailure $ "verifyBundle failed: " ++ err 1087 Right () -> do 1088 bundleRoots decoded @?= bundleRoots bundle 1089 Seq.length (bundleNodes decoded) @?= Seq.length (bundleNodes bundle) 1090 1091 , testCase "Indexed bundle: unsupported manifest semantics rejected" $ do 1092 let term = Leaf 1093 bundle = buildBundle [("main", term)] 1094 manifest = bundleManifest bundle 1095 partialBundle = bundle 1096 { bundleManifest = manifest { manifestClosure = ClosurePartial } 1097 , bundleManifestBytes = BS.empty 1098 } 1099 capabilityBundle = bundle 1100 { bundleManifest = manifest 1101 { manifestRuntime = (manifestRuntime manifest) 1102 { runtimeCapabilities = ["host.io"] } 1103 } 1104 , bundleManifestBytes = BS.empty 1105 } 1106 wrongHashBundle = bundle 1107 { bundleManifest = manifest 1108 { manifestTree = (manifestTree manifest) 1109 { treeNodeHash = (treeNodeHash $ manifestTree manifest) 1110 { nodeHashAlgorithm = "blake3" } 1111 } 1112 } 1113 , bundleManifestBytes = BS.empty 1114 } 1115 case verifyBundle partialBundle of 1116 Left err -> assertBool ("Expected closure error, got: " ++ err) ("closure = complete" `isInfixOf` err) 1117 Right () -> assertFailure "Expected partial closure to be rejected" 1118 case verifyBundle capabilityBundle of 1119 Left err -> assertBool ("Expected capability error, got: " ++ err) ("capabilities" `isInfixOf` err) 1120 Right () -> assertFailure "Expected runtime capabilities to be rejected" 1121 case verifyBundle wrongHashBundle of 1122 Left err -> assertBool ("Expected hash algorithm error, got: " ++ err) ("node hash algorithm" `isInfixOf` err) 1123 Right () -> assertFailure "Expected unsupported node hash algorithm to be rejected" 1124 ] 1125 1126 -- -------------------------------------------------------------------------- 1127 -- Tricu reader tests 1128 -- Smoke-test the tricu-native Arboricx reader against indexed bundles. 1129 -- -------------------------------------------------------------------------- 1130 1131 tricuReaderTests :: TestTree 1132 tricuReaderTests = testGroup "Tricu Reader Tests" 1133 [ testCase "Tricu reader parses indexed bundle (id fixture)" $ check "id" 1134 , testCase "Tricu reader parses indexed bundle (append fixture)" $ check "append" 1135 , testCase "Tricu reader parses indexed bundle (bool fixtures)" $ mapM_ check ["true", "false"] 1136 ] 1137 where 1138 check name = do 1139 bytes <- BS.readFile ("./test/fixtures/" ++ name ++ ".arboricx") 1140 let reply = foldl apply (fixtureExports "arboricx" Map.! "runArboricx") [ofBytes bytes, Leaf] 1141 apply (fixtureExports "base" Map.! "fst") reply @?= trueT 1142 1143 -- -------------------------------------------------------------------------- 1144 -- Byte-list utility tests 1145 -- Expected values built with canonical Haskell-side T constructors. 1146 -- -------------------------------------------------------------------------- 1147 1148 -- | Helpers for byte-list test expectations. 1149 1150 trueT :: T 1151 trueT = Stem Leaf 1152 1153 falseT :: T 1154 falseT = Leaf 1155 1156 pairT :: T -> T -> T 1157 pairT = Fork 1158 1159 portableReply :: Bool -> T -> T -> Bool -> Bool -> T 1160 portableReply success payload rest consumed committed = 1161 pairT (if success then trueT else falseT) 1162 (pairT payload (pairT rest (pairT 1163 (if consumed then trueT else falseT) 1164 (if committed then trueT else falseT)))) 1165 1166 portableDiagnostic :: T -> T -> T -> T -> T -> T 1167 portableDiagnostic code expected found location contexts = 1168 pairT code (pairT expected (pairT found (pairT location contexts))) 1169 1170 -- -------------------------------------------------------------------------- 1171 -- Reusable, located character-scanning primitives 1172 -- -------------------------------------------------------------------------- 1173 1174 locatedLexerTests :: TestTree 1175 locatedLexerTests = testGroup "Located character-scanning library" 1176 [ testCase "checked native scanner handles abstract input" $ do 1177 env <- evaluateCompilerFile "./test/lexerScanImport.tri" 1178 case Map.lookup "scanner" env of 1179 Nothing -> assertFailure "missing portable scanner" 1180 Just scanner -> do 1181 apply scanner (ofString "aa!") @?= 1182 portableReply True 1183 (pairT (ofList [ofNumber 97, ofNumber 97]) 1184 (gatePosition 1 3 2)) 1185 (ofString "!") True False 1186 apply scanner Leaf @?= 1187 portableReply True (pairT Leaf (gatePosition 1 1 0)) Leaf False False 1188 apply scanner (ofString "a\nx") @?= 1189 portableReply True 1190 (pairT (ofList [ofNumber 97]) (gatePosition 1 2 1)) 1191 (ofString "\nx") True False 1192 case Map.lookup "dispatch" env of 1193 Nothing -> assertFailure "missing ordered dispatch" 1194 Just dispatch -> do 1195 apply dispatch (ofNumber 97) @?= ofNumber 1 1196 apply dispatch (ofNumber 98) @?= ofNumber 3 1197 ] 1198 1199 -- -------------------------------------------------------------------------- 1200 -- Single-expression feasibility-gate source lexer 1201 -- -------------------------------------------------------------------------- 1202 1203 gateLexerEnv :: Env 1204 gateLexerEnv = fixtureExports "source.lexer" 1205 {-# NOINLINE gateLexerEnv #-} 1206 1207 gatePosition :: Integer -> Integer -> Integer -> T 1208 gatePosition line column offset = 1209 pairT (ofNumber line) (pairT (ofNumber column) (ofNumber offset)) 1210 1211 gateToken :: Integer -> T -> T -> T -> T 1212 gateToken kind payload start end = 1213 pairT (ofNumber kind) (pairT payload (pairT start end)) 1214 1215 gateLexerTests :: TestTree 1216 gateLexerTests = testGroup "Feasibility-gate source lexer" 1217 [ testCase "module export normalizes under abstract source input" $ 1218 withSystemTempDirectory "gate-lexer-import" $ \dir -> do 1219 env <- evaluateCompilerFile "./test/sourceLexerImport.tri" 1220 Map.lookup "main" env @?= Just Leaf 1221 , testCase "keyword, identifiers, and punctuation carry spans" $ do 1222 check "t (foo : turtle)" 1223 (portableReply True 1224 (ofList 1225 [ gateToken 1 (ofString "t") (gatePosition 1 1 0) (gatePosition 1 2 1) 1226 , gateToken 3 Leaf (gatePosition 1 3 2) (gatePosition 1 4 3) 1227 , gateToken 2 (ofString "foo") (gatePosition 1 4 3) (gatePosition 1 7 6) 1228 , gateToken 5 Leaf (gatePosition 1 8 7) (gatePosition 1 9 8) 1229 , gateToken 2 (ofString "turtle") (gatePosition 1 10 9) (gatePosition 1 16 15) 1230 , gateToken 4 Leaf (gatePosition 1 16 15) (gatePosition 1 17 16) 1231 ]) Leaf True False) 1232 , testCase "decimal digits accumulate and retain leading-zero span" $ do 1233 check "009 x" (portableReply True 1234 (ofList 1235 [ gateToken 7 (ofNumber 9) (gatePosition 1 1 0) (gatePosition 1 4 3) 1236 , gateToken 2 (ofString "x") (gatePosition 1 5 4) (gatePosition 1 6 5) 1237 ]) Leaf True False) 1238 , testCase "quoted string decodes escapes and retains raw source span" $ do 1239 let input = "\"a\\n\\\"b\"" 1240 check input (portableReply True 1241 (ofList [gateToken 8 (ofString "a\n\"b") 1242 (gatePosition 1 1 0) (gatePosition 1 9 8)]) Leaf True False) 1243 , testCase "source string escape repertoire is preserved" $ do 1244 let input = "\"\\t\\r\\f\\b\\\\\\'\"" 1245 decoded = "\t\r\f\b\\'" 1246 width = fromIntegral (length input) 1247 check input (portableReply True 1248 (ofList [gateToken 8 (ofString decoded) 1249 (gatePosition 1 1 0) (gatePosition 1 (width + 1) width)]) 1250 Leaf True False) 1251 , testCase "bracket punctuation retains both half-open spans" $ do 1252 check "[t]" (portableReply True 1253 (ofList 1254 [ gateToken 9 Leaf (gatePosition 1 1 0) (gatePosition 1 2 1) 1255 , gateToken 1 (ofString "t") (gatePosition 1 2 1) (gatePosition 1 3 2) 1256 , gateToken 10 Leaf (gatePosition 1 3 2) (gatePosition 1 4 3) 1257 ]) Leaf True False) 1258 , testCase "equation commas are located tokens, not expression-list syntax" $ 1259 check "[t, t]" (portableReply True (ofList 1260 [ gateToken 9 Leaf (gatePosition 1 1 0) (gatePosition 1 2 1) 1261 , gateToken 1 (ofString "t") (gatePosition 1 2 1) (gatePosition 1 3 2) 1262 , gateToken 20 Leaf (gatePosition 1 3 2) (gatePosition 1 4 3) 1263 , gateToken 1 (ofString "t") (gatePosition 1 5 4) (gatePosition 1 6 5) 1264 , gateToken 10 Leaf (gatePosition 1 6 5) (gatePosition 1 7 6) 1265 ]) Leaf True False) 1266 , testCase "comment delimiters inside strings remain literal" $ 1267 check "\"--|-\"" (portableReply True 1268 (ofList [gateToken 8 (ofString "--|-") 1269 (gatePosition 1 1 0) (gatePosition 1 7 6)]) Leaf True False) 1270 , testCase "invalid and unfinished string escapes return diagnostics" $ do 1271 check "\"\\q\"" (portableReply False 1272 (portableDiagnostic (ofNumber 6) (ofNumber 34) (ofNumber 113) 1273 (gatePosition 1 3 2) Leaf) (ofString "\\q\"") True False) 1274 check "\"hi" (portableReply False 1275 (portableDiagnostic (ofNumber 5) (ofNumber 34) Leaf 1276 (gatePosition 1 1 0) Leaf) Leaf True False) 1277 , testCase "line comment preserves newline token and locations" $ do 1278 check "t --hello\nx" (portableReply True 1279 (ofList 1280 [ gateToken 1 (ofString "t") (gatePosition 1 1 0) (gatePosition 1 2 1) 1281 , gateToken 6 Leaf (gatePosition 1 10 9) (gatePosition 2 1 10) 1282 , gateToken 2 (ofString "x") (gatePosition 2 1 10) (gatePosition 2 2 11) 1283 ]) Leaf True False) 1284 , testCase "block comment consumes internal newlines but preserves end span" $ do 1285 let input = "t |-ab\nc-| t" 1286 check input (portableReply True 1287 (ofList 1288 [ gateToken 1 (ofString "t") (gatePosition 1 1 0) (gatePosition 1 2 1) 1289 , gateToken 1 (ofString "t") (gatePosition 2 5 11) (gatePosition 2 6 12) 1290 ]) Leaf True False) 1291 , testCase "empty block comment consumes both delimiters" $ 1292 check "|- -|t" (portableReply True 1293 (ofList [gateToken 1 (ofString "t") 1294 (gatePosition 1 6 5) (gatePosition 1 7 6)]) Leaf True False) 1295 , testCase "unterminated block comment returns a structured diagnostic" $ 1296 check "t |-oops" (portableReply False 1297 (portableDiagnostic (ofNumber 5) (ofString "-|") Leaf 1298 (gatePosition 1 3 2) Leaf) Leaf True False) 1299 , testCase "comment at EOF leaves no phantom token" $ 1300 check "t --bye" (portableReply True 1301 (ofList [gateToken 1 (ofString "t") 1302 (gatePosition 1 1 0) (gatePosition 1 2 1)]) Leaf True False) 1303 , testCase "lone dash remains a located lexical error" $ 1304 check "-" (portableReply False 1305 (portableDiagnostic (ofNumber 4) Leaf (ofNumber 45) 1306 (gatePosition 1 1 0) Leaf) (ofString "-") False False) 1307 , testCase "newline advances source position" $ do 1308 check "t\nfoo" 1309 (portableReply True 1310 (ofList 1311 [ gateToken 1 (ofString "t") (gatePosition 1 1 0) (gatePosition 1 2 1) 1312 , gateToken 6 Leaf (gatePosition 1 2 1) (gatePosition 2 1 2) 1313 , gateToken 2 (ofString "foo") (gatePosition 2 1 2) (gatePosition 2 4 5) 1314 ]) Leaf True False) 1315 , testCase "invalid character returns structured diagnostic at actual input" $ 1316 check "t #" 1317 (portableReply False 1318 (portableDiagnostic (ofNumber 4) Leaf (ofNumber 35) 1319 (gatePosition 1 3 2) Leaf) (ofString "#") True False) 1320 , testCase "explicit scalar BOM is rejected, matching browser character input" $ 1321 apply (gateLexerEnv Map.! "lexGateSource") (ofString "\xfeff\&t") @?= 1322 portableReply False 1323 (portableDiagnostic (ofNumber 4) Leaf (ofNumber 65279) 1324 (gatePosition 1 1 0) Leaf) (ofString "\xfeff\&t") False False 1325 , testCase "empty input yields empty tokens and no consumption" $ 1326 check "" 1327 (portableReply True Leaf Leaf False False) 1328 , testCase "tab is rejected at its source position" $ 1329 check "t\tfoo" 1330 (portableReply False 1331 (portableDiagnostic (ofNumber 4) Leaf (ofNumber 9) 1332 (gatePosition 1 2 1) Leaf) (ofString "\tfoo") True False) 1333 , testCase "pipe operators retain token kinds and two-character spans" $ do 1334 check "<| |>" (portableReply True 1335 (ofList 1336 [ gateToken 15 Leaf (gatePosition 1 1 0) (gatePosition 1 3 2) 1337 , gateToken 16 Leaf (gatePosition 1 4 3) (gatePosition 1 6 5) 1338 ]) Leaf True False) 1339 , testCase "do and bind arrow have independent located token kinds" $ do 1340 check "do <-" (portableReply True 1341 (ofList 1342 [ gateToken 17 (ofString "do") 1343 (gatePosition 1 1 0) (gatePosition 1 3 2) 1344 , gateToken 18 Leaf (gatePosition 1 4 3) (gatePosition 1 6 5) 1345 ]) Leaf True False) 1346 , testCase "ASCII letter/digit range boundaries remain recognized" $ do 1347 check "AZaz09" (portableReply True 1348 (ofList [gateToken 2 (ofString "AZaz09") 1349 (gatePosition 1 1 0) (gatePosition 1 7 6)]) Leaf True False) 1350 , testCase "longer ASCII identifier scans without discarding suffix" $ 1351 let name = replicate 32 'a' 1352 in check (name ++ " t") 1353 (portableReply True 1354 (ofList 1355 [ gateToken 2 (ofString name) (gatePosition 1 1 0) (gatePosition 1 33 32) 1356 , gateToken 1 (ofString "t") (gatePosition 1 34 33) (gatePosition 1 35 34) 1357 ]) Leaf True False) 1358 ] 1359 where 1360 check input expected = 1361 apply (gateLexerEnv Map.! "lexGateSource") (ofString input) @?= expected 1362 1363 -- -------------------------------------------------------------------------- 1364 -- Single-expression feasibility-gate surface parser 1365 -- -------------------------------------------------------------------------- 1366 1367 -- Library paths select authenticated published interfaces. Unmapped test units 1368 -- use the portable compiler; neither path interprets historical frontend syntax. 1369 evaluateCompilerFile :: FilePath -> IO Env 1370 evaluateCompilerFile path = 1371 case [legacy | (legacy, _, source) <- fst compiledLibraries, 1372 source == maybe path id (stripPrefix "./" path)] of 1373 [legacy] -> pure (fixtureExports legacy) 1374 [] -> compileFixtureFile path 1375 _ -> error "ambiguous compiler fixture module" 1376 1377 -- Test wrappers are compiled by the retained portable stable unit API; library 1378 -- payloads come from authenticated fixtures. Packing remains a host mechanism. 1379 packCompilerFile :: FilePath -> FilePath -> [Text] -> IO () 1380 packCompilerFile input output names = do 1381 env <- evaluateCompilerFile input 1382 let selected = [(name, env Map.! unpack name) | name <- names] 1383 BS.writeFile output (encodeBundle (buildBundle selected)) 1384 1385 -- Immutable pre-optimization worker from the accepted d7b3c37 generation. 1386 -- No source compilation or fresh module builds enter the fast gate. Keeping 1387 -- this separate from the current compiler fixture makes fallback comparisons 1388 -- independent of the implementation under test. 1389 hashHexReference :: Env 1390 hashHexReference = unsafePerformIO $ do 1391 bytes <- BS.readFile "test/fixtures/hash-hex-reference.arboricx" 1392 let digest = unpack (hashObject (Domain "tricu.hash-hex-reference.v1") bytes) 1393 unless (digest == "b5419c577b5a3e8a4458261bfa6934a43cce40eb86e24326aa9a33d6d0552e81") $ 1394 error "hash-hex reference fixture changed; do not refresh it from the candidate" 1395 bundle <- either error pure (decodeBundle bytes) 1396 either error pure (verifyBundle bundle) 1397 let terms = reconstructBundleTerms (bundleNodes bundle) 1398 pure $ Map.fromList 1399 [(unpack (exportName entry), terms V.! fromIntegral (exportRoot entry)) | 1400 entry <- manifestExports (bundleManifest bundle)] 1401 {-# NOINLINE hashHexReference #-} 1402 1403 digestHexTests :: TestTree 1404 digestHexTests = testGroup "guarded digest hex" 1405 [ testCase "all canonical byte values match integer oracle and old kernel" $ 1406 forM_ [0..255] $ \value -> do 1407 let input = ofList [ofNumber value] 1408 expected = if (value >= 48 && value <= 57) || 1409 (value >= 97 && value <= 102) then Stem Leaf else Leaf 1410 apply old input @?= expected 1411 apply current input @?= expected 1412 , testCase "wider, noncanonical and malformed trees retain old behavior" $ 1413 forM_ unusual $ \input -> apply current input @?= apply old input 1414 , testCase "complete digests and late invalid bytes retain old behavior" $ 1415 forM_ strings $ \input -> apply current (ofString input) @?= apply old (ofString input) 1416 , testCase "public digest length and alphabet checks remain exact" $ do 1417 let valid = snd compilerFixture Map.! "source_object.sourceObjectHashValid" 1418 forM_ [0,1,63,64,65] $ \size -> 1419 apply valid (ofString (replicate size 'a')) @?= if size == 64 then Stem Leaf else Leaf 1420 forM_ ['A','G','/', '\NUL', '\255'] $ \byte -> 1421 apply valid (ofString (replicate 63 'a' ++ [byte])) @?= Leaf 1422 ] 1423 where 1424 fix = hashHexReference Map.! "fix" 1425 old = apply fix (hashHexReference Map.! "worker") 1426 current = snd compilerFixture Map.! "source_object.sourceObjectHashHex" 1427 pad Leaf count = foldr (\_ rest -> Fork Leaf rest) Leaf [1..count] 1428 pad (Fork bit rest) count = Fork bit (pad rest count) 1429 pad other _ = other 1430 unusual = 1431 map (ofList . (:[]) . ofNumber) [256,257,511,512,65535] ++ 1432 [ofList [pad (ofNumber value) count] | 1433 value <- [0,1,47,48,49,57,58,96,97,102,103,255], count <- [1,2]] ++ 1434 map (ofList . (:[])) 1435 [Stem Leaf, Stem (Stem Leaf), Fork (Fork Leaf Leaf) Leaf, 1436 Fork (Stem (Stem Leaf)) Leaf, Fork Leaf (Stem Leaf)] ++ 1437 [Leaf, Stem Leaf, Stem (Stem Leaf), 1438 Fork (ofNumber 97) (Stem Leaf), Fork (ofNumber 103) (Stem Leaf)] 1439 strings = ["0123456789abcdef", replicate 64 'a', replicate 64 '0', 1440 replicate 64 'f', concat (replicate 4 "0123456789abcdef"), 1441 'A' : replicate 63 'a', replicate 63 'a' ++ "G", 1442 replicate 31 'a' ++ "\NUL" ++ replicate 32 'a', 1443 replicate 31 'a' ++ "\255" ++ replicate 32 'a'] 1444 1445 compilerFixtureTests :: TestTree 1446 compilerFixtureTests = testGroup "self-built reference compiler fixture" 1447 [ testCase "matches all recorded reference source inputs" $ do 1448 length (fst compilerFixture) @?= 34 1449 assertBool "missing stable binding-pass exports" 1450 (all (`Map.member` snd compilerFixture) 1451 ["source_binding.bindingTransform", "source_binding.bindingInspect", 1452 "source_binding_analysis.analyze", "source_binding_support.choose", 1453 "source_binding_support.not"]) 1454 assertBool "fixture-only patterns still in compiler closure" 1455 (all (\(_, alias, _) -> alias /= "patterns") (fst compilerFixture)) 1456 assertBool "patterns missing from support cohort" 1457 (any (\(_, alias, _) -> alias == "patterns") (fst supportFixture)) 1458 assertBool "missing name index exports" 1459 (all (`Map.member` snd compilerFixture) 1460 ["source_index.sourceIndexBuild", "source_index.sourceIndexGet", "source_index.sourceIndexPut"]) 1461 assertBool "missing unit compiler export" 1462 (Map.member "source_unit.compileSource" (snd compilerFixture)) 1463 , testCase "direct fixture interfaces exclude imports and reject unknown modules" $ do 1464 let http = fixtureExports "http" 1465 assertBool "missing owned HTTP export" (Map.member "parseHeaders" http) 1466 forM_ ["append", "kernel", "source_unit.compileSource"] $ \name -> 1467 assertBool ("leaked imported/injected name: " ++ name) (Map.notMember name http) 1468 rejected <- try (evaluate (Map.size (fixtureExports "missing.fixture"))) :: IO (Either SomeException Int) 1469 case rejected of 1470 Left _ -> pure () 1471 Right _ -> assertFailure "unknown fixture module silently accepted" 1472 , testCase "portable wrapper compilation keeps unused definitions lazy" $ do 1473 env <- compileFixtureDefinitions Map.empty 1474 "unused = (x : x x) (x : x x)\nmain = t" 1475 Map.keys env @?= ["main", "unused"] 1476 Map.lookup "main" env @?= Just Leaf 1477 , testCase "portable wrapper compilation rejects invalid source without fallback" $ do 1478 forM_ ["main = missing", "main = t\nmain = t", "t", "main = #"] $ \source -> do 1479 rejected <- try (compileFixtureDefinitions Map.empty source) :: IO (Either SomeException Env) 1480 case rejected of 1481 Left err -> assertBool "expected portable diagnostic" 1482 ("portable test compilation failed" `isInfixOf` show err) 1483 Right _ -> assertFailure ("accepted invalid wrapper: " ++ source) 1484 , testCase "portable wrapper compilation uses only explicit externals" $ do 1485 env <- compileFixtureDefinitions (Map.singleton "outside" (Stem Leaf)) "main = outside t" 1486 Map.keys env @?= ["main"] 1487 Map.lookup "main" env @?= Just (Fork Leaf Leaf) 1488 rejected <- try (compileFixtureDefinitions (Map.singleton "outside" Leaf) "outside = t") 1489 :: IO (Either SomeException Env) 1490 case rejected of 1491 Left _ -> pure () 1492 Right _ -> assertFailure "wrapper shadows an imported name" 1493 , testCase "every reference compiler module authors native headers" $ do 1494 forM_ (fst compilerFixture) $ \(_, _, path) -> do 1495 source <- readTestSource path 1496 assertBool ("missing native export: " ++ path) 1497 (any ("!export " `isPrefixOf`) (lines source)) 1498 assertBool ("legacy import: " ++ path) 1499 (not (any ("!import \"" `isPrefixOf`) (lines source))) 1500 , testCase "reference source rejects corrupt bytes and unsafe digests" $ 1501 withSystemTempDirectory "tricu-reference-source" $ \dir -> do 1502 let bytes = BS.pack [120,32,61,32,116,10] 1503 pin = unpack (hashObject (Domain (pack "arboricx.source.v1")) bytes) 1504 path = dir </> (pin ++ ".tri") 1505 rejected action = do 1506 result <- try action :: IO (Either SomeException BS.ByteString) 1507 case result of 1508 Left _ -> pure () 1509 Right _ -> assertFailure "invalid reference source accepted" 1510 rejected (readPinnedSource dir pin) 1511 BS.writeFile path bytes 1512 readPinnedSource dir pin >>= (@?= bytes) 1513 BS.writeFile path (bytes <> BS.singleton 32) 1514 rejected (readPinnedSource dir pin) 1515 rejected (readPinnedSource dir "../source") 1516 , testCase "native prelude retains exactly its dependency re-exports" $ do 1517 let exports = snd compilerFixture 1518 owned alias = Map.fromList 1519 [(drop (length alias + 1) name, tree) | (name, tree) <- Map.toList exports, 1520 (alias ++ ".") `isPrefixOf` name] 1521 owned "prelude" @?= Map.unions (map owned ["base", "bytes", "conversions"]) 1522 , testCase "native base effect interpreter and guard use the rebuilt kernels" $ do 1523 env <- pure (fixtureExports "base") 1524 let pureValue value = Fork (ofNumber 0) value 1525 success value rest = Fork (Stem Leaf) (Fork value rest) 1526 failure value = Fork Leaf (Fork value Leaf) 1527 value = ofNumber 42 1528 exception = Fork (ofNumber 99) 1529 (Fork (ofString "wanted") (Fork value Leaf)) 1530 handler = apply (env Map.! "const") 1531 (apply (env Map.! "const") (pureValue (ofNumber 9))) 1532 handled = foldl apply (env Map.! "handleM") [ofString "wanted", handler, exception] 1533 predicate = apply (env Map.! "const") (Fork Leaf Leaf) 1534 apply (env Map.! "runM") (pureValue value) @?= success value Leaf 1535 apply (env Map.! "runM") exception @?= failure value 1536 apply (env Map.! "runM") (Stem Leaf) @?= Stem Leaf 1537 handled @?= pureValue (ofNumber 9) 1538 foldl apply (env Map.! "guardC") [ofString "bad", predicate, value, ofNumber 7] 1539 @?= success value (ofNumber 7) 1540 , testCase "mixed support reuse requires all provider trees, including unused ones" $ do 1541 let before = Map.fromList [("P.used",1),("P.unused",2)] 1542 source = "!import P as X\n!export f\nf x = X.used x\n" 1543 verifyReuseProviders before before source @?= Right () 1544 forM_ ["P.used", "P.unused"] $ \name -> 1545 verifyReuseProviders before (Map.insert name 3 before) source @?= 1546 Left "provider kernels changed: P" 1547 forM_ [Map.insert "P.extra" 4 before, Map.delete "P.unused" before, Map.empty] $ \after -> 1548 verifyReuseProviders before after source @?= Left "full export set changed: P" 1549 verifyReuseProviders Map.empty before source @?= Left "missing original provider: P" 1550 , testCase "mixed support reuse no longer exempts shadowed imports" $ do 1551 let before = Map.fromList [("P.used",1),("P.unused",2)] 1552 shadowed = "!import P as X\n!export f\nf used = used\n" 1553 verifyReuseProviders before before shadowed @?= Right () 1554 verifyReuseProviders before (Map.insert "P.used" 4 before) shadowed @?= 1555 Left "provider kernels changed: P" 1556 , testCase "mixed support reuse checks standalone reexports and all import forms" $ do 1557 let before = Map.fromList [("P.used",1),("P.unused",2),("PQ.other",3)] 1558 forM_ ["!import P", "!import P *", "!import P as X", "!export * from P", 1559 " !import P *", "!import P *\n!export * from P"] $ \header -> do 1560 let source = header ++ "\n!export f\nf x = x\n" 1561 verifyReuseProviders before before source @?= Right () 1562 verifyReuseProviders before (Map.insert "PQ.other" 4 before) source @?= Right () 1563 verifyReuseProviders before (Map.insert "P.used" 4 before) source @?= 1564 Left "provider kernels changed: P" 1565 verifyReuseProviders before before "!export * from Missing\n" @?= 1566 Left "missing original provider: Missing" 1567 , testCase "mixed support reuse rejects changed namespace collision outcomes" $ do 1568 let before = Map.fromList [("P.used",1),("Q.used",1)] 1569 source = "!import P *\n!import Q *\n!export f\nf x = x\n" 1570 verifyReuseProviders before before source @?= Right () 1571 verifyReuseProviders before (Map.insert "Q.used" 2 before) source @?= 1572 Left "provider kernels changed: Q" 1573 , testCase "mixed support reuse rejects unsupported dependency directives" $ do 1574 let before = Map.singleton "P.used" 1 1575 forM_ ["!import", "!import P as", "!import P * extra", "!import P as *", 1576 "!import \"P\" *", "!export * from", "!export * from P extra", 1577 "!source P", "!unknown P", "!import " ++ replicate 64 'a' ++ " *"] $ \header -> 1578 assertBool ("accepted unsupported reuse header: " ++ header) 1579 (case verifyReuseProviders before before (header ++ "\n") of Left _ -> True; _ -> False) 1580 , testCase "mixed support reuse compares canonical trees, not bundle-local node indices" $ do 1581 let original = buildBundle [("P.used", Stem Leaf), ("P.unused", Fork Leaf Leaf)] 1582 reordered = buildBundle [("Q.extra", ofNumber 42), ("P.unused", Fork Leaf Leaf), 1583 ("P.used", Stem Leaf)] 1584 changed = buildBundle [("P.used", Stem Leaf), ("P.unused", Fork Leaf (Stem Leaf))] 1585 forM_ [original, reordered, changed] $ \bundle -> verifyBundle bundle @?= Right () 1586 case canonicalEnvs [original, reordered, changed] of 1587 [before, same, different] -> do 1588 verifyReuseProviders before same "!import P *\n" @?= Right () 1589 verifyReuseProviders before different "!import P *\n" @?= 1590 Left "provider kernels changed: P" 1591 _ -> assertFailure "incorrect canonical environment count" 1592 , testCase "mixed support reuse permits provider-free unchanged programs" $ 1593 verifyReuseProviders Map.empty Map.empty "-- no dependencies\n!export main\nmain = t\n" @?= Right () 1594 , testCase "support publication allows new rebuilt sources, not relabeled retained sources" $ do 1595 let old = [["module","http","http","lib/http.tri","old-source"]] 1596 new = [["module","http","http","lib/http.tri","new-source"]] 1597 verifySupportSources old new (Set.singleton "http") @?= Right () 1598 verifySupportSources old old Set.empty @?= Right () 1599 verifySupportSources old new Set.empty @?= 1600 Left "retained source fingerprint changed: http" 1601 verifySupportSources old [["module","other","http","lib/http.tri","new-source"]] 1602 (Set.singleton "http") @?= Left "support cohort identity changed" 1603 , testCase "compiler-to-support relocation requires exact identity and a rebuild" $ do 1604 let old = [["module","http","http","lib/http.tri","old-source"]] 1605 added = ["module","patterns","patterns","lib/patterns.tri","new-source"] 1606 declared = ["relocated","compiler-library","patterns","patterns","lib/patterns.tri"] 1607 current = old ++ [added, declared] 1608 verifyRelocatedSupportSources old current (Set.singleton "patterns") @?= Right () 1609 forM_ [old ++ [added], old ++ [added, declared, declared], 1610 old ++ [added, declared, ["module","extra","extra","lib/extra.tri","pin"]], 1611 old ++ [["module","patterns","patterns","lib/other.tri","pin"], declared]] $ \bad -> 1612 assertBool "unauthorized relocation accepted" 1613 (case verifyRelocatedSupportSources old bad (Set.singleton "patterns") of Left _ -> True; _ -> False) 1614 verifyRelocatedSupportSources old current Set.empty @?= Left "invalid patterns cohort relocation" 1615 verifySupportSources old current (Set.singleton "patterns") @?= Left "support cohort identity changed" 1616 , testCase "IO support additions require declarations, exact membership and rebuilds" $ do 1617 original <- map words . lines <$> readFile "test/fixtures/support-origin.v1" 1618 current <- map words . lines <$> readFile "test/fixtures/support-cohort-v4.v1" 1619 let built = Set.fromList [name | ["rebuilt",name,_,_,_] <- current] 1620 declarations = [row | row <- current, take 1 row == ["added"]] 1621 length declarations @?= 10 1622 verifyIOSupportSources original current built @?= Right () 1623 forM_ [filter ((/= ["added"]) . take 1) current, 1624 current ++ [head declarations], 1625 current ++ [["module","unexpected","unexpected","lib/unexpected.tri","pin"]], 1626 map (\row -> if take 3 row == ["module","io_v2","io_v2"] 1627 then ["module","io_v2","io_v2","lib/wrong.tri","pin"] else row) current] $ \bad -> 1628 assertBool "unauthorized support growth accepted" 1629 (case verifyIOSupportSources original bad built of Left _ -> True; _ -> False) 1630 assertBool "unrebuilt IO addition accepted" 1631 (case verifyIOSupportSources original current (Set.delete "io_v2" built) of Left _ -> True; _ -> False) 1632 , testCase "compiler IO cohort has exact disjoint ownership and rebuilt support" $ do 1633 original <- map words . lines <$> readFile "test/fixtures/support-origin.v1" 1634 old <- map words . lines <$> readFile "test/fixtures/support-cohort-v4.v1" 1635 let moved = [("io_v2","lib/io-v2.tri"), ("io_v2_storage","lib/io-v2/storage.tri"), 1636 ("io_v2_text","lib/io-v2/text.tri")] 1637 names = map fst moved 1638 keep row = case row of 1639 ["module",_,name,_,_] -> name `notElem` names 1640 ["added",name,_,_] -> name `notElem` names 1641 _ -> True 1642 ioRow = ["module","io","io","lib/io.tri","source"] 1643 current = filter keep old ++ [ioRow, ["added","io","io","lib/io.tri"]] ++ 1644 [["compiler-provider",name,path] | (name,path) <- moved] 1645 compiler = [["module",name,name,path,"source"] | 1646 (name,path) <- moved ++ [("compiler_io","lib/source/io-v2.tri")]] 1647 built = Set.fromList [name | ["module",_,name,_,_] <- current] 1648 check c rows b = verifyCompilerIOSupportSources c original rows b 1649 check compiler current built @?= Right () 1650 forM_ [tail compiler, compiler ++ [ioRow], compiler ++ [head compiler], 1651 map (\row -> if take 3 row == ["module","io_v2","io_v2"] 1652 then ["module","io_v2","io_v2","lib/wrong.tri","source"] else row) compiler] $ \bad -> 1653 assertBool "incorrect compiler ownership accepted" (case check bad current built of Left _ -> True; _ -> False) 1654 forM_ [filter ((/= ["compiler-provider"]) . take 1) current, 1655 current ++ [["compiler-provider","io_v2","lib/io-v2.tri"]], 1656 current ++ [["module","io_v2","io_v2","lib/io-v2.tri","source"]], 1657 filter (/= ioRow) current] $ \bad -> 1658 assertBool "incorrect support ownership accepted" (case check compiler bad built of Left _ -> True; _ -> False) 1659 assertBool "unrebuilt general IO accepted" 1660 (case check compiler current (Set.delete "io" built) of Left _ -> True; _ -> False) 1661 , testCase "canonical IO ownership rejects extra providers and incomplete rebuilds" $ do 1662 original <- map words . lines <$> readFile "test/fixtures/support-origin.v1" 1663 let moved = [("io","lib/io.tri"), ("io_storage","lib/io/storage.tri"), ("io_text","lib/io/text.tri")] 1664 additions = [("arboricx_handlers","lib/arboricx/handlers.tri"), 1665 ("arboricx_streaming","lib/arboricx/streaming.tri"), 1666 ("handler_artifacts","lib/handlers/artifacts.tri"), ("handler_log","lib/handlers/log.tri"), 1667 ("handlers","lib/handlers.tri"), ("io_artifacts","lib/io/artifacts.tri"), 1668 ("io_http","lib/io/http-transport.tri")] 1669 modules = [["module",name,name,path,"source"] | (name,path) <- additions] 1670 patternRow = ["module","patterns","patterns","lib/patterns.tri","source"] 1671 current = original ++ [patternRow, ["relocated","compiler-library","patterns","patterns","lib/patterns.tri"]] ++ 1672 modules ++ [["added",name,name,path] | (name,path) <- additions] ++ 1673 [["compiler-provider",name,path] | (name,path) <- moved] 1674 compiler = [["module",name,name,path,"source"] | (name,path) <- moved ++ [("compiler_io","lib/source/io.tri")]] 1675 built = Set.fromList ("patterns":map fst additions) 1676 check = verifyCanonicalIOSupportSources 1677 check compiler original current built @?= Right () 1678 forM_ [tail compiler, compiler ++ [head compiler]] $ \bad -> 1679 assertBool "incorrect canonical compiler ownership accepted" 1680 (case check bad original current built of Left _ -> True; _ -> False) 1681 forM_ [current ++ [["module","io","io","lib/io.tri","source"]], 1682 current ++ [["compiler-provider","extra","lib/extra.tri"]], 1683 filter ((/= ["added"]) . take 1) current] $ \bad -> 1684 assertBool "incorrect canonical support ownership accepted" 1685 (case check compiler original bad built of Left _ -> True; _ -> False) 1686 assertBool "unrebuilt canonical addition accepted" 1687 (case check compiler original current (Set.delete "io_artifacts" built) of Left _ -> True; _ -> False) 1688 , testCase "binary64 cohort requires exact identities, ownership and complete rebuilds" $ do 1689 original <- map words . lines <$> readFile "test/fixtures/support-origin.v1" 1690 current <- map words . lines <$> readFile "test/fixtures/support-library.v1" 1691 compiler <- map words . lines <$> readFile "test/fixtures/compiler-library.v1" 1692 let names = "binary64":"demos_binary64":map ("binary64_" ++) 1693 ["adders","chains","fast","finite","ieee","kernels","layouts", 1694 "near","product_quotient","quantize","working"] 1695 built = Set.fromList [name | ["rebuilt",name,_,_,_] <- current] 1696 check = verifyBinary64SupportSources compiler original 1697 rejects rows b = assertBool "invalid binary64 cohort accepted" 1698 (case check rows b of Left _ -> True; _ -> False) 1699 check current built @?= Right () 1700 forM_ names $ \name -> do 1701 let owns ["module",_,alias,_,_] = alias == name 1702 owns _ = False 1703 declares ["added",_,alias,_] = alias == name 1704 declares _ = False 1705 rows = filter owns current 1706 rejects (filter (not . owns) current) built 1707 rejects (filter (not . declares) current) built 1708 rejects (current ++ rows) built 1709 rejects current (Set.delete name built) 1710 rejects (map (\row -> if owns row then take 3 row ++ ["lib/wrong.tri","source"] else row) current) built 1711 assertBool "duplicate compiler ownership accepted" 1712 (case verifyBinary64SupportSources (compiler ++ rows) original current built of Left _ -> True; _ -> False) 1713 rejects (current ++ [["module","extra","extra","lib/extra.tri","source"]]) built 1714 assertBool "historical V6 validator accepted V7 membership" 1715 (case verifyCanonicalIOSupportSources compiler original current built of Left _ -> True; _ -> False) 1716 , testCase "rebuilt export sets follow the new manifest, not the old interface" $ do 1717 let manifest kind names = unlines ("arboricx.module-manifest.v1" : 1718 ["export " ++ name ++ " " ++ kind ++ " hash arboricx.abi.tree.v1" | name <- names]) 1719 names = Set.fromList ["parseHeaders", "httpAsciiBit"] 1720 forM_ ["arboricx.tree-term.v1", "arboricx.indexed-tree.v1"] $ \kind -> do 1721 verifySupportExports names (manifest kind (Set.toList names)) @?= Right () 1722 forM_ [["parseHeaders"], ["parseHeaders", "httpAsciiBit", "byte7BitsOr"], 1723 ["parseHeaders", "httpAsciiBit", "httpAsciiBit"]] $ \bad -> 1724 verifySupportExports names (manifest kind bad) @?= Left "rebuilt manifest export set mismatch" 1725 verifySupportExports names (manifest "unknown-kind" (Set.toList names)) 1726 @?= Left "rebuilt manifest export set mismatch" 1727 verifySupportExports names (unlines ["arboricx.module-manifest.v1", 1728 "export parseHeaders arboricx.tree-term.v1 hash arboricx.abi.tree.v1", 1729 "export httpAsciiBit arboricx.indexed-tree.v1 hash arboricx.abi.tree.v1"]) 1730 @?= Left "rebuilt manifest export set mismatch" 1731 , testCase "rebuilt consumers reject stale support provider manifests" $ do 1732 let providers = Map.singleton "http" "new-manifest" 1733 rows pin = [["local","http",pin,"facade-source"], 1734 ["pin","http",pin,"parseHeaders","tree"]] 1735 verifySupportDependencies providers (rows "new-manifest") @?= Right () 1736 verifySupportDependencies providers (rows "old-manifest") @?= 1737 Left "stale support dependency: http" 1738 verifySupportDependencies providers 1739 [["local","http","new-manifest","source"], ["pin","http","old-manifest","f","tree"]] 1740 @?= Left "stale support dependency: http" 1741 verifySupportDependencies providers 1742 (replicate 2 ["local","http","new-manifest","source"]) @?= Left "duplicate local dependency" 1743 , testCase "native staging preserves namespaces and explicit exports" $ do 1744 env <- pure (fixtureExports "source.map") 1745 let row = pairT (ofString "example") (pairT (ofString "example") 1746 (pairT (ofString "example.tri") 1747 (pairT (ofList [ofString "native_math"]) (ofString "native")))) 1748 source = ofString "!import native_math as N\n!export main\nmain = N.add 2 3\n" 1749 prepare text = apply (apply (apply (env Map.! "sourceMapPrepareBody") row) text) Leaf 1750 prepare source @?= pairT (Stem Leaf) (pairT source Leaf) 1751 apply (apply (env Map.! "sourceMapOverlay") row) source @?= source 1752 forM_ ["", "!source other\n", "!source extra\n"] $ \bad -> 1753 apply (fixtureExports "base" Map.! "fst") (prepare (ofString bad)) @?= Leaf 1754 , testCase "shared native header derives ordered deduplicated edges and masks positions" $ do 1755 env <- pure (fixtureExports "source.module") 1756 let header = "-- lead\r\n!import math\n!export * from base\n!import math as M\n!source build_only\n!export main\n" 1757 body = "main = missing\n" 1758 masked = map (\c -> if c == '\n' || c == '\r' then c else ' ') header ++ body 1759 wordsT = ofList . map ofString 1760 staged = pairT (wordsT ["math", "base", "build_only"]) 1761 (pairT (ofList [wordsT ["math"], wordsT ["math", "as", "M"]]) 1762 (pairT (ofList [wordsT ["*", "from", "base"], wordsT ["main"]]) (ofString masked))) 1763 apply (env Map.! "sourceModuleHeaderStage") (ofString (header ++ body)) @?= 1764 pairT (Stem Leaf) (pairT staged Leaf) 1765 , testCase "native header rejects redundant edges and bare hashes at original locations" $ do 1766 env <- pure (fixtureExports "source.module") 1767 let bad sources = forM_ sources $ \source -> 1768 apply (fixtureExports "base" Map.! "fst") (apply (env Map.! "sourceModuleHeaderStage") (ofString source)) @?= Leaf 1769 bad ["!source dep\n!import dep\n", "!import dep\n!source dep\n", 1770 "!source dep\n!export * from dep\n", "!source dep\n!source dep\n", 1771 "!import " ++ replicate 64 'a' ++ "\n", "!use dep *\n", "!import \"dep\" !Local\n"] 1772 let outcome = apply (env Map.! "sourceModuleHeaderStage") 1773 (ofString "-- lead\n\n!use dep *\n") 1774 problem = apply (fixtureExports "base" Map.! "fst") (apply (fixtureExports "base" Map.! "snd") outcome) 1775 apply (fixtureExports "parser" Map.! "parserDiagnosticLocation") problem @?= gatePosition 3 1 9 1776 , testCase "native math exports are public only and general equality remains" $ do 1777 env <- pure (fixtureExports "math.native") 1778 assertBool "missing math decimal" (Map.member "decimal" env) 1779 let owned = [name | name <- Map.keys (snd compilerFixture), "native_math." `isPrefixOf` name] 1780 length owned @?= 27 1781 forM_ ["divMod", "quotient", "remainder", "power"] $ \name -> 1782 assertBool ("missing native arithmetic export: " ++ name) (Map.member name env) 1783 assertBool "private math helper exported" (not ("native_math.compareStep" `elem` owned)) 1784 prelude <- pure (fixtureExports "prelude") 1785 assertBool "general equality removed" (Map.member "equal?" prelude) 1786 ] 1787 1788 gateParserEnv :: Env 1789 gateParserEnv = fixtureExports "source.parser" 1790 {-# NOINLINE gateParserEnv #-} 1791 1792 gateAst :: Integer -> T -> T -> T -> T 1793 gateAst kind payload start end = 1794 pairT (ofNumber kind) (pairT payload (pairT start end)) 1795 1796 gateParserTests :: TestTree 1797 gateParserTests = testGroup "Feasibility-gate source parser" 1798 [ testCase "self-built parser export executes through its wrapper" $ do 1799 env <- evaluateCompilerFile "./test/sourceGateParserImport.tri" 1800 Map.lookup "main" env @?= Just 1801 (portableReply True 1802 (gateAst 1 Leaf (gatePosition 1 1 0) (gatePosition 1 2 1)) 1803 Leaf True False) 1804 , testCase "leaf retains its source span" $ 1805 check "t" (portableReply True 1806 (gateAst 1 Leaf (gatePosition 1 1 0) (gatePosition 1 2 1)) 1807 Leaf True False) 1808 , testCase "decimal integer is a located value" $ 1809 check "007" (portableReply True 1810 (gateAst 6 (ofNumber 7) (gatePosition 1 1 0) (gatePosition 1 4 3)) 1811 Leaf True False) 1812 , testCase "string is a located decoded value" $ 1813 check "\"hi\"" (portableReply True 1814 (gateAst 7 (ofString "hi") (gatePosition 1 1 0) (gatePosition 1 5 4)) 1815 Leaf True False) 1816 , testCase "empty list retains its brackets and canonical empty payload" $ 1817 check "[]" (portableReply True 1818 (gateAst 8 Leaf (gatePosition 1 1 0) (gatePosition 1 3 2)) 1819 Leaf True False) 1820 , testCase "list elements are separate atoms, not an application" $ 1821 let leaf1 = gateAst 1 Leaf (gatePosition 1 2 1) (gatePosition 1 3 2) 1822 leaf2 = gateAst 1 Leaf (gatePosition 1 4 3) (gatePosition 1 5 4) 1823 in check "[t t]" (portableReply True 1824 (gateAst 8 (ofList [leaf1, leaf2]) 1825 (gatePosition 1 1 0) (gatePosition 1 6 5)) Leaf True False) 1826 , testCase "nested lists and grouped applications retain distinct spans" $ 1827 let innerLeaf = gateAst 1 Leaf 1828 (gatePosition 1 3 2) (gatePosition 1 4 3) 1829 innerList = gateAst 8 (ofList [innerLeaf]) 1830 (gatePosition 1 2 1) (gatePosition 1 5 4) 1831 left = gateAst 1 Leaf (gatePosition 1 7 6) (gatePosition 1 8 7) 1832 right = gateAst 1 Leaf (gatePosition 1 9 8) (gatePosition 1 10 9) 1833 app = gateAst 3 (pairT left right) 1834 (gatePosition 1 7 6) (gatePosition 1 10 9) 1835 group = gateAst 5 app (gatePosition 1 6 5) (gatePosition 1 11 10) 1836 in check "[[t] (t t)]" (portableReply True 1837 (gateAst 8 (ofList [innerList, group]) 1838 (gatePosition 1 1 0) (gatePosition 1 12 11)) Leaf True False) 1839 , testCase "unclosed list reports the expected closing bracket" $ 1840 check "[t" (portableReply False 1841 (portableDiagnostic (ofNumber 2) (ofNumber 10) Leaf Leaf Leaf) 1842 Leaf True False) 1843 , testCase "unexpected separator retains its token and location" $ 1844 check "[t : t]" (portableReply False 1845 (portableDiagnostic (ofNumber 2) (ofNumber 10) (ofNumber 5) 1846 (gatePosition 1 4 3) Leaf) 1847 (ofList 1848 [ gateToken 5 Leaf (gatePosition 1 4 3) (gatePosition 1 5 4) 1849 , gateToken 1 (ofString "t") 1850 (gatePosition 1 6 5) (gatePosition 1 7 6) 1851 , gateToken 10 Leaf (gatePosition 1 7 6) (gatePosition 1 8 7) 1852 ]) True False) 1853 , testCase "application is left associative" $ 1854 let name = gateAst 2 (ofString "f") (gatePosition 1 1 0) (gatePosition 1 2 1) 1855 arg1 = gateAst 1 Leaf (gatePosition 1 3 2) (gatePosition 1 4 3) 1856 arg2 = gateAst 1 Leaf (gatePosition 1 5 4) (gatePosition 1 6 5) 1857 first = gateAst 3 (pairT name arg1) (gatePosition 1 1 0) (gatePosition 1 4 3) 1858 in check "f t t" (portableReply True 1859 (gateAst 3 (pairT first arg2) (gatePosition 1 1 0) (gatePosition 1 6 5)) 1860 Leaf True False) 1861 , testCase "let retains binding, value, body, and complete span" $ 1862 let name = gateToken 2 (ofString "x") 1863 (gatePosition 1 5 4) (gatePosition 1 6 5) 1864 value = gateAst 1 Leaf (gatePosition 1 9 8) (gatePosition 1 10 9) 1865 body = gateAst 2 (ofString "x") 1866 (gatePosition 1 14 13) (gatePosition 1 15 14) 1867 in check "let x = t in x" (portableReply True 1868 (gateAst 9 (pairT name (pairT value body)) 1869 (gatePosition 1 1 0) (gatePosition 1 15 14)) Leaf True False) 1870 , testCase "where retains the span of the whole expression" $ 1871 let name = gateToken 2 (ofString "x") 1872 (gatePosition 1 9 8) (gatePosition 1 10 9) 1873 value = gateAst 1 Leaf (gatePosition 1 13 12) (gatePosition 1 14 13) 1874 body = gateAst 2 (ofString "x") 1875 (gatePosition 1 1 0) (gatePosition 1 2 1) 1876 in check "x where x = t" (portableReply True 1877 (gateAst 9 (pairT name (pairT value body)) 1878 (gatePosition 1 1 0) (gatePosition 1 14 13)) Leaf True False) 1879 , testCase "a top-level newline does not silently join application operands" $ 1880 check "t\n t" (portableReply False 1881 (portableDiagnostic (ofNumber 2) Leaf (ofNumber 6) 1882 (gatePosition 1 2 1) Leaf) 1883 (ofList 1884 [ gateToken 6 Leaf 1885 (gatePosition 1 2 1) (gatePosition 2 1 2) 1886 , gateToken 1 (ofString "t") 1887 (gatePosition 2 2 3) (gatePosition 2 3 4) 1888 ]) True False) 1889 , testCase "parentheses permit multiline application with original spans" $ 1890 let left = gateAst 1 Leaf 1891 (gatePosition 1 2 1) (gatePosition 1 3 2) 1892 right = gateAst 1 Leaf 1893 (gatePosition 2 2 4) (gatePosition 2 3 5) 1894 application = gateAst 3 (pairT left right) 1895 (gatePosition 1 2 1) (gatePosition 2 3 5) 1896 in check "(t\n t)" (portableReply True 1897 (gateAst 5 application 1898 (gatePosition 1 1 0) (gatePosition 2 4 6)) Leaf True False) 1899 , testCase "pipe retains source order and direction before elaboration" $ 1900 let value = gateAst 6 (ofNumber 2) 1901 (gatePosition 1 1 0) (gatePosition 1 2 1) 1902 name = gateAst 2 (ofString "f") 1903 (gatePosition 1 6 5) (gatePosition 1 7 6) 1904 in check "2 |> f" (portableReply True 1905 (gateAst 10 (pairT (ofNumber 16) (pairT value name)) 1906 (gatePosition 1 1 0) (gatePosition 1 7 6)) Leaf True False) 1907 , testCase "do retains operator, located binders, and statements" $ 1908 let op = gateAst 2 (ofString "b") 1909 (gatePosition 1 4 3) (gatePosition 1 5 4) 1910 name = gateToken 2 (ofString "x") 1911 (gatePosition 2 3 7) (gatePosition 2 4 8) 1912 action = gateAst 6 (ofNumber 2) 1913 (gatePosition 2 8 12) (gatePosition 2 9 13) 1914 body = gateAst 2 (ofString "x") 1915 (gatePosition 3 3 16) (gatePosition 3 4 17) 1916 in check "do b\n x <- 2\n x" (portableReply True 1917 (gateAst 11 (pairT op (ofList 1918 [pairT (Stem name) action, pairT Leaf body])) 1919 (gatePosition 1 1 0) (gatePosition 3 4 17)) Leaf True False) 1920 , testCase "single-argument lambda preserves binder and body" $ 1921 let body = gateAst 2 (ofString "x") (gatePosition 1 5 4) (gatePosition 1 6 5) 1922 in check "x : x" (portableReply True 1923 (gateAst 4 (pairT (ofString "x") body) 1924 (gatePosition 1 1 0) (gatePosition 1 6 5)) Leaf True False) 1925 , testCase "multiple same-line lambda binders elaborate in source order" $ 1926 let body = gateAst 2 (ofString "a") 1927 (gatePosition 1 7 6) (gatePosition 1 8 7) 1928 inner = gateAst 4 (pairT (ofString "b") body) 1929 (gatePosition 1 3 2) (gatePosition 1 8 7) 1930 in check "a b : a" (portableReply True 1931 (gateAst 4 (pairT (ofString "a") inner) 1932 (gatePosition 1 1 0) (gatePosition 1 8 7)) Leaf True False) 1933 , testCase "lambda body may start on a later line" $ 1934 let body = gateAst 2 (ofString "x") 1935 (gatePosition 2 3 6) (gatePosition 2 4 7) 1936 in check "x :\n x" (portableReply True 1937 (gateAst 4 (pairT (ofString "x") body) 1938 (gatePosition 1 1 0) (gatePosition 2 4 7)) Leaf True False) 1939 , testCase "nested lambdas associate to the right" $ 1940 let body = gateAst 2 (ofString "x") (gatePosition 1 9 8) (gatePosition 1 10 9) 1941 inner = gateAst 4 (pairT (ofString "y") body) 1942 (gatePosition 1 5 4) (gatePosition 1 10 9) 1943 in check "x : y : x" (portableReply True 1944 (gateAst 4 (pairT (ofString "x") inner) 1945 (gatePosition 1 1 0) (gatePosition 1 10 9)) Leaf True False) 1946 , testCase "grouping preserves its outer span" $ 1947 let leaf = gateAst 1 Leaf (gatePosition 1 2 1) (gatePosition 1 3 2) 1948 group = gateAst 5 leaf (gatePosition 1 1 0) (gatePosition 1 4 3) 1949 arg = gateAst 1 Leaf (gatePosition 1 5 4) (gatePosition 1 6 5) 1950 in check "(t) t" (portableReply True 1951 (gateAst 3 (pairT group arg) 1952 (gatePosition 1 1 0) (gatePosition 1 6 5)) Leaf True False) 1953 , testCase "empty input reports expected atom" $ 1954 check "" (portableReply False 1955 (portableDiagnostic (ofNumber 2) (ofNumber 7) Leaf Leaf Leaf) 1956 Leaf False False) 1957 , testCase "opened group fails after consumption" $ 1958 check "t (" (portableReply False 1959 (portableDiagnostic (ofNumber 2) (ofNumber 7) Leaf Leaf Leaf) 1960 Leaf True False) 1961 , testCase "missing lambda body is a consumed parse failure" $ 1962 check "x : " (portableReply False 1963 (portableDiagnostic (ofNumber 2) (ofNumber 7) Leaf Leaf Leaf) 1964 Leaf True False) 1965 , testCase "unmatched close paren retains its span" $ 1966 check "t)" (portableReply False 1967 (portableDiagnostic (ofNumber 2) Leaf (ofNumber 4) 1968 (gatePosition 1 2 1) Leaf) 1969 (ofList [gateToken 4 Leaf (gatePosition 1 2 1) (gatePosition 1 3 2)]) 1970 True False) 1971 , testCase "lexical error passes through the source parser" $ 1972 check "t #" (portableReply False 1973 (portableDiagnostic (ofNumber 4) Leaf (ofNumber 35) 1974 (gatePosition 1 3 2) Leaf) 1975 (ofString "#") True False) 1976 , testCase "newline does not turn into application whitespace" $ 1977 check "t\nfoo" (portableReply False 1978 (portableDiagnostic (ofNumber 2) Leaf (ofNumber 6) 1979 (gatePosition 1 2 1) Leaf) 1980 (ofList 1981 [ gateToken 6 Leaf (gatePosition 1 2 1) (gatePosition 2 1 2) 1982 , gateToken 2 (ofString "foo") (gatePosition 2 1 2) (gatePosition 2 4 5) 1983 ]) True False) 1984 ] 1985 where 1986 check input expected = 1987 apply (gateParserEnv Map.! "parseGateSource") (ofString input) @?= expected 1988 1989 -- -------------------------------------------------------------------------- 1990 -- Program-level feasibility-gate parsing (definitions and final expression) 1991 -- -------------------------------------------------------------------------- 1992 1993 equationHelperEnv :: Env 1994 equationHelperEnv = fixtureExports "source.equations" 1995 {-# NOINLINE equationHelperEnv #-} 1996 1997 equationHelperTests :: TestTree 1998 equationHelperTests = testGroup "Equation helper pattern adoption" 1999 [ testCase "expected token consumes one cell and preserves suffix" $ 2000 expect 7 input @?= portableReply True token suffix True False 2001 , testCase "wrong token preserves full input and original location" $ 2002 expect 2 input @?= portableReply False 2003 (portableDiagnostic (ofNumber 2) (ofNumber 2) (ofNumber 7) start Leaf) 2004 input False False 2005 , testCase "empty token input has unchanged end diagnostic" $ 2006 expect 2 Leaf @?= endReply 2 Leaf 2007 , testCase "non-list token input has unchanged end diagnostic" $ 2008 expect 2 (Stem Leaf) @?= endReply 2 (Stem Leaf) 2009 , testCase "labelled error preserves token kind span and full remainder" $ 2010 labelled input @?= portableReply False 2011 (portableDiagnostic (ofNumber 48) label (ofNumber 7) start Leaf) 2012 input False False 2013 , testCase "labelled empty error retains protocol flags" $ 2014 labelled Leaf @?= labelledEnd Leaf 2015 , testCase "labelled non-list error retains original input" $ 2016 labelled (Stem Leaf) @?= labelledEnd (Stem Leaf) 2017 , testCase "empty pattern prefix returns supplied terminator" $ 2018 list Leaf @?= nil 2019 , testCase "non-list pattern prefix returns supplied terminator" $ 2020 list (Stem Leaf) @?= nil 2021 , testCase "pattern prefix has independent nested payload and span golden" $ 2022 list (ofList [left, right]) @?= joined left (joined right nil) 2023 , testCase "improper pattern suffix retains prior non-list termination" $ 2024 list (Fork left (Stem Leaf)) @?= joined left nil 2025 , testCase "empty pattern list retains an opaque supplied tail" $ 2026 apply (fixtureExports "base" Map.! "fst") 2027 (call "sourceEquationList" [Leaf, Fork (ofNumber 23) 2028 (error "unused suffix demanded")]) @?= ofNumber 23 2029 ] 2030 where 2031 call name args = foldl apply (equationHelperEnv Map.! name) args 2032 start = gatePosition 2 3 5 2033 end = gatePosition 2 4 6 2034 token = gateToken 7 (ofNumber 23) start end 2035 suffix = ofList [Leaf] 2036 input = Fork token suffix 2037 label = ofString "pattern" 2038 expect kind value = call "sourceEquationExpect" [ofNumber kind, value] 2039 labelled value = call "sourceEquationError" [label, value] 2040 endReply kind value = portableReply False 2041 (portableDiagnostic (ofNumber 2) (ofNumber kind) Leaf Leaf Leaf) 2042 value False False 2043 labelledEnd value = portableReply False 2044 (portableDiagnostic (ofNumber 48) label Leaf Leaf Leaf) value False False 2045 patternNode tag payload a b = gateAst 13 (pairT (ofNumber tag) payload) a b 2046 nil = patternNode 1 Leaf (gatePosition 2 7 9) (gatePosition 2 8 10) 2047 left = patternNode 4 (ofString "x") start end 2048 right = patternNode 4 (ofString "y") (gatePosition 2 5 7) (gatePosition 2 6 8) 2049 joined a b = patternNode 3 (pairT a b) 2050 (case a of Fork _ (Fork _ (Fork x _)) -> x; _ -> error "invalid golden") 2051 (gatePosition 2 8 10) 2052 list value = call "sourceEquationList" [value, nil] 2053 2054 gateProgramEnv :: Env 2055 gateProgramEnv = fixtureExports "source.program" 2056 {-# NOINLINE gateProgramEnv #-} 2057 2058 gateProgramTests :: TestTree 2059 gateProgramTests = testGroup "Feasibility-gate program parser" 2060 [ testCase "portable wrapper normalizes the definition parser" $ do 2061 env <- evaluateCompilerFile 2062 "./test/sourceGateProgramImport.tri" 2063 Map.lookup "main" env @?= Just programResult 2064 , testCase "definition arguments elaborate to located lambdas" $ do 2065 check "id x = x\nid t" programResult 2066 , testCase "an identifier-led expression without assignment stays an expression" $ 2067 let name = gateAst 2 (ofString "f") 2068 (gatePosition 1 1 0) (gatePosition 1 2 1) 2069 leaf = gateAst 1 Leaf 2070 (gatePosition 1 3 2) (gatePosition 1 4 3) 2071 expression = gateAst 3 (pairT name leaf) 2072 (gatePosition 1 1 0) (gatePosition 1 4 3) 2073 in check "f t" (portableReply True 2074 (pairT Leaf (Stem expression)) Leaf True False) 2075 , testCase "unfinished definition fails after consuming the assignment" $ 2076 check "id x =" (portableReply False 2077 (portableDiagnostic (ofNumber 2) (ofNumber 7) Leaf Leaf Leaf) 2078 Leaf True False) 2079 , testCase "an empty unit has no definitions or final expression" $ 2080 check "" (portableReply True (pairT Leaf Leaf) Leaf False False) 2081 ] 2082 where 2083 programResult = 2084 let name = gateToken 2 (ofString "id") 2085 (gatePosition 1 1 0) (gatePosition 1 3 2) 2086 bodyName = gateAst 2 (ofString "x") 2087 (gatePosition 1 8 7) (gatePosition 1 9 8) 2088 body = gateAst 4 (pairT (ofString "x") bodyName) 2089 (gatePosition 1 4 3) (gatePosition 1 9 8) 2090 expressionName = gateAst 2 (ofString "id") 2091 (gatePosition 2 1 9) (gatePosition 2 3 11) 2092 leaf = gateAst 1 Leaf 2093 (gatePosition 2 4 12) (gatePosition 2 5 13) 2094 expression = gateAst 3 (pairT expressionName leaf) 2095 (gatePosition 2 1 9) (gatePosition 2 5 13) 2096 in portableReply True 2097 (pairT (ofList [pairT name body]) (Stem expression)) Leaf True False 2098 check input expected = 2099 apply (gateProgramEnv Map.! "parseGateProgram") (ofString input) @?= expected 2100 2101 -- -------------------------------------------------------------------------- 2102 -- Portable, stable dependency ordering and cycle witnesses 2103 -- -------------------------------------------------------------------------- 2104 2105 sourceGraphEnv :: Env 2106 sourceGraphEnv = fixtureExports "graph" 2107 {-# NOINLINE sourceGraphEnv #-} 2108 2109 sourceGraphTests :: TestTree 2110 sourceGraphTests = testGroup "Source dependency graph" 2111 [ testCase "checked native graph sorts dependencies before dependents" $ do 2112 env <- evaluateCompilerFile "./test/sourceGraphImport.tri" 2113 Map.lookup "main" env @?= Just 2114 (parserOk (ofList (map ofString ["leaf", "inner", "outer"])) Leaf) 2115 , testCase "sort preserves source order among simultaneously ready nodes" $ 2116 check [("b", []), ("a", [])] 2117 (parserOk (ofList [ofString "b", ofString "a"]) Leaf) 2118 , testCase "cycle witness is in the cycle, not just a dependent" $ 2119 check [("outside", ["inside"]), ("inside", ["last"]), ("last", ["inside"])] 2120 (parserErr (ofString "inside") Leaf) 2121 ] 2122 where 2123 check entries expected = 2124 apply (sourceGraphEnv Map.! "graphSort") 2125 (ofList [Fork (ofString name) (ofList (map ofString dependencies)) 2126 | (name, dependencies) <- entries]) @?= expected 2127 2128 -- -------------------------------------------------------------------------- 2129 -- Program-level free names and dependency ordering 2130 -- -------------------------------------------------------------------------- 2131 2132 gateOrderingEnv :: Env 2133 gateOrderingEnv = fixtureExports "source.order" 2134 {-# NOINLINE gateOrderingEnv #-} 2135 2136 gateOrderingTests :: TestTree 2137 gateOrderingTests = testGroup "Feasibility-gate definition ordering" 2138 [ testCase "portable wrapper orders forward references" $ do 2139 env <- evaluateCompilerFile 2140 "./test/sourceGateOrderImport.tri" 2141 Map.lookup "main" env @?= Just (parserOk 2142 (ofList [ofString "f", ofString "main"]) Leaf) 2143 , testCase "unused definitions still have their names checked" $ 2144 check Leaf "main = t\nbroken = u" (parserErr 2145 (portableDiagnostic (ofNumber 20) (ofString "u") Leaf 2146 (gatePosition 2 10 18) Leaf) 2147 (gateAst 2 (ofString "u") 2148 (gatePosition 2 10 18) (gatePosition 2 11 19))) 2149 , testCase "lexical binder shadows a definition and external name" $ 2150 check (ofList [Fork (ofString "x") Leaf]) "x = t\nf x = x" (parserOk 2151 (ofList [ofString "x", ofString "f"]) Leaf) 2152 , testCase "top-level self reference returns a cycle witness" $ 2153 check Leaf "f = f" (parserErr 2154 (portableDiagnostic (ofNumber 31) (ofString "acyclic") 2155 (ofString "f") (gatePosition 1 1 0) 2156 (ofList [ofString "f"])) Leaf) 2157 , testCase "duplicate definition reports the later name location" $ 2158 let name = gateToken 2 (ofString "f") 2159 (gatePosition 2 1 6) (gatePosition 2 2 7) 2160 body = gateAst 1 Leaf 2161 (gatePosition 2 5 10) (gatePosition 2 6 11) 2162 in check Leaf "f = t\nf = t" (parserErr 2163 (portableDiagnostic (ofNumber 30) (ofString "f") 2164 (ofString "f") (gatePosition 2 1 6) Leaf) 2165 (pairT name body)) 2166 , testCase "a final expression also checks unresolved names" $ 2167 check Leaf "f = t\nu" (parserErr 2168 (portableDiagnostic (ofNumber 20) (ofString "u") Leaf 2169 (gatePosition 2 1 6) Leaf) 2170 (gateAst 2 (ofString "u") 2171 (gatePosition 2 1 6) (gatePosition 2 2 7))) 2172 ] 2173 where 2174 check environment source expected = 2175 foldl apply (gateOrderingEnv Map.! "orderGateSource") [environment, ofString source] 2176 @?= expected 2177 2178 -- -------------------------------------------------------------------------- 2179 -- End-to-end program compilation through ordered definitions 2180 -- -------------------------------------------------------------------------- 2181 2182 gateUnitEnv :: Env 2183 gateUnitEnv = unsafePerformIO $ evaluateCompilerFile "./lib/source/unit.tri" 2184 {-# NOINLINE gateUnitEnv #-} 2185 2186 gateUnitTests :: TestTree 2187 gateUnitTests = testGroup "Feasibility-gate program compiler" 2188 [ testCase "real source files compile without rewriting or relexing" $ do 2189 let corpus = 2190 [ ("./test/fork.tri", [], Fork Leaf Leaf) 2191 , ("./test/comments-1.tri", [], Fork (Stem Leaf) Leaf) 2192 , ("./test/map.tri", ["map", "append", "equal?", "head"], trueT) 2193 ] 2194 forM_ corpus $ \(path, names, expected) -> do 2195 source <- readFile path 2196 let external = ofList [Fork (ofString name) (fixtureExports "base" Map.! name) | name <- names] 2197 foldl apply (fixtureExports "source.unit" Map.! "compileGateProgram") 2198 [external, ofString source] @?= parserOk expected Leaf 2199 , testCase "portable size counts leaf, stem, and fork arguments" $ do 2200 source <- readFile "./test/size.tri" 2201 let definitions = unlines (init (lines source)) 2202 external = ofList [Fork (ofString name) (fixtureExports "base" Map.! name) 2203 | name <- ["y", "triage"]] 2204 forM_ [("t", 1), ("t t", 2), ("t t t", 3)] $ \(argument, count) -> do 2205 let input = definitions ++ "size (" ++ argument ++ ")\n" 2206 foldl apply (fixtureExports "source.unit" Map.! "compileGateProgram") 2207 [external, ofString input] @?= parserOk (ofNumber count) Leaf 2208 , testCase "real file inputs match the natively packed self-built artifact" $ 2209 withSystemTempDirectory "gate-file-corpus" $ \dir -> do 2210 let path = dir </> "file-matches.arboricx" 2211 corpus = 2212 [ ("./test/fork.tri", parserOk (Fork Leaf Leaf) Leaf) 2213 , ("./test/comments-1.tri", 2214 parserOk (Fork (Stem Leaf) Leaf) Leaf) 2215 , ("./test/map.tri", parserOk trueT Leaf) 2216 , -- Optimized unit lowering with constructor-K-compiled y/triage. 2217 -- Independently counted the emitted size tree on the host: 500 2218 -- nodes (formerly 680 with the old support representations). 2219 ("./test/size.tri", parserOk (ofNumber 500) Leaf) 2220 ] 2221 packCompilerFile 2222 "./test/sourceGateFileCompiler.tri" path [pack "matchesFile"] 2223 bytes <- BS.readFile path 2224 case decodeBundle bytes of 2225 Left diagnostic -> assertFailure diagnostic 2226 Right bundle -> do 2227 let artifact = reconstructBundleTerms (bundleNodes bundle) 2228 V.! fromIntegral (head (bundleRoots bundle)) 2229 forM_ corpus $ \(fixture, expected) -> do 2230 source <- readFile fixture 2231 apply (apply artifact (ofString source)) expected @?= trueT 2232 , testCase "self-built local binding compiler executes through its wrapper" $ do 2233 env <- evaluateCompilerFile 2234 "./test/sourceGateLocalImport.tri" 2235 Map.lookup "main" env @?= Just (parserOk Leaf Leaf) 2236 , testCase "independent program goldens match the Haskell bundle" $ 2237 withSystemTempDirectory "gate-unit-goldens" $ \dir -> do 2238 let path = dir </> "program-matches.arboricx" 2239 packCompilerFile 2240 "./test/sourceGateUnitCompiler.tri" path [pack "matchesExpected"] 2241 bytes <- BS.readFile path 2242 goldenEnv <- evaluateCompilerFile "./test/sourceGateExpected.tri" 2243 let name = gateToken 2 (ofString "f") 2244 (gatePosition 2 1 6) (gatePosition 2 2 7) 2245 body = gateAst 1 Leaf 2246 (gatePosition 2 5 10) (gatePosition 2 6 11) 2247 cycleError = parserErr 2248 (portableDiagnostic (ofNumber 31) (ofString "acyclic") 2249 (ofString "a") (gatePosition 1 1 0) 2250 (ofList [ofString "a"])) Leaf 2251 cases = 2252 [ ("main = id 42\nid x = x", "goldenFortyTwo", 2253 parserOk (ofNumber 42) Leaf) 2254 , ("id x = x\nid 42", "goldenFortyTwo", 2255 parserOk (ofNumber 42) Leaf) 2256 , ("main = 2\n3", "goldenTwo", parserOk (ofNumber 2) Leaf) 2257 , ("main = [(id 2)]\nid x = x", "goldenProgramList", 2258 parserOk (ofList [ofNumber 2]) Leaf) 2259 , ("helper = t", "goldenMissingRoot", parserErr 2260 (portableDiagnostic (ofNumber 32) (ofString "main") 2261 Leaf Leaf Leaf) Leaf) 2262 , ("a = b\nb = a\nmain = t", "goldenProgramCycle", cycleError) 2263 , ("f = t\nf = t", "goldenProgramDuplicate", parserErr 2264 (portableDiagnostic (ofNumber 30) (ofString "f") 2265 (ofString "f") (gatePosition 2 1 6) Leaf) 2266 (pairT name body)) 2267 , ("main = u", "goldenProgramUnknown", parserErr 2268 (portableDiagnostic (ofNumber 20) (ofString "u") Leaf 2269 (gatePosition 1 8 7) Leaf) 2270 (gateAst 2 (ofString "u") 2271 (gatePosition 1 8 7) (gatePosition 1 9 8))) 2272 , ("unused = (x : x x) (x : x x)\nmain = t", "goldenLeaf", 2273 parserOk Leaf Leaf) 2274 , ("main = let x = t in x", "goldenLeaf", parserOk Leaf Leaf) 2275 , ("main = x where x = t", "goldenLeaf", parserOk Leaf Leaf) 2276 , ("main = let id x = x in id 2", "goldenTwo", 2277 parserOk (ofNumber 2) Leaf) 2278 , ("main = value where value = helper t\nhelper x = x", 2279 "goldenLeaf", parserOk Leaf Leaf) 2280 , ("main = let x = x in x", "goldenLocalMissing", parserErr 2281 (portableDiagnostic (ofNumber 20) (ofString "x") Leaf 2282 (gatePosition 1 16 15) Leaf) 2283 (gateAst 2 (ofString "x") 2284 (gatePosition 1 16 15) (gatePosition 1 17 16))) 2285 , ("main = let x = t x", "goldenLetMissingIn", parserErr 2286 (portableDiagnostic (ofNumber 2) (ofNumber 13) 2287 Leaf Leaf Leaf) Leaf) 2288 , ("main = (t\n t)", "goldenMultilineGroup", 2289 parserOk (Stem Leaf) Leaf) 2290 , ("main = [(t\n t)]", "goldenMultilineList", 2291 parserOk (ofList [Stem Leaf]) Leaf) 2292 , ("main = (x\n where x = t)", "goldenLeaf", 2293 parserOk Leaf Leaf) 2294 , ("main =\n t", "goldenLeaf", parserOk Leaf Leaf) 2295 , ("main =\n t\n t", "goldenMultilineGroup", parserOk (Stem Leaf) Leaf) 2296 , ("main =\n (x : x)\n 2", "goldenTwo", 2297 parserOk (ofNumber 2) Leaf) 2298 , ("main =\n id\n t\nid x = x", "goldenLeaf", 2299 parserOk Leaf Leaf) 2300 , ("main = value\nwhere value = t", "goldenLeaf", 2301 parserOk Leaf Leaf) 2302 , ("main =\n value\n where value = t", "goldenLeaf", 2303 parserOk Leaf Leaf) 2304 , ("main =\n", "goldenMissingDefinitionBody", parserErr 2305 (portableDiagnostic (ofNumber 40) 2306 (ofString "definition body") Leaf Leaf Leaf) Leaf) 2307 , ("main = let x =\n 2\nin x", "goldenTwo", 2308 parserOk (ofNumber 2) Leaf) 2309 , ("main = let x = t in\n x", "goldenLeaf", 2310 parserOk Leaf Leaf) 2311 , ("main = let\n id x =\n x\nin id 2", "goldenTwo", 2312 parserOk (ofNumber 2) Leaf) 2313 , ("main =\n let x =\n t\n in x", "goldenLeaf", 2314 parserOk Leaf Leaf) 2315 , ("main = t where x =\n 2", "goldenLeaf", 2316 parserOk Leaf Leaf) 2317 , ("main = id 2 where\n id x =\n x", "goldenTwo", 2318 parserOk (ofNumber 2) Leaf) 2319 , ("main = let x =\n (y : y)\n 2\nin x", "goldenTwo", 2320 parserOk (ofNumber 2) Leaf) 2321 , ("main = let id x = x in\n id\n 2", "goldenTwo", 2322 parserOk (ofNumber 2) Leaf) 2323 , ("main = let x =\n", "goldenMissingLocalValue", parserErr 2324 (portableDiagnostic (ofNumber 40) 2325 (ofString "local binding value") Leaf Leaf Leaf) Leaf) 2326 , ("main = let x = t in\n", "goldenMissingLocalBody", parserErr 2327 (portableDiagnostic (ofNumber 40) 2328 (ofString "local binding body") Leaf Leaf Leaf) Leaf) 2329 , ("main = 2 |> (x : x)", "goldenTwo", 2330 parserOk (ofNumber 2) Leaf) 2331 , ("main = (x : x) <| 2", "goldenTwo", 2332 parserOk (ofNumber 2) Leaf) 2333 , ("main = 2 |> (x : x) |> (x : x)", "goldenTwo", 2334 parserOk (ofNumber 2) Leaf) 2335 , ("main = t <| t t", "goldenPipeApplication", 2336 parserOk (Stem (Stem Leaf)) Leaf) 2337 , ("main = 2 |> id\nid x = x", "goldenTwo", 2338 parserOk (ofNumber 2) Leaf) 2339 , ("main = 2 |> id where id x = x", "goldenTwo", 2340 parserOk (ofNumber 2) Leaf) 2341 , ("main = 2 |> missing", "goldenPipeUnknown", parserErr 2342 (portableDiagnostic (ofNumber 20) (ofString "missing") 2343 Leaf (gatePosition 1 13 12) Leaf) 2344 (gateAst 2 (ofString "missing") 2345 (gatePosition 1 13 12) (gatePosition 1 20 19))) 2346 , ("main = 2 |>", "goldenPipeMissingOperand", parserErr 2347 (portableDiagnostic (ofNumber 2) (ofNumber 7) 2348 Leaf Leaf Leaf) Leaf) 2349 , ("f = 2\nf |> let id x = x in id", "goldenTwo", 2350 parserOk (ofNumber 2) Leaf) 2351 , ("main = 2\n |> (x : x)", "goldenTwo", 2352 parserOk (ofNumber 2) Leaf) 2353 , ("main = 2 |>\n (x : x)", "goldenTwo", 2354 parserOk (ofNumber 2) Leaf) 2355 , ("main = do bind\n x <- 2\n x\nbind a f = f a", 2356 "goldenTwo", parserOk (ofNumber 2) Leaf) 2357 , ("main = do unused\n 2", "goldenTwo", 2358 parserOk (ofNumber 2) Leaf) 2359 , ("main = do bind\n 1\n 2\nbind a f = f a", 2360 "goldenTwo", parserOk (ofNumber 2) Leaf) 2361 , ("main = do bind\n x <- 2\n y <- x\n y\nbind a f = f a", 2362 "goldenTwo", parserOk (ofNumber 2) Leaf) 2363 , ("main = do bind\n x <- 2\n do bind\n y <- x\n y\nbind a f = f a", 2364 "goldenTwo", parserOk (ofNumber 2) Leaf) 2365 , ("main = do bind\n x <- (y : y)\n 2\n x\nbind a f = f a", 2366 "goldenTwo", parserOk (ofNumber 2) Leaf) 2367 , ("main = let _ = 42 in do bind\n 1\n _\nbind a f = f a", 2368 "goldenOne", parserOk (ofNumber 1) Leaf) 2369 , ("main = do bind\n x <- 2", "goldenDoMissingFinal", parserErr 2370 (portableDiagnostic (ofNumber 40) 2371 (ofString "do final expression") Leaf 2372 (gatePosition 2 3 17) Leaf) Leaf) 2373 , ("main = do bind\n x <- missing\n x\nbind a f = f a", 2374 "goldenDoUnboundAction", parserErr 2375 (portableDiagnostic (ofNumber 20) (ofString "missing") 2376 Leaf (gatePosition 2 8 22) Leaf) 2377 (gateAst 2 (ofString "missing") 2378 (gatePosition 2 8 22) (gatePosition 2 15 29))) 2379 ] 2380 case decodeBundle bytes of 2381 Left diagnostic -> assertFailure diagnostic 2382 Right bundle -> do 2383 let artifact = reconstructBundleTerms (bundleNodes bundle) 2384 V.! fromIntegral (head (bundleRoots bundle)) 2385 forM_ cases $ \(source, goldenName, expected) -> do 2386 Map.lookup goldenName goldenEnv @?= Just expected 2387 assertBool ("program golden mismatch: " ++ goldenName 2388 ++ " for " ++ show source) 2389 (apply (apply artifact (ofString source)) expected == trueT) 2390 case Map.lookup "goldenProgramCycleWrongPosition" goldenEnv of 2391 Nothing -> assertFailure "missing program wrong-position control" 2392 Just wrong -> 2393 apply (apply artifact (ofString "a = b\nb = a\nmain = t")) wrong 2394 @?= falseT 2395 , testCase "output goldens match the portable Haskell bundle" $ 2396 withSystemTempDirectory "gate-unit-output-goldens" $ \dir -> do 2397 let path = dir </> "output-matches.arboricx" 2398 packCompilerFile 2399 "./test/sourceGateUnitCompiler.tri" path [pack "matchesOutput"] 2400 bytes <- BS.readFile path 2401 goldenEnv <- evaluateCompilerFile "./test/sourceGateExpected.tri" 2402 case decodeBundle bytes of 2403 Left diagnostic -> assertFailure diagnostic 2404 Right bundle -> do 2405 let artifact = reconstructBundleTerms (bundleNodes bundle) 2406 V.! fromIntegral (head (bundleRoots bundle)) 2407 cases = 2408 [ ("main = value\nvalue = t", "goldenOutputForward", 2409 parserOk (pairT Leaf (ofList 2410 [ pairT (ofString "value") Leaf 2411 , pairT (ofString "main") Leaf 2412 ])) Leaf) 2413 , ("2", "goldenOutputExpression", 2414 parserOk (pairT (ofNumber 2) Leaf) Leaf) 2415 ] 2416 forM_ cases $ \(source, goldenName, expected) -> do 2417 Map.lookup goldenName goldenEnv @?= Just expected 2418 apply (apply artifact (ofString source)) expected @?= trueT 2419 , testCase "output includes root and definitions in dependency order" $ do 2420 let source = "main = value\nvalue = t" 2421 output = foldl apply (gateUnitEnv Map.! "compileGateProgramOutput") [Leaf, ofString source] 2422 definitions = ofList 2423 [ pairT (ofString "value") Leaf 2424 , pairT (ofString "main") Leaf 2425 ] 2426 output @?= parserOk (pairT Leaf definitions) Leaf 2427 check Leaf source (parserOk Leaf Leaf) 2428 , testCase "empty output environment still carries the selected root" $ 2429 foldl apply (gateUnitEnv Map.! "compileGateProgramOutput") [Leaf, ofString "t"] @?= 2430 parserOk (pairT Leaf Leaf) Leaf 2431 , testCase "output entrypoint preserves structured diagnostics" $ 2432 foldl apply (gateUnitEnv Map.! "compileGateProgramOutput") [Leaf, ofString "helper = t"] @?= 2433 parserErr (portableDiagnostic (ofNumber 32) 2434 (ofString "main") Leaf Leaf Leaf) Leaf 2435 , testCase "public compileSource normalizes with abstract environment and source" $ do 2436 env <- evaluateCompilerFile 2437 "./test/sourcePublicCompiler.tri" 2438 Map.member "matchesSource" env @?= True 2439 let check environment source expected = do 2440 foldl apply (fixtureExports "source.unit" Map.! "compileSource") 2441 [environment, ofString source] @?= expected 2442 foldl apply (env Map.! "matchesSource") 2443 [environment, ofString source, expected] @?= trueT 2444 assertBool "test wrapper leaked imported compiler" (Map.notMember "compileSource" env) 2445 check Leaf "main = value\nvalue = t" 2446 (parserOk (pairT Leaf (ofList 2447 [pairT (ofString "value") Leaf, pairT (ofString "main") Leaf])) Leaf) 2448 check (ofList [Fork (ofString "host") (Stem Leaf)]) "host" 2449 (parserOk (pairT (Stem Leaf) Leaf) Leaf) 2450 check Leaf "helper = t" 2451 (parserErr (portableDiagnostic (ofNumber 32) 2452 (ofString "main") Leaf Leaf Leaf) Leaf) 2453 foldl apply (env Map.! "matchesSource") 2454 [Leaf, ofString "main = t", parserOk (pairT (Stem Leaf) Leaf) Leaf] @?= falseT 2455 , testCase "public compileSource is an exportable two-argument bundle" $ 2456 withSystemTempDirectory "public-source-artifact" $ \dir -> do 2457 let path = dir </> "compile-source.arboricx" 2458 packCompilerFile 2459 "./lib/source/unit.tri" path [pack "compileSource"] 2460 bytes <- BS.readFile path 2461 case decodeBundle bytes of 2462 Left diagnostic -> assertFailure diagnostic 2463 Right bundle -> do 2464 let artifact = reconstructBundleTerms (bundleNodes bundle) 2465 V.! fromIntegral (head (bundleRoots bundle)) 2466 cases = 2467 [ (Leaf, "2", parserOk (pairT (ofNumber 2) Leaf) Leaf) 2468 , (Leaf, "main = value\nvalue = t", parserOk 2469 (pairT Leaf (ofList [pairT (ofString "value") Leaf, 2470 pairT (ofString "main") Leaf])) Leaf) 2471 , (ofList [pairT (ofString "host") (Stem Leaf)], 2472 "host", parserOk (pairT (Stem Leaf) Leaf) Leaf) 2473 , (Leaf, "helper = t", parserErr 2474 (portableDiagnostic (ofNumber 32) 2475 (ofString "main") Leaf Leaf Leaf) Leaf) 2476 , (Leaf, "#", parserErr 2477 (portableDiagnostic (ofNumber 4) Leaf (ofNumber 35) 2478 (gatePosition 1 1 0) Leaf) (ofString "#")) 2479 , (Leaf, "!import \"x\" NS", parserErr 2480 (portableDiagnostic (ofNumber 4) Leaf (ofNumber 33) 2481 (gatePosition 1 1 0) Leaf) 2482 (ofString "!import \"x\" NS")) 2483 ] 2484 forM_ cases $ \(environment, source, expected) -> do 2485 let compiled = apply (apply artifact environment) (ofString source) 2486 compiled @?= expected 2487 -- Repeated fixed inputs have structurally identical complete 2488 -- outputs, not merely the same selected root. 2489 apply (apply artifact environment) (ofString source) @?= compiled 2490 forM_ 2491 [ ("./test/fork.tri", Fork Leaf Leaf) 2492 , ("./test/comments-1.tri", Fork (Stem Leaf) Leaf) 2493 ] $ \(fixture, root) -> do 2494 source <- readFile fixture 2495 apply (apply artifact Leaf) (ofString source) @?= 2496 parserOk (pairT root 2497 (ofList [pairT (ofString "main") root])) Leaf 2498 let externalNames = ["map", "append", "equal?", "head", "y", "triage"] 2499 external = ofList 2500 [ pairT (ofString name) (fixtureExports "base" Map.! name) 2501 | name <- externalNames ] 2502 stringSource <- readFile "./test/string.tri" 2503 apply (apply artifact external) (ofString stringSource) @?= 2504 parserOk (pairT (ofString "String test!") Leaf) Leaf 2505 mapSource <- readFile "./test/map.tri" 2506 apply (apply artifact external) (ofString mapSource) @?= 2507 parserOk (pairT trueT (ofList 2508 [ pairT (ofString "x") 2509 (ofList [ofString "Successfully concatenated two strings!"]) 2510 , pairT (ofString "main") trueT 2511 ])) Leaf 2512 , testCase "public bundle functions agree by application, not tree shape" $ 2513 withSystemTempDirectory "public-source-functions" $ \dir -> do 2514 let path = dir </> "compile-source.arboricx" 2515 packCompilerFile 2516 "./lib/source/unit.tri" path [pack "compileSource"] 2517 bytes <- BS.readFile path 2518 comparatorEnv <- evaluateCompilerFile "./test/sourcePublicCompiler.tri" 2519 case decodeBundle bytes of 2520 Left diagnostic -> assertFailure diagnostic 2521 Right bundle -> do 2522 let compiler = reconstructBundleTerms (bundleNodes bundle) 2523 V.! fromIntegral (head (bundleRoots bundle)) 2524 comparator = comparatorEnv Map.! "matchesSourceApplicationArtifact" 2525 cases = 2526 [ ("main x = x", ofNumber 2, ofNumber 2) 2527 , ("main x = t", Stem Leaf, Leaf) 2528 , ("main x = let id a = a in id x", Stem Leaf, Stem Leaf) 2529 , ("main x = do bind\n y <- x\n y\nbind a f = f a", 2530 ofNumber 2, ofNumber 2) 2531 ] 2532 forM_ cases $ \(source, input, expected) -> do 2533 foldl apply comparator 2534 [compiler, Leaf, ofString source, input, expected] @?= trueT 2535 let rootComparator = comparatorEnv Map.! "matchesSourceRootArtifact" 2536 unused = "unused = (x : x x) (x : x x)\nmain = t" 2537 unusedRoot <- Timeout.timeout (30 * 1000000) $ 2538 evaluate (foldl apply rootComparator 2539 [compiler, Leaf, ofString unused, Leaf] == trueT) 2540 unusedRoot @?= Just True 2541 , testCase "self-built compiler wrapper handles a forward reference" $ do 2542 env <- evaluateCompilerFile 2543 "./test/sourceGateUnitImport.tri" 2544 Map.lookup "main" env @?= Just (parserOk Leaf Leaf) 2545 , testCase "forward definitions with arguments evaluate before main" $ do 2546 let source = "main = id 42\nid x = x" 2547 check Leaf source (parserOk (ofNumber 42) Leaf) 2548 , testCase "final expression selects root in absence of main" $ 2549 check Leaf "id x = x\nid t" (parserOk Leaf Leaf) 2550 , testCase "a local main wins over a final expression" $ 2551 check Leaf "main = 2\n3" (parserOk (ofNumber 2) Leaf) 2552 , testCase "provided environment is visible inside definitions" $ 2553 check (ofList [Fork (ofString "host") (Stem Leaf)]) "main = host t" 2554 (parserOk (Fork Leaf Leaf) Leaf) 2555 , testCase "multiple definition arguments preserve binder order" $ 2556 let source = "main x y = x" 2557 program = foldl apply (gateUnitEnv Map.! "compileGateProgram") [Leaf, ofString source] 2558 in case program of 2559 Fork tag (Fork fun Leaf) | tag == trueT -> 2560 apply (apply fun Leaf) (Stem Leaf) @?= Leaf 2561 other -> assertFailure ("Expected compiled two-argument function, got " ++ show other) 2562 , testCase "recursive top-level function and triage compile from source" $ do 2563 let source = "triage leaf stem fork = t (t leaf stem) fork\n" 2564 ++ "main xs = y (self : rest : triage t (h : self h) (a : b : self a) rest) xs" 2565 external = ofList [Fork (ofString "y") (gateCompileBaseEnv Map.! "y")] 2566 compiled = foldl apply (gateUnitEnv Map.! "compileGateProgram") [external, ofString source] 2567 case compiled of 2568 Fork tag (Fork fun Leaf) | tag == trueT -> do 2569 apply fun Leaf @?= Leaf 2570 apply fun (Stem Leaf) @?= Leaf 2571 apply fun (Fork (Stem Leaf) Leaf) @?= Leaf 2572 other -> assertFailure ("Expected recursive source function, got " ++ show other) 2573 , testCase "recursive function, y, and triage compile entirely from source" $ do 2574 let source = "y = ((mut : wait : fun : wait mut (x : fun (wait mut x))) " 2575 ++ "(x : x x) (a0 : a1 : a2 : t (t a0) (t t a2) a1))\n" 2576 ++ "triage leaf stem fork = t (t leaf stem) fork\n" 2577 ++ "main xs = y (self : rest : triage t (h : self h) " 2578 ++ "(a : b : self a) rest) xs" 2579 compiled = foldl apply (gateUnitEnv Map.! "compileGateProgram") [Leaf, ofString source] 2580 case compiled of 2581 Fork tag (Fork fun Leaf) | tag == trueT -> do 2582 apply fun Leaf @?= Leaf 2583 apply fun (Stem Leaf) @?= Leaf 2584 apply fun (Fork (Stem Leaf) Leaf) @?= Leaf 2585 other -> assertFailure ("Expected fully sourced recursive function, got " ++ show other) 2586 , testCase "indented definition bodies preserve continuation layout" $ do 2587 let sources = 2588 [ ("main =\n t", Leaf) 2589 , ("main =\n (x : x)\n 2", ofNumber 2) 2590 , ("main =\n id\n t\nid x = x", Leaf) 2591 , ("main =\n id\n 23\nid x = x", ofNumber 23) 2592 , ("main =\n id\n 23\nid x = x", ofNumber 23) 2593 , ("main = id\n 23\nid x = x", ofNumber 23) 2594 , ("main = value\nwhere value = t", Leaf) 2595 , ("main =\n value\n where value = t", Leaf) 2596 ] 2597 forM_ sources $ \(source, value) -> do 2598 check Leaf source (parserOk value Leaf) 2599 , testCase "multiline grouped applications retain expected results" $ do 2600 let sources = 2601 [ ("main = (t\n t)", Stem Leaf) 2602 , ("main = [(t\n t)]", ofList [Stem Leaf]) 2603 , ("main = (let x = t in x\n t)", Stem Leaf) 2604 , ("main = (x\n where x = t)", Leaf) 2605 ] 2606 forM_ sources $ \(source, value) -> do 2607 check Leaf source (parserOk value Leaf) 2608 , testCase "an unindented do block has a located diagnostic" $ 2609 let diagnostic = portableDiagnostic (ofNumber 40) 2610 (ofString "indented do block") (ofNumber 2) 2611 (gatePosition 2 1 15) Leaf 2612 reply = apply (gateProgramEnv Map.! "parseGateProgram") 2613 (ofString "main = do bind\nx <- 2\n x") 2614 in apply (fixtureExports "parser" Map.! "parserReplyPayload") reply @?= diagnostic 2615 , testCase "pipe followed by a local assignment stays a final expression" $ 2616 check Leaf "f = 2\nf |> let id x = x in id" 2617 (parserOk (ofNumber 2) Leaf) 2618 , testCase "explicit-bind do with nested statements retains expected results" $ do 2619 let sources = 2620 [ "main = do bind\n x <- 2\n x\nbind a f = f a" 2621 , "main = do unused\n 2" 2622 , "main = do bind\n 1\n 2\nbind a f = f a" 2623 , "main = do bind\n x <- 2\n y <- x\n y\nbind a f = f a" 2624 , "main = do bind\n x <- 2\n do bind\n y <- x\n y\nbind a f = f a" 2625 ] 2626 forM_ sources $ \source -> do 2627 check Leaf source (parserOk (ofNumber 2) Leaf) 2628 , testCase "pipe directions, precedence, and local names retain expected results" $ do 2629 let sources = 2630 [ ("main = 2 |> (x : x)", ofNumber 2) 2631 , ("main = (x : x) <| 2", ofNumber 2) 2632 , ("main = 2 |> (x : x) |> (x : x)", ofNumber 2) 2633 , ("main = t <| t t", Stem (Stem Leaf)) 2634 , ("main = 2 |> id\nid x = x", ofNumber 2) 2635 , ("main = 2 |> id where id x = x", ofNumber 2) 2636 , ("main = 2\n |> (x : x)", ofNumber 2) 2637 , ("main = 2 |>\n (x : x)", ofNumber 2) 2638 ] 2639 forM_ sources $ \(source, value) -> do 2640 check Leaf source (parserOk value Leaf) 2641 , testCase "multiline local let/where layout retains expected results" $ do 2642 let sources = 2643 [ ("main = let x =\n 2\nin x", ofNumber 2) 2644 , ("main = let x = t in\n x", Leaf) 2645 , ("main = let\n id x =\n x\nin id 2", ofNumber 2) 2646 , ("main =\n let x =\n t\n in x", Leaf) 2647 , ("main = t where x =\n 2", Leaf) 2648 , ("main = id 2 where\n id x =\n x", ofNumber 2) 2649 , ("main = let x =\n (y : y)\n 2\nin x", ofNumber 2) 2650 , ("main = let id x = x in\n id\n 2", ofNumber 2) 2651 ] 2652 forM_ sources $ \(source, value) -> do 2653 check Leaf source (parserOk value Leaf) 2654 , testCase "local bindings preserve lexical scope and argument order" $ do 2655 let sources = 2656 [ ("main = let x = 2 in x", ofNumber 2) 2657 , ("main = let id x = x in id 2", ofNumber 2) 2658 , ("main = id 2 where id x = x", ofNumber 2) 2659 , ("result where result = t", Leaf) 2660 , ("main = let x = t in (y : x) 2", Leaf) 2661 ] 2662 forM_ sources $ \(source, value) -> do 2663 check Leaf source (parserOk value Leaf) 2664 , testCase "local binding value has no self-reference" $ 2665 check Leaf "main = let x = x in x" (parserErr 2666 (portableDiagnostic (ofNumber 20) (ofString "x") Leaf 2667 (gatePosition 1 16 15) Leaf) 2668 (gateAst 2 (ofString "x") 2669 (gatePosition 1 16 15) (gatePosition 1 17 16))) 2670 , testCase "a local binder shadows a top-level definition" $ 2671 check Leaf "main = local where local = t\nlocal = 2" 2672 (parserOk Leaf Leaf) 2673 , testCase "local free names order forward-referenced definitions" $ 2674 check Leaf "main = value where value = helper t\nhelper x = x" 2675 (parserOk Leaf Leaf) 2676 , testCase "lists may refer to forward definitions" $ 2677 check Leaf "main = [(id 2)]\nid x = x" 2678 (parserOk (ofList [ofNumber 2]) Leaf) 2679 , testCase "unused divergent body is checked but not demanded" $ do 2680 let source = "unused = (x : x x) (x : x x)\nmain = t" 2681 check Leaf source (parserOk Leaf Leaf) 2682 , testCase "a next-line body cannot occupy its owner's column" $ do 2683 let reply = apply (gateProgramEnv Map.! "parseGateProgram") 2684 (ofString "main =\nt") 2685 diagnostic = portableDiagnostic (ofNumber 40) 2686 (ofString "definition body") (ofNumber 1) (gatePosition 2 1 7) Leaf 2687 apply (fixtureExports "parser" Map.! "parserReplyPayload") reply @?= diagnostic 2688 , testCase "missing multiline definition body is a structured diagnostic" $ 2689 check Leaf "main =\n" (parserErr 2690 (portableDiagnostic (ofNumber 40) (ofString "definition body") 2691 Leaf Leaf Leaf) Leaf) 2692 , testCase "missing local value or body returns a structured diagnostic" $ do 2693 check Leaf "main = let x =\n" (parserErr 2694 (portableDiagnostic (ofNumber 40) 2695 (ofString "local binding value") Leaf Leaf Leaf) Leaf) 2696 check Leaf "main = let x = t in\n" (parserErr 2697 (portableDiagnostic (ofNumber 40) 2698 (ofString "local binding body") Leaf Leaf Leaf) Leaf) 2699 , testCase "no root is a structured diagnostic" $ 2700 check Leaf "helper = t" (parserErr 2701 (portableDiagnostic (ofNumber 32) (ofString "main") Leaf Leaf Leaf) 2702 Leaf) 2703 , testCase "cycle fails before result selection" $ 2704 check Leaf "a = b\nb = a\nmain = t" (parserErr 2705 (portableDiagnostic (ofNumber 31) (ofString "acyclic") 2706 (ofString "a") (gatePosition 1 1 0) 2707 (ofList [ofString "a"])) Leaf) 2708 ] 2709 where 2710 check external source expected = 2711 foldl apply (gateUnitEnv Map.! "compileGateProgram") [external, ofString source] 2712 @?= expected 2713 2714 -- -------------------------------------------------------------------------- 2715 -- Single-expression feasibility-gate name resolution 2716 -- -------------------------------------------------------------------------- 2717 2718 gateResolveEnv :: Env 2719 gateResolveEnv = fixtureExports "source.resolve" 2720 {-# NOINLINE gateResolveEnv #-} 2721 2722 gateCore :: Integer -> T -> T -> T -> T 2723 gateCore = gateAst 2724 2725 gateResolutionTests :: TestTree 2726 gateResolutionTests = testGroup "Feasibility-gate resolution" 2727 [ testCase "self-built resolver export executes through its wrapper" $ do 2728 env <- evaluateCompilerFile "./test/sourceGateResolveImport.tri" 2729 let bound = gateCore 2 (ofNumber 0) (gatePosition 1 5 4) (gatePosition 1 6 5) 2730 lam = gateCore 4 bound (gatePosition 1 1 0) (gatePosition 1 6 5) 2731 Map.lookup "main" env @?= Just (parserOk lam Leaf) 2732 , testCase "nearest lexical binder has index zero" $ 2733 let bound = gateCore 2 (ofNumber 0) (gatePosition 1 5 4) (gatePosition 1 6 5) 2734 in check Leaf "x : x" (parserOk 2735 (gateCore 4 bound (gatePosition 1 1 0) (gatePosition 1 6 5)) Leaf) 2736 , testCase "outer lexical binder has index one" $ 2737 let bound = gateCore 2 (ofNumber 1) (gatePosition 1 9 8) (gatePosition 1 10 9) 2738 inner = gateCore 4 bound (gatePosition 1 5 4) (gatePosition 1 10 9) 2739 in check Leaf "x : y : x" (parserOk 2740 (gateCore 4 inner (gatePosition 1 1 0) (gatePosition 1 10 9)) Leaf) 2741 , testCase "local binding lowers to an applied scoped lambda" $ 2742 let value = gateCore 1 Leaf 2743 (gatePosition 1 9 8) (gatePosition 1 10 9) 2744 bound = gateCore 2 (ofNumber 0) 2745 (gatePosition 1 14 13) (gatePosition 1 15 14) 2746 scoped = gateCore 4 bound 2747 (gatePosition 1 1 0) (gatePosition 1 15 14) 2748 in check Leaf "let x = t in x" (parserOk 2749 (gateCore 3 (pairT scoped value) 2750 (gatePosition 1 1 0) (gatePosition 1 15 14)) Leaf) 2751 , testCase "forward pipe elaborates to reversed core application" $ 2752 let function = gateCore 5 (Stem Leaf) 2753 (gatePosition 1 6 5) (gatePosition 1 7 6) 2754 argument = gateCore 6 (ofNumber 2) 2755 (gatePosition 1 1 0) (gatePosition 1 2 1) 2756 in check (ofList [Fork (ofString "f") (Stem Leaf)]) "2 |> f" (parserOk 2757 (gateCore 3 (pairT function argument) 2758 (gatePosition 1 1 0) (gatePosition 1 7 6)) Leaf) 2759 , testCase "number becomes a resolved tree literal" $ 2760 check Leaf "42" (parserOk 2761 (gateCore 6 (ofNumber 42) (gatePosition 1 1 0) (gatePosition 1 3 2)) Leaf) 2762 , testCase "string becomes a resolved tree literal" $ 2763 check Leaf "\"hi\"" (parserOk 2764 (gateCore 7 (ofString "hi") (gatePosition 1 1 0) (gatePosition 1 5 4)) Leaf) 2765 , testCase "list elements become individually resolved core nodes" $ 2766 let leaf = gateCore 1 Leaf (gatePosition 1 2 1) (gatePosition 1 3 2) 2767 number = gateCore 6 (ofNumber 2) 2768 (gatePosition 1 4 3) (gatePosition 1 5 4) 2769 in check Leaf "[t 2]" (parserOk 2770 (gateCore 8 (ofList [leaf, number]) 2771 (gatePosition 1 1 0) (gatePosition 1 6 5)) Leaf) 2772 , testCase "unresolved list element preserves its own span" $ 2773 check Leaf "[u]" (parserErr 2774 (portableDiagnostic (ofNumber 20) (ofString "u") Leaf 2775 (gatePosition 1 2 1) Leaf) 2776 (gateAst 2 (ofString "u") 2777 (gatePosition 1 2 1) (gatePosition 1 3 2))) 2778 , testCase "supplied external tree becomes a core value" $ 2779 let external = gateCore 5 (Stem Leaf) 2780 (gatePosition 1 1 0) (gatePosition 1 2 1) 2781 leaf = gateCore 1 Leaf (gatePosition 1 3 2) (gatePosition 1 4 3) 2782 in check (ofList [Fork (ofString "f") (Stem Leaf)]) "f t" (parserOk 2783 (gateCore 3 (pairT external leaf) 2784 (gatePosition 1 1 0) (gatePosition 1 4 3)) Leaf) 2785 , testCase "lexical binder shadows an external value" $ 2786 let bound = gateCore 2 (ofNumber 0) (gatePosition 1 5 4) (gatePosition 1 6 5) 2787 in check (ofList [Fork (ofString "x") (Stem Leaf)]) "x : x" (parserOk 2788 (gateCore 4 bound (gatePosition 1 1 0) (gatePosition 1 6 5)) Leaf) 2789 , testCase "unresolved name reports its original position" $ 2790 check Leaf "u" (parserErr 2791 (portableDiagnostic (ofNumber 20) (ofString "u") Leaf 2792 (gatePosition 1 1 0) Leaf) 2793 (gateAst 2 (ofString "u") (gatePosition 1 1 0) (gatePosition 1 2 1))) 2794 , testCase "grouping carries its span into the core" $ 2795 check Leaf "(t)" (parserOk 2796 (gateCore 1 Leaf (gatePosition 1 1 0) (gatePosition 1 4 3)) Leaf) 2797 ] 2798 where 2799 check environment input expected = 2800 foldl apply (gateResolveEnv Map.! "resolveGateSource") [environment, ofString input] @?= expected 2801 2802 -- -------------------------------------------------------------------------- 2803 -- Reference S/K/I compiler for the feasibility gate 2804 -- -------------------------------------------------------------------------- 2805 2806 gateCompileEnv :: Env 2807 gateCompileEnv = fixtureExports "source.compile" 2808 {-# NOINLINE gateCompileEnv #-} 2809 2810 gateCompileBaseEnv :: Env 2811 gateCompileBaseEnv = fixtureExports "base" 2812 {-# NOINLINE gateCompileBaseEnv #-} 2813 2814 gateCompilationTests :: TestTree 2815 gateCompilationTests = testGroup "Feasibility-gate source compiler" 2816 [ testCase "Haskell reducer executes the exported conformance artifact" $ 2817 withSystemTempDirectory "gate-conforms-bundle" $ \dir -> do 2818 let path = dir </> "conforms.arboricx" 2819 packCompilerFile "./test/sourceGateCompiler.tri" 2820 path [pack "conforms"] 2821 bytes <- BS.readFile path 2822 case decodeBundle bytes of 2823 Left diagnostic -> assertFailure diagnostic 2824 Right bundle -> do 2825 let artifact = reconstructBundleTerms (bundleNodes bundle) 2826 V.! fromIntegral (head (bundleRoots bundle)) 2827 corpus = 2828 [ ("t", "t", trueT) 2829 , ("t t", "t", falseT) 2830 , ("t t t", "t t t", trueT) 2831 , ("(x : x) t", "t", trueT) 2832 , ("(x : t) (t t)", "t", trueT) 2833 , ("u", "u", trueT) 2834 , ("u", "t", falseT) 2835 , (nestedIdentity 16, "t", trueT) 2836 , (nestedBinders 16, "t", trueT) 2837 ] 2838 forM_ corpus $ \(source, expectedSource, expected) -> 2839 apply (apply artifact (ofString source)) (ofString expectedSource) 2840 @?= expected 2841 , testCase "independent golden results match the Haskell artifact" $ 2842 withSystemTempDirectory "gate-golden-bundle" $ \dir -> do 2843 let path = dir </> "matches.arboricx" 2844 packCompilerFile "./test/sourceGateCompiler.tri" 2845 path [pack "matchesExpected"] 2846 bytes <- BS.readFile path 2847 goldenEnv <- evaluateCompilerFile "./test/sourceGateExpected.tri" 2848 let unknown = parserErr 2849 (portableDiagnostic (ofNumber 20) (ofString "u") Leaf 2850 (gatePosition 1 1 0) Leaf) 2851 (gateAst 2 (ofString "u") (gatePosition 1 1 0) (gatePosition 1 2 1)) 2852 unsupported = parserErr 2853 (portableDiagnostic (ofNumber 4) Leaf (ofNumber 35) 2854 (gatePosition 1 1 0) Leaf) 2855 (ofString "#") 2856 badEscape = parserErr 2857 (portableDiagnostic (ofNumber 6) (ofNumber 34) (ofNumber 113) 2858 (gatePosition 1 3 2) Leaf) 2859 (ofString "\\q\"") 2860 unfinishedString = parserErr 2861 (portableDiagnostic (ofNumber 5) (ofNumber 34) Leaf 2862 (gatePosition 1 1 0) Leaf) Leaf 2863 unfinishedBlock = parserErr 2864 (portableDiagnostic (ofNumber 5) (ofString "-|") Leaf 2865 (gatePosition 1 1 0) Leaf) Leaf 2866 unknownInList = parserErr 2867 (portableDiagnostic (ofNumber 20) (ofString "u") Leaf 2868 (gatePosition 1 2 1) Leaf) 2869 (gateAst 2 (ofString "u") 2870 (gatePosition 1 2 1) (gatePosition 1 3 2)) 2871 missingListClose = parserErr 2872 (portableDiagnostic (ofNumber 2) (ofNumber 10) Leaf Leaf Leaf) 2873 Leaf 2874 comma = parserErr 2875 (portableDiagnostic (ofNumber 2) (ofNumber 10) (ofNumber 20) 2876 (gatePosition 1 3 2) Leaf) 2877 (ofList 2878 [ gateToken 20 Leaf (gatePosition 1 3 2) (gatePosition 1 4 3) 2879 , gateToken 1 (ofString "t") (gatePosition 1 5 4) (gatePosition 1 6 5) 2880 , gateToken 10 Leaf (gatePosition 1 6 5) (gatePosition 1 7 6) 2881 ]) 2882 golden = 2883 [ ("t", "goldenLeaf", parserOk Leaf Leaf) 2884 , ("t t", "goldenStem", parserOk (Stem Leaf) Leaf) 2885 , ("t t t", "goldenFork", parserOk (Fork Leaf Leaf) Leaf) 2886 , ("(x : x) t", "goldenLeaf", parserOk Leaf Leaf) 2887 , ("42", "goldenFortyTwo", parserOk (ofNumber 42) Leaf) 2888 , ("\"hi\"", "goldenHi", parserOk (ofString "hi") Leaf) 2889 , ("|-note-| \"hi\"", "goldenHi", parserOk (ofString "hi") Leaf) 2890 , ("u", "goldenUnknown", unknown) 2891 , ("#", "goldenUnsupported", unsupported) 2892 , ("\"\\q\"", "goldenBadEscape", badEscape) 2893 , ("\"hi", "goldenUnfinishedString", unfinishedString) 2894 , ("|-oops", "goldenUnfinishedBlock", unfinishedBlock) 2895 , ("[]", "goldenLeaf", parserOk Leaf Leaf) 2896 , ("[t 2]", "goldenListTwo", 2897 parserOk (ofList [Leaf, ofNumber 2]) Leaf) 2898 , ("[t\n2]", "goldenListTwo", 2899 parserOk (ofList [Leaf, ofNumber 2]) Leaf) 2900 , ("[[t] (t t)]", "goldenNestedList", 2901 parserOk (ofList [ofList [Leaf], Stem Leaf]) Leaf) 2902 , ("[u]", "goldenUnknownInList", unknownInList) 2903 , ("[t", "goldenMissingListClose", missingListClose) 2904 , ("[t, t]", "goldenComma", comma) 2905 ] 2906 case decodeBundle bytes of 2907 Left diagnostic -> assertFailure diagnostic 2908 Right bundle -> do 2909 let artifact = reconstructBundleTerms (bundleNodes bundle) 2910 V.! fromIntegral (head (bundleRoots bundle)) 2911 forM_ golden $ \(source, name, expected) -> do 2912 Map.lookup name goldenEnv @?= Just expected 2913 apply (apply artifact (ofString source)) expected @?= trueT 2914 case Map.lookup "goldenUnknownWrongPosition" goldenEnv of 2915 Nothing -> assertFailure "missing incorrect-position control" 2916 Just wrong -> do 2917 apply (apply artifact (ofString "u")) wrong @?= falseT 2918 apply (apply artifact (ofString "t t")) 2919 (parserOk Leaf Leaf) @?= falseT 2920 , testCase "self-built resolver export executes through its wrapper" $ do 2921 env <- evaluateCompilerFile "./test/sourceGateCompileImport.tri" 2922 Map.lookup "main" env @?= Just (parserOk Leaf Leaf) 2923 , testCase "leaf compiles to raw Leaf" $ 2924 check "t" (parserOk Leaf Leaf) 2925 , testCase "decimal literal compiles to the canonical number tree" $ do 2926 check "42" (parserOk (ofNumber 42) Leaf) 2927 check "000" (parserOk Leaf Leaf) 2928 , testCase "list element boundaries distinguish atoms from grouped applications" $ do 2929 check "[t t]" (parserOk (ofList [Leaf, Leaf]) Leaf) 2930 check "[(t t)]" (parserOk (ofList [Stem Leaf]) Leaf) 2931 check "[t\n2]" (parserOk (ofList [Leaf, ofNumber 2]) Leaf) 2932 , testCase "lists compile to canonical Fork cells" $ do 2933 check "[]" (parserOk Leaf Leaf) 2934 check "[t 2]" (parserOk (ofList [Leaf, ofNumber 2]) Leaf) 2935 check "[[t] (t t)]" (parserOk 2936 (ofList [ofList [Leaf], Stem Leaf]) Leaf) 2937 check "[t\n2]" (parserOk (ofList [Leaf, ofNumber 2]) Leaf) 2938 check "(x : [x]) t" (parserOk (ofList [Leaf]) Leaf) 2939 , testCase "string literal compiles to the canonical character list" $ do 2940 check "\"hi\"" (parserOk (ofString "hi") Leaf) 2941 check "(x : x) \"hi\"" (parserOk (ofString "hi") Leaf) 2942 , testCase "comments and literals compose without a special host lexer" $ do 2943 check "42 --ok" (parserOk (ofNumber 42) Leaf) 2944 check "|-note-| \"hi\"" (parserOk (ofString "hi") Leaf) 2945 , testCase "application evaluates to raw Stem and Fork" $ do 2946 check "t t" (parserOk (Stem Leaf) Leaf) 2947 check "t t t" (parserOk (Fork Leaf Leaf) Leaf) 2948 , testCase "identity behaves correctly under arguments" $ 2949 checkFunction "x : x" [ofNumber 5] (ofNumber 5) 2950 , testCase "constant behaves correctly under arguments" $ 2951 checkFunction "x : t" [ofNumber 5] Leaf 2952 , testCase "nested lambdas preserve the outer argument" $ 2953 checkFunction "x : y : x" [ofNumber 5, ofNumber 8] (ofNumber 5) 2954 , testCase "pruned bracket agrees with the retained reference" $ do 2955 let term kind payload = Fork (ofNumber kind) (Fork payload Leaf) 2956 var = term 2 Leaf 2957 leaf = term 1 Leaf 2958 app f x = term 3 (Fork f x) 2959 terms = [var, leaf, app var leaf, app leaf var, app var var] 2960 compiled method value = apply (gateCompileEnv Map.! "sourceRunTerm") 2961 (apply (gateCompileEnv Map.! method) value) 2962 forM_ terms $ \term -> 2963 case (compiled "sourceBracketReference" term, 2964 compiled "sourceBracket" term) of 2965 (Fork referenceTag (Fork reference Leaf), 2966 Fork optimizedTag (Fork optimized Leaf)) 2967 | referenceTag == trueT && optimizedTag == trueT -> 2968 forM_ [Leaf, Stem Leaf] $ \argument -> 2969 apply optimized argument @?= apply reference argument 2970 other -> assertFailure ("Expected two bracketed trees, got " ++ show other) 2971 , testCase "star eta and retained B/C reference agree when applied" $ do 2972 let compiled method source = 2973 case foldl apply (gateResolveEnv Map.! "resolveGateSource") [Leaf, ofString source] of 2974 Fork tag (Fork core Leaf) | tag == trueT -> apply (gateCompileEnv Map.! method) core 2975 other -> error ("reference corpus failed resolution: " ++ show other) 2976 corpus = 2977 [ ("x : x", [ofNumber 5], ofNumber 5) 2978 , ("x y : x", [ofNumber 5, ofNumber 8], ofNumber 5) 2979 , ("x y : y", [ofNumber 5, ofNumber 8], ofNumber 8) 2980 , ("x : x x", [Stem Leaf], Fork Leaf (Stem Leaf)) 2981 , ("f x : f x", [Stem Leaf, Leaf], Fork Leaf Leaf) 2982 , ("f x y : f (x y)", [Stem Leaf, Stem Leaf, Leaf], 2983 Fork Leaf (Fork Leaf Leaf)) 2984 , ("x : [x 2]", [Leaf], ofList [Leaf, ofNumber 2]) 2985 , ("a : (a : a)", [Leaf, Stem Leaf], Stem Leaf) 2986 ] 2987 forM_ corpus $ \(source, arguments, expected) -> 2988 forM_ ["sourceCompileCoreReference", "sourceCompileCore", 2989 "sourceCompileCoreKiselyov"] $ \method -> 2990 case compiled method source of 2991 Fork tag (Fork fn Leaf) | tag == trueT -> 2992 foldl apply fn arguments @?= expected 2993 other -> assertFailure ("Failed to compile " ++ method ++ " " 2994 ++ source ++ ": " ++ show other) 2995 , testCase "application inside a lambda behaves correctly" $ 2996 checkFunction "f : x : f x" [Stem Leaf, ofNumber 8] (Fork Leaf (ofNumber 8)) 2997 , testCase "external tree is applied without host lookup" $ 2998 foldl apply (gateCompileEnv Map.! "compileGateSource") 2999 [ofList [Fork (ofString "f") (Stem Leaf)], ofString "f t"] @?= 3000 parserOk (Fork Leaf Leaf) Leaf 3001 , testCase "backend compiles a recursive source function" $ do 3002 let source = "xs : y (self : rest : triage t (h : self h) (a : b : self a) rest) xs" 3003 external = ofList [Fork (ofString name) (gateCompileBaseEnv Map.! name) 3004 | name <- ["y", "triage"]] 3005 compiled = foldl apply (gateCompileEnv Map.! "compileGateSource") [external, ofString source] 3006 case compiled of 3007 Fork tag (Fork compiled Leaf) | tag == trueT -> do 3008 apply compiled Leaf @?= Leaf 3009 apply compiled (Stem Leaf) @?= Leaf 3010 apply compiled (Fork (Stem Leaf) Leaf) @?= Leaf 3011 other -> assertFailure ("Expected compiled recursive function, got " ++ show other) 3012 , testCase "unresolved name stays a diagnostic" $ 3013 check "u" (parserErr 3014 (portableDiagnostic (ofNumber 20) (ofString "u") Leaf 3015 (gatePosition 1 1 0) Leaf) 3016 (gateAst 2 (ofString "u") (gatePosition 1 1 0) (gatePosition 1 2 1))) 3017 ] 3018 where 3019 nestedIdentity depth = iterate (\body -> "(x : x) (" ++ body ++ ")") "t" !! depth 3020 nestedBinders depth = "(" ++ concatMap (\i -> "x" ++ show i ++ " : ") 3021 [0 .. depth - 1] ++ "x0) " ++ unwords (replicate depth "t") 3022 compile input = foldl apply (gateCompileEnv Map.! "compileGateSource") [Leaf, ofString input] 3023 check input expected = compile input @?= expected 3024 checkFunction input args expected = case compile input of 3025 Fork tag (Fork fn Leaf) | tag == trueT -> foldl apply fn args @?= expected 3026 other -> assertFailure ("Expected compiled function, got " ++ show other) 3027 3028 -- -------------------------------------------------------------------------- 3029 -- Binary parser combinator tests 3030 -- -------------------------------------------------------------------------- 3031 3032 parserOk :: T -> T -> T 3033 parserOk val rest = Fork trueT (Fork val rest) 3034 3035 parserErr :: T -> T -> T 3036 parserErr code rest = Fork falseT (Fork code rest) 3037 3038 -- -------------------------------------------------------------------------- 3039 -- IO driver tests 3040 -- -------------------------------------------------------------------------- 3041 3042 registryContractTests :: TestTree 3043 registryContractTests = testCase "Registry hash contract: bounded rejection before service dispatch" $ 3044 withSystemTempDirectory "registry-contract" $ \dir -> do 3045 env <- evaluateCompilerFile "./lib/arboricx/server.tri" 3046 let call name = foldl apply (env Map.! name) 3047 http name = foldl apply (fixtureExports "http" Map.! name) 3048 valid = concat (replicate 4 "0123456789abcdef") 3049 hash = ofString valid 3050 diagnostic = ofString "expected 64 lowercase hexadecimal hash characters" 3051 ok value rest = Fork (Stem Leaf) (Fork value rest) 3052 err value rest = Fork Leaf (Fork value rest) 3053 root = ofString "/registry" 3054 expectedPath = "/registry/objects/012/" ++ valid ++ ".arboricx" 3055 invalid = ["", replicate 63 'a', replicate 65 'a', replicate 64 'A', 3056 'g' : tail valid, take 32 valid ++ "g" ++ drop 33 valid, 3057 take 63 valid ++ "g", "../secret", "abc/../../secret", 3058 take 63 valid ++ "/", replicate 65536 'a'] 3059 -- All host byte values, not only selected examples. 3060 forM_ [0..255] $ \byte -> do 3061 let expected = if byte `elem` map (toInteger . fromEnum) "0123456789abcdef" 3062 then Stem Leaf else Leaf 3063 call "bundleHexByte?" [ofNumber byte] @?= expected 3064 forM_ ("0123456789abcdef" :: String) $ \digit -> 3065 call "bundleHashValid?" [ofString (replicate 64 digit)] @?= Stem Leaf 3066 call "bundleHashC" [hash, ofNumber 7] @?= ok hash (ofNumber 7) 3067 forM_ invalid $ \bad -> 3068 call "bundleHashC" [ofString bad, ofNumber 7] @?= err diagnostic (ofNumber 7) 3069 -- Arbitrary malformed trees are rejected as well as canonical byte lists. 3070 forM_ [Stem Leaf, Fork (Stem Leaf) Leaf, 3071 foldr Fork (Stem Leaf) (replicate 64 (ofNumber 97)), 3072 Fork (Fork (Fork Leaf Leaf) Leaf) (ofString (tail valid))] $ \bad -> 3073 call "bundleHashValid?" [bad] @?= Leaf 3074 -- A 65th cons is sufficient evidence: neither its byte nor tail is forced. 3075 let oversized = foldr Fork 3076 (Fork (error "65th byte inspected") (error "oversized suffix inspected")) 3077 (replicate 64 (ofNumber 97)) 3078 call "bundleHashValid?" [oversized] @?= Leaf 3079 let malformedByte = foldr Fork 3080 (Fork (error "extra byte bit inspected") (error "extra byte suffix inspected")) 3081 [Stem Leaf, Leaf, Leaf, Leaf, Leaf, Stem Leaf, Stem Leaf] 3082 call "bundleHexByte?" [malformedByte] @?= Leaf 3083 call "bundleObjectPath" [root, hash] @?= ofString expectedPath 3084 let artifacts = fixtureExports "handler_artifacts" 3085 handlers = fixtureExports "handlers" 3086 observe value = apply (handlers Map.! "view") (apply (artifacts Map.! "load") value) 3087 completed value = Fork (ofNumber 100) value 3088 invalidReply = err (ofList [ofString "invalid-digest", diagnostic]) Leaf 3089 -- Independent service-boundary oracle: invalid input performs no request; 3090 -- valid input emits exactly one load and preserves both ordinary Results. 3091 forM_ invalid $ \bad -> observe (ofString bad) @?= completed invalidReply 3092 case observe hash of 3093 Fork tag (Fork event (Fork labels (Fork continuation Leaf))) -> do 3094 tag @?= ofNumber 106 3095 event @?= ofList [ofString "artifacts", ofString "load", hash] 3096 labels @?= Leaf 3097 forM_ [ok (ofString "bundle") Leaf, err (ofString "missing") Leaf] $ \reply -> 3098 apply continuation reply @?= completed reply 3099 _ -> assertFailure "expected one artifact-load service request" 3100 3101 -- Tiny source probes run through the authenticated portable compiler fixture. 3102 -- Force complete generated trees before applying deliberately partial host data. 3103 localControlTests :: TestTree 3104 localControlTests = testGroup "Local control construction and non-strictness" 3105 [ testCase "case constructs code, skips unselected work and preserves wildcard laziness" $ do 3106 selected <- compileLocalControl [] $ unlines 3107 [ "main input = case input of" 3108 , " t = 23" 3109 , " _ = let rec loop x = loop x in loop t" ] 3110 apply selected Leaf @?= ofNumber 23 3111 ignored <- compileLocalControl [] "main input = case input of\n _ = 23" 3112 apply ignored (error "wildcard forced its subject") @?= ofNumber 23 3113 first <- compileLocalControl [] "main input = case input of\n (head, _) = head\n _ = t" 3114 apply first (Fork (ofNumber 23) (error "case forced an ignored sibling")) @?= ofNumber 23 3115 , testCase "local recursive hash worker rejects poisoned byte/tail with bounded inspection" $ do 3116 server <- evaluateCompilerFile "./lib/arboricx/server.tri" 3117 math <- evaluateCompilerFile "./lib/math/native.tri" 3118 function <- compileLocalControl 3119 [("true", Stem Leaf), ("false", Leaf), 3120 ("hex", server Map.! "bundleHexByte?"), ("subtract", math Map.! "subtract")] $ unlines 3121 [ "main hash = (let rec" 3122 , " walk [] 0 = true" 3123 , " walk _ 0 = false" 3124 , " walk [byte | rest] remaining = case (hex byte) of" 3125 , " ^true = walk rest (subtract remaining 1)" 3126 , " _ = false" 3127 , " walk _ _ = false" 3128 , " in walk hash 64)" ] 3129 apply function (ofString (replicate 64 'a')) @?= Stem Leaf 3130 let oversized = foldr Fork 3131 (Fork (error "local worker inspected byte 65") (error "local worker inspected suffix")) 3132 (replicate 64 (ofNumber 97)) 3133 malformedByte = foldr Fork 3134 (Fork (error "local worker inspected extra bit") (error "local worker inspected numeral suffix")) 3135 [Stem Leaf, Leaf, Leaf, Leaf, Leaf, Stem Leaf, Stem Leaf] 3136 apply function oversized @?= Leaf 3137 apply function (Fork malformedByte (error "local worker continued after malformed byte")) @?= Leaf 3138 ] 3139 3140 compileLocalControl :: [(String, T)] -> String -> IO T 3141 compileLocalControl bindings source = do 3142 unit <- evaluateCompilerFile "./lib/source/unit.tri" 3143 let external = ofList [Fork (ofString name) value | (name,value) <- bindings] 3144 answer = apply (apply (unit Map.! "compileStableModuleDefinitions") external) (ofString source) 3145 forceTree Leaf = () 3146 forceTree (Stem child) = forceTree child 3147 forceTree (Fork left right) = forceTree left `seq` forceTree right 3148 case answer of 3149 Fork (Stem Leaf) (Fork (Fork (Fork name function) Leaf) Leaf) -> do 3150 name @?= ofString "main" 3151 evaluate (forceTree function) 3152 pure function 3153 other -> assertFailure ("local control compilation failed: " ++ show other) >> pure Leaf 3154 3155 containsAll :: String -> [String] -> Assertion 3156 containsAll text needles = 3157 forM_ needles $ \needle -> 3158 assertBool ("expected " ++ show needle ++ " in: " ++ text) (needle `isInfixOf` text) 3159 3160 3161 ioOkResult :: T -> T 3162 ioOkResult val = Fork (Stem Leaf) (Fork val Leaf) 3163 3164 ioErrResult :: String -> T 3165 ioErrResult msg = Fork Leaf (Fork (ofString msg) Leaf)