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.
  -}
  
  {-# LANGUAGE ApplicativeDo #-}
  
  -- | Library for running an indexer and running unit tests on the
  -- resulting Repo.
  module Glean.Regression.Test
    ( -- * your test code
      TestIndex
      -- * make your main
    , mainTestIndex
    , mainTestIndexExternal
    , mainTestIndexGeneric
    , mainTestIndexXlang
    ) where
  
  import Foreign.Marshal.Utils
  import qualified Data.Text as Text
  import Options.Applicative
  import System.FilePath
  import System.IO.Temp
  import Test.HUnit
  import TestRunner
  import Util.IO
  
  import Glean
  import Glean.LocalOrRemote
  import Glean.Indexer
  import Glean.Init (withUnitTestOptions)
  import Glean.Regression.Config
  import Glean.Regression.Indexer
  import Glean.Regression.Snapshot.Driver
  import Glean.Regression.Snapshot.Options
  import Glean.Util.Some (Some(..))
  
  type TestIndex = IO (Some LocalOrRemote, Repo) -> Test
  
  withOutputDir :: String -> Maybe FilePath -> (FilePath -> IO a) -> IO a
  withOutputDir _dir (Just output) act = act output
  withOutputDir dir Nothing act = withSystemTempDirectory dir act
  
  -- | Run a test with an arbitrary indexer
  mainTestIndex
    :: String -- ^ just a string to identify this test
    -> Indexer opts
    -> TestIndex
    -> IO ()
  mainTestIndex dir indexer testIndex =
    mainTestIndexGeneric
      (driverFromIndexer indexer)
      (pure ())
      dir
      (\_ _ _ _ -> testIndex)
  
  -- | Run a test with an external indexer
  mainTestIndexExternal
    :: String -- ^ just a string to identify this test
    -> TestIndex
    -> IO ()
  mainTestIndexExternal dir testIndex =
    mainTestIndexGeneric externalDriver (pure ()) dir (\_ _ _ _ -> testIndex)
  
  createTestConfig :: Text.Text -> String -> String -> Config -> TestConfig
  createTestConfig repo group outDir cfg =
     TestConfig
        { testRepo = Repo repo (Text.pack group)
        , testOutput = if null group
            then outDir
            else outDir </> group
        , testRoot = cfgRoot cfg
        , testProjectRoot = cfgProjectRoot cfg
        , testGroup = group
        , testSchema = cfgSchema cfg
        }
  
  -- | Run a test with an arbitrary indexer
  mainTestIndexGeneric
    :: Driver driverOpts
    -> Parser extraOpts -- ^ parser for extra options to recognise
    -> String -- ^ just a string to identify this test
    -> (extraOpts -> driverOpts -> Config -> TestConfig -> TestIndex)
    -> IO ()
  mainTestIndexGeneric driver extraOptParser dir testIndex = do
    let
      indexer = driverIndexer driver
      parse = (,) <$> indexerOptParser indexer <*> extraOptParser
    withUnitTestOptions (optionsWith parse) $
            \ action (mkcfg, (driverOpts, extraOpts)) -> do
      -- TODO: we're using the options for snapshot tests which have a
      -- couple of flags that don't make sense here: --replace for
      -- example.
      cfg <- mkcfg
      let
        theGroups = driverGroups driver driverOpts
        (platforms, mkLabel) = if null theGroups
            then (["testhash"], const dir)
            else (theGroups, \group -> dir <> " : " <> group)
      withOutputDir dir (cfgOutput cfg) $ \ outDir -> do
        let
          withPlatformTest :: String -> (Test -> IO a) -> IO a
          withPlatformTest platform fn = do
            let testConfig = createTestConfig "test" platform outDir cfg
                withSetup :: ((Some LocalOrRemote, Repo) -> IO a) -> IO a
                withSetup f =
                  withTestBackend testConfig $ \backend -> do
                    driverCreateDatabase driver driverOpts backend
                      (indexerRun indexer) testConfig
                    f (backend, testRepo testConfig)
  
            withLazy withSetup $ \get ->
              fn $ TestLabel (mkLabel platform) $
                testIndex extraOpts driverOpts cfg testConfig get
  
        withMany withPlatformTest platforms $ \tests ->
          testRunnerAction action (TestList tests)
  
  -- | Run a test which runs two indexers, the one in the driver
  --   plus an extra one
  mainTestIndexXlang
    :: (Driver driverOpts, Text.Text) -- ^ Driver and name of db to be created
    -> (Indexer opts, Text.Text) -- ^ Indexer and name of db to be created
    -> String -- ^ just a string to identify this test
    -> Parser extraOpts -- ^ parser for extra options to recognise
    -> (extraOpts -> TestIndex)
    -> IO ()
  mainTestIndexXlang
    (driver, repoName) (indexer', repoName') dir extraOptParser testIndex = do
    let
      indexer = driverIndexer driver
      parse = (,,)
        <$> indexerOptParser indexer
        <*> indexerOptParser indexer'
        <*> extraOptParser
    withUnitTestOptions (optionsWith parse) $
            \ action (mkcfg, (driverOpts, driverOpts', extraOpts)) -> do
      -- TODO: we're using the options for snapshot tests which have a
      -- couple of flags that don't make sense here: --replace for
      -- example.
      cfg <- mkcfg
      let
        -- We ignore groups of second driver
        theGroups = driverGroups driver driverOpts
        (platforms, mkLabel) = if null theGroups
            then (["testhash"], const dir)
            else (theGroups, \group -> dir <> " : " <> group)
      withOutputDir dir (cfgOutput cfg) $ \ outDir -> do
        let
          withPlatformTest :: String -> (Test -> IO a) -> IO a
          withPlatformTest platform fn = do
            let testConfig = createTestConfig repoName platform outDir cfg
                testConfig' = testConfig {
                  testRepo = Repo repoName' (Text.pack platform)
                }
                withSetup :: ((Some LocalOrRemote, Repo) -> IO a) -> IO a
                withSetup f =
                  withTestBackend testConfig $ \backend -> do
                    driverCreateDatabase driver driverOpts backend
                      (indexerRun indexer) testConfig
                    defaultCreateDatabase driverOpts' backend
                      (indexerRun indexer') testConfig'
                    f (backend, testRepo testConfig')
  
            withLazy withSetup $ \get ->
              fn $ TestLabel (mkLabel platform) $ testIndex extraOpts get
  
        withMany withPlatformTest platforms $ \tests ->
          testRunnerAction action (TestList tests)
  