packages feed

ychr-0.1.0.0: test/YCHR/CollectTest.hs

{-# LANGUAGE OverloadedStrings #-}

module YCHR.CollectTest (tests) where

import Data.Map.Strict qualified as Map
import Data.Text (Text)
import Test.Tasty (TestTree, testGroup)
import Test.Tasty.HUnit (testCase, (@?=))
import YCHR.Internal.Collect
import YCHR.Internal.Collected qualified as C
import YCHR.Internal.Diagnostic (Diagnostic (..), noDiag)
import YCHR.Internal.PExpr (PExpr (Atom))
import YCHR.Internal.Parsed

tests :: TestTree
tests =
  testGroup
    "Collect"
    [ testCase "no seeds, no closure" $
        resolveLibraryClosure False Map.empty [] @?= Right [],
      testCase "resolves a single library import" $
        let libs = Map.fromList [("foo", libMod "foo")]
         in case resolveLibraryClosure False libs [noAnnP "foo"] of
              Right mods -> length mods @?= 1
              Left errs -> fail (show errs),
      testCase "library imports collapse into CollectedImport, name preserved" $
        let userMod_ = userMod [noAnnP (LibraryImport "foo" Nothing)]
            rewritten = rewriteImports [userMod_]
            -- The library/module distinction is erased at the type level
            -- (every import is a CollectedImport); assert the conversion
            -- preserves the source module name.
            names = [imp.node.importModule | m <- rewritten, imp <- m.imports]
         in names @?= ["foo"],
      testCase "transitive library imports resolved" $
        let libA = (libMod "a") {imports = [noAnnP (LibraryImport "b" Nothing)]}
            libB = libMod "b"
            libs = Map.fromList [("a", libA), ("b", libB)]
         in case resolveLibraryClosure False libs [noAnnP "a"] of
              Right mods -> length mods @?= 2
              Left errs -> fail (show errs),
      testCase "unknown library reports error" $
        resolveLibraryClosure False Map.empty [noAnnP "missing"]
          @?= Left [noDiag (AnnP (UnknownLibrary "missing") dummyLoc (Atom ""))],
      testCase "prelude not auto-included when includeStdlib is False" $
        let libs = Map.fromList [("prelude", libMod "prelude")]
         in resolveLibraryClosure False libs [] @?= Right [],
      testCase "stdlib included when includeStdlib is True" $
        let libs = Map.fromList [("prelude", libMod "prelude")]
         in case resolveLibraryClosure True libs [] of
              Right mods -> length mods @?= 1
              Left errs -> fail (show errs),
      testCase "circular import reports error" $
        let libA = (libMod "a") {imports = [noAnnP (LibraryImport "b" Nothing)]}
            libB = (libMod "b") {imports = [noAnnP (LibraryImport "a" Nothing)]}
            libs = Map.fromList [("a", libA), ("b", libB)]
         in case resolveLibraryClosure False libs [noAnnP "a"] of
              Left errs ->
                any isCircularError errs @?= True
              Right _ -> fail "expected circular import error",
      testCase "addLibraryPrelude prepends prelude to non-prelude libraries" $
        case addLibraryPrelude [libMod "foo", libMod "prelude"] of
          [foo, prelude] -> do
            length foo.imports @?= 1
            length prelude.imports @?= 0
          mods -> fail $ "expected 2 modules, got " ++ show (length mods)
    ]

userMod :: [AnnP Import] -> Module
userMod imps =
  Module
    { name = "user",
      nameLoc = dummyLoc,
      imports = imps,
      decls = [],
      extensionTypes = [],
      typeDecls = [],
      rules = [],
      equations = [],
      extensions = [],
      classExtensions = [],
      exports = Nothing
    }

libMod :: Text -> Module
libMod name =
  Module
    { name = name,
      nameLoc = dummyLoc,
      imports = [],
      decls = [],
      extensionTypes = [],
      typeDecls = [],
      rules = [],
      equations = [],
      extensions = [],
      classExtensions = [],
      exports = Nothing
    }

isCircularError :: Diagnostic CollectError -> Bool
isCircularError (Diagnostic _ (AnnP (CircularLibraryImport _) _ _)) = True
isCircularError _ = False