packages feed

sensei-0.9.0: test/GHC/DiagnosticSpec.hs

{-# LANGUAGE CPP #-}
module GHC.DiagnosticSpec (spec) where

import           Prelude hiding (span)

import           Helper hiding (diagnostic)

import           System.Process
import           System.Environment
import           Data.Text (Text)

import           Language.Haskell.GhciWrapper (lookupGhc)
import           GHC.Diagnostic

data Requirement = NoRequirement | RequireGhc912

test :: HasCallStack => FilePath -> [String] -> Maybe Action -> Spec
test name args = testWith name NoRequirement args

testWith :: HasCallStack => FilePath -> Requirement -> [String] -> Maybe Action -> Spec
testWith name requirement extraArgs action = it name $ do
  err <- translate <$> ghc ["-fno-diagnostics-show-caret"]
  json <- encodeUtf8 <$> ghc ["-fdiagnostics-as-json", "--interactive", "-ignore-dot-ghci"]
  ensureFile (dir </> "err.out") (encodeUtf8 err)
  ensureFile (dir </> "err.json") json
  Just diagnostic <- return $ parse json
  when shouldRun $ do
    format diagnostic `shouldBe` err
  normalizeFileName <$> analyze diagnostic `shouldBe` action
  where
    dir :: FilePath
    dir = "test" </> "assets" </> name

    ghc :: [String] -> IO String
    ghc args = do
      requireGhc [9,10]
      bin <- lookupGhc <$> getEnvironment
      let
        process :: CreateProcess
        process = proc bin ("-fno-code" : args ++ extraArgs ++ [dir </> "Foo.hs"])
      (_, _, err) <- readCreateProcessWithExitCode process ""
      return err

    translate :: String -> String
    translate = map \ case
      '‘' -> '`'
      '’' -> '\''
      c -> c

    shouldRun :: Bool
    shouldRun = case requirement of
      NoRequirement -> True
      RequireGhc912 ->
#if __GLASGOW_HASKELL__ < 912
        False
#else
        True
#endif

normalizeFileName :: Action -> Action
normalizeFileName = \ case
  Choices choices -> Choices $ map normalizeFileName choices
  AddExtension _ name -> AddExtension "Foo.hs" name
  Replace span substitute -> Replace span {file = "Foo.hs"} substitute

ftest :: HasCallStack => FilePath -> [String] -> Maybe Action -> Spec
ftest name args = focus . test name args

xtest :: HasCallStack => FilePath -> [String] -> Maybe Action -> Spec
xtest name args = before_ pending . test name args

_ignore :: ()
_ignore = let _ = (ftest, xtest) in ()

replace :: Location -> Location -> Text -> Action
replace start end = Replace (Span "Foo.hs" start end)

replace_ :: Location -> Location -> Text -> Maybe Action
replace_ start end = Just . replace start end

addExtension :: Text -> Maybe Action
addExtension = Just . AddExtension "Foo.hs"

spec :: Spec
spec = do
  describe "format" $ do
    test "not-in-scope" [] Nothing
    test "not-in-scope-perhaps-use" [] $ replace_ (Location 2 7) (Location 2 14) "filter"
    test "not-in-scope-perhaps-use-one-of-these" [] . Just . Choices $ map
      (replace (Location 2 7) (Location 2 11)) [
        "foldl"
      , "foldr"
      ]
    test "not-in-scope-perhaps-use-multiline" [] . Just . Choices $ map
      (replace (Location 3 7) (Location 3 11)) [
        "foldl"
      , "foldr"
      ]
    test "use-BlockArguments" [] $ addExtension "BlockArguments"
    test "use-TemplateHaskellQuotes" [] $ addExtension "TemplateHaskellQuotes"
    testWith "redundant-import" RequireGhc912 ["-Wall", "-Werror"] $ replace_ (Location 2 1) (Location 3 1) ""
    test "non-existing" [] Nothing
    test "parse-error" [] Nothing
    test "lex-error" [] Nothing
    test "multiple-error-messages" [] Nothing

  describe "extractIdentifiers" $ do
    it "extracts identifiers" $ do
      extractIdentifiers ".. `foldl' ..., `foldr' .." `shouldBe` ["foldl", "foldr"]

  describe "applyReplace" $ do
    it "replaces a given source span with a substitute" $ do
      applyReplace (Location 2 7) (Location 2 14) "filter" [
          "module Foo where"
        , "foo = filter_ p xs"
        ] `shouldBe` [
          "module Foo where"
        , "foo = filter p xs"
        ]

    it "correctly handles source spans that span over multiple lines" $ do
      applyReplace (Location 2 8) (Location 3 7) "Ya" [
          "module Foo where"
        , "import Data.Maybe"
        , "foo = bar"
        , "one = two"
        ] `shouldBe` [
          "module Foo where"
        , "import Yabar"
        , "one = two"
        ]