content: {-
    Copyright (c) Meta Platforms, Inc. and affiliates.
    All rights reserved.
  
    This source code is licensed under the BSD-style license found in the
    LICENSE file in the root directory of this source tree.
  -}
  
  module Glean.Glass.Regression.Tests (
    testDocumentSymbolListX,
    testDescribeSymbolMatchesPath,
    testFindReferences,
    testDescribeSymbolComments,
    testDescribeSymbolHasAnnotations,
    testDescribeSymbolHasVisibility,
    testSearchRelated,
    testSymbolLocation,
  ) where
  
  import Data.Default
  import Data.List
  import qualified Data.Map as Map
  import Data.Text (Text)
  import qualified Data.Text as Text
  import Test.HUnit hiding (Path)
  
  import Glean
  import Glean.LocalOrRemote
  import Glean.Util.Some
  
  import Glean.Glass.Handler.Documents
  import Glean.Glass.Handler.Symbols
  import Glean.Glass.Types as Glass
  
  import Glean.Glass.Regression.Util
  
  type Getter = IO (Some LocalOrRemote, Repo)
  
  
  -- | Test that documentSymbolListX returns non-empty definitions and
  -- references for the given Path.
  testDocumentSymbolListX :: Path -> Getter -> Test
  testDocumentSymbolListX path get =
    TestLabel "testDocumentSymbolListX" $ TestCase $ do
      (backend, _repo) <- get
      withTestEnv backend $ \env -> do
        let req = DocumentSymbolsRequest
              { documentSymbolsRequest_repository = RepoName "test"
              , documentSymbolsRequest_filepath = path
              , documentSymbolsRequest_range = Nothing
              , documentSymbolsRequest_include_refs = True
              , documentSymbolsRequest_include_xlang_refs = False
              }
        res <- documentSymbolListX env req def
        assertBool "documentSymbolListX"
          (not (null (documentSymbolListXResult_references res)) &&
           not (null (documentSymbolListXResult_definitions res)))
  
  testSymbolLocation :: SymbolId -> Path -> Getter -> Test
  testSymbolLocation sym@(SymbolId name) path get =
    TestLabel (Text.unpack name) $ TestCase $ do
      (backend, _repo) <- get
      withTestEnv backend $ \env -> do
        SymbolLocation{..} <- symbolLocation env sym def
        let LocationRange{..} = symbolLocation_location
        assertEqual "symbolLocation Path matches" locationRange_filepath path
  
  -- | Test that both describeSymbol and symbolLocation for a SymbolId
  -- find the symbol in a given Path.
  testDescribeSymbolMatchesPath
    :: SymbolId -> Path -> Getter -> Test
  testDescribeSymbolMatchesPath sym@(SymbolId name) path get =
    TestLabel (Text.unpack name) $ TestCase $ do
      (backend, _repo) <- get
      withTestEnv backend $ \env -> do
        SymbolDescription{..} <- describeSymbol env sym def
        assertEqual "describeSymbol Path matches"
          (symbolPath_filepath symbolDescription_location)
          path
        SymbolLocation{..} <- symbolLocation env sym def
        let LocationRange{..} = symbolLocation_location
        assertEqual "symbolLocation Path matches" locationRange_filepath path
  
  -- | Test that both describeSymbol has expected comment
  testDescribeSymbolComments
    :: SymbolId -> (Int, Int) -> Getter -> Test
  testDescribeSymbolComments sym@(SymbolId name) (line, col) get =
    TestLabel (Text.unpack name) $ TestCase $ do
      (backend, _repo) <- get
      withTestEnv backend $ \env -> do
        SymbolDescription{..} <- describeSymbol env sym def
        assertEqual "describeSymbol Comment start matches"
          [(fromIntegral line, fromIntegral col)] $
          zip
            (range_lineBegin . locationRange_range . symbolComment_location
              <$> symbolDescription_pretty_comments)
            (range_columnBegin . locationRange_range . symbolComment_location
              <$> symbolDescription_pretty_comments)
  
  -- | Test that describeSymbol has specific annotations
  testDescribeSymbolHasAnnotations
    :: SymbolId -> [(Text, Text)] -> Getter -> Test
  testDescribeSymbolHasAnnotations sym@(SymbolId name) anns get =
    TestLabel (Text.unpack name) $ TestCase $ do
      (backend, _repo) <- get
      withTestEnv backend $ \env -> do
        SymbolDescription{..} <- describeSymbol env sym def
        assertEqual "describeSymbol Annotations equal"
          anns
          (maybe []
            (map $ \Annotation{..} -> (annotation_name, annotation_source))
            symbolDescription_annotations)
  
  -- | Test that describeSymbol has a specific visibility
  testDescribeSymbolHasVisibility
    :: SymbolId -> Visibility -> Getter -> Test
  testDescribeSymbolHasVisibility sym@(SymbolId name) vis get =
    TestLabel (Text.unpack name) $ TestCase $ do
      (backend, _repo) <- get
      withTestEnv backend $ \env -> do
        SymbolDescription{..} <- describeSymbol env sym def
        assertEqual "describeSymbol Visibility equal"
          (Just vis)
          symbolDescription_visibility
  
  -- | Test findReferences and findReferenceRanges: given a SymbolId check
  -- that the number of references returned for each Path matches the input.
  testFindReferences
    :: SymbolId -> [(Path,Int)] -> Getter -> Test
  testFindReferences sym@(SymbolId name) paths get =
    TestLabel (Text.unpack name) $ TestCase $ do
      (backend, _repo) <- get
      withTestEnv backend $ \env -> do
        locs <- findReferenceRanges env sym def
        let
          assertLocsEq s results =
            assertEqual s
              (Map.fromList paths )
              (Map.fromList
                [ (head paths, length paths) | paths <- group (sort results) ])
        assertLocsEq "findReferences Path matches" $
          map locationRange_filepath locs
  
  -- | Test search related contains an edge
  testSearchRelated
    :: SymbolId
    -> Bool
    -> RelationDirection
    -> RelationType
    -> (SymbolId, SymbolId)
    -> Getter -> Test
  testSearchRelated sym recurse dir rel (parent, child) get =
    TestLabel label $ TestCase $ do
      (backend, _repo) <- get
      withTestEnv backend $ \env -> do
        SearchRelatedResult{..} <-
          searchRelated env sym def SearchRelatedRequest {..}
        let
          actual = -- better error messages
            if
              any
                ((==) relatedSymbols_parent . Glass.relatedSymbols_parent)
                searchRelatedResult_edges &&
              any
                ((==) relatedSymbols_child . Glass.relatedSymbols_child)
                searchRelatedResult_edges
            then [(parent, arrow, child)]
            else
              map
                (\(RelatedSymbols p c _) -> (p, arrow, c))
                searchRelatedResult_edges
        assertEqual
          "searchRelated contains edge" [(parent, arrow, child)] actual
    where
      label =
        showSymbolId sym <>
        ": " <>
        showSymbolId parent <>
        " " <>
        arrow <>
        " " <>
        showSymbolId child
      showSymbolId (SymbolId sym) = Text.unpack sym
      searchRelatedRequest_relatedBy = rel
      searchRelatedRequest_relation = dir
      searchRelatedRequest_recursive = recurse
      searchRelatedRequest_filter = Nothing
      searchRelatedRequest_detailedResults = False -- don't compute details
      relatedSymbols_parent = parent
      relatedSymbols_child = child
      arrowTail = if recurse then "-/ /-" else "--"
      arrow = case dir of
        RelationDirection_Parent -> arrowTail <> show rel <> "->"
        RelationDirection_Child -> "<-" <> show rel <> arrowTail
        RelationDirection__UNKNOWN{} -> "??" <> show rel <> "??"
  