packages feed

haskell-language-server-1.10.0.0: test/functional/Config.hs

{-# LANGUAGE DeriveGeneric     #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeApplications  #-}
{-# LANGUAGE TypeFamilies      #-}

module Config (tests) where

import           Control.DeepSeq
import           Control.Lens            hiding (List, (.=))
import           Control.Monad
import           Data.Aeson
import           Data.Hashable
import qualified Data.HashMap.Strict     as HM
import qualified Data.Map                as Map
import qualified Data.Text               as T
import           Data.Typeable           (Typeable)
import           Development.IDE         (RuleResult, action, define,
                                          getFilesOfInterestUntracked,
                                          getPluginConfigAction, ideErrorText,
                                          uses_)
import           Development.IDE.Test    (expectDiagnostics)
import           GHC.Generics
import           Ide.Plugin.Config
import           Ide.Types
import           Language.LSP.Test       as Test
import qualified Language.LSP.Types.Lens as L
import           System.FilePath         ((</>))
import           Test.Hls
import           Test.Hls.Command

{-# ANN module ("HLint: ignore Reduce duplication"::String) #-}

tests :: TestTree
tests = testGroup "plugin config" [
      -- Note: there are more comprehensive tests over config in hls-hlint-plugin
      -- TODO: Add generic tests over some example plugin
      configParsingTests, genericConfigTests
    ]

configParsingTests :: TestTree
configParsingTests = testGroup "config parsing"
    [ testCase "empty object as user configuration should not send error logMessage" $ runConfigSession "" $ do
        let config = object []
        sendConfigurationChanged (toJSON config)

        -- Send custom request so server returns a response to prevent blocking
        void $ sendNotification (SCustomMethod "non-existent-method") Null

        logNot <- skipManyTill Test.anyMessage (message SWindowLogMessage)

        liftIO $ (logNot ^. L.params . L.xtype) > MtError
                 || "non-existent-method" `T.isInfixOf` (logNot ^. L.params . L.message)
                    @? "Server sends logMessage with MessageType = Error"
    ]

    where
        runConfigSession :: FilePath -> Session a -> IO a
        runConfigSession subdir  =
            failIfSessionTimeout . runSession hlsCommand fullCaps ("test/testdata" </> subdir)

genericConfigTests :: TestTree
genericConfigTests = testGroup "generic plugin config"
    [
        testCase "custom defaults" $ runConfigSession "diagnostics" $ do
            _doc <- createDoc "Foo.hs" "haskell" "module Foo where\nfoo = False"
            -- getting only the standard diagnostics means the plugin wasn't enabled
            expectDiagnostics standardDiagnostics
   ,    testCase "custom defaults and user config on some other plugin" $ runConfigSession "diagnostics" $ do
            _doc <- createDoc "Foo.hs" "haskell" "module Foo where\nfoo = False"
            -- test that the user config doesn't accidentally override the initial config
            sendConfigurationChanged $ toJSON (changeConfig "someplugin" def{plcHoverOn = False})
            -- getting only the expected diagnostics means the plugin wasn't enabled
            expectDiagnostics standardDiagnostics
    ,   expectFailBecause "partial config is not supported" $
        testCase "custom defaults and non overlapping user config" $ runConfigSession "diagnostics" $ do
            _doc <- createDoc "Foo.hs" "haskell" "module Foo where\nfoo = False"
            -- test that the user config doesn't accidentally override the initial config
            sendConfigurationChanged $ toJSON (changeConfig testPluginId def{plcHoverOn = False})
            -- getting only the expected diagnostics means the plugin wasn't enabled
            expectDiagnostics standardDiagnostics
    ,   testCase "custom defaults and overlapping user plugin config" $ runConfigSession "diagnostics" $ do
            _doc <- createDoc "Foo.hs" "haskell" "module Foo where\nfoo = False"
            -- test that the user config overrides the default initial config
            sendConfigurationChanged $ toJSON (changeConfig testPluginId def{plcGlobalOn = True})
            -- getting only the expected diagnostics means the plugin wasn't enabled
            expectDiagnostics testPluginDiagnostics
    ,   testCase "custom defaults and non plugin user config" $ runConfigSession "diagnostics" $ do
            _doc <- createDoc "Foo.hs" "haskell" "module Foo where\nfoo = False"
            -- test that the user config doesn't accidentally override the initial config
            sendConfigurationChanged $ toJSON (def {formattingProvider = "foo"})
            -- getting only the expected diagnostics means the plugin wasn't enabled
            expectDiagnostics standardDiagnostics
    ]
    where
        standardDiagnostics = [("Foo.hs", [(DsWarning, (1,0), "Top-level binding")])]
        testPluginDiagnostics = [("Foo.hs", [(DsError, (0,0), "testplugin")])]

        runConfigSession subdir =
            failIfSessionTimeout . runSessionWithServer @() plugin ("test/testdata" </> subdir)

        testPluginId = "testplugin"
        -- A disabled-by-default plugin that creates diagnostics
        plugin = mkPluginTestDescriptor' pd testPluginId
        pd plId = (defaultPluginDescriptor plId)
          {
            pluginConfigDescriptor = configDisabled
          , pluginRules = do
              action $ do
                plc <- getPluginConfigAction testPluginId
                when (plcGlobalOn plc && plcDiagnosticsOn plc) $ do
                    files <- getFilesOfInterestUntracked
                    void $ uses_ GetTestDiagnostics $ HM.keys files
              define mempty $ \GetTestDiagnostics file -> do
                let diags = [ideErrorText file "testplugin"]
                return (diags,Nothing)
          }
        -- A config that disables the plugin initially
        configDisabled = defaultConfigDescriptor{
            configInitialGenericConfig = def{plcGlobalOn = False, plcDiagnosticsOn = False}
        }
        changeConfig :: PluginId -> PluginConfig -> Config
        changeConfig plugin conf =
            def{plugins = Map.fromList [(plugin, conf)]}


data GetTestDiagnostics = GetTestDiagnostics
    deriving (Eq, Show, Typeable, Generic)
instance Hashable GetTestDiagnostics
instance NFData   GetTestDiagnostics
type instance RuleResult GetTestDiagnostics = ()