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 InventoryTest
    ( main
    ) where
  
  import Control.Monad (forM)
  import Data.Default
  import qualified Data.HashMap.Strict as HM
  import Data.HashMap.Strict (HashMap)
  import qualified Data.Map as Map
  import Test.HUnit
  
  import TestRunner
  import Util.IO (listDirectoryRecursive)
  
  import Glean.Database.Config
  import Glean.Database.Schema
  import Glean.Database.Schema.Types
  import Glean.Init
  import Glean.Internal.Types (storedSchema_predicateIds)
  import Glean.RTS.Foreign.Inventory (Inventory)
  import qualified Glean.RTS.Foreign.Inventory as Inventory
  
  serializeTest :: Inventory -> IO ()
  serializeTest inventory = assertBool "roundtrip" $
    Inventory.deserialize (Inventory.serialize inventory) == inventory
  
  mkSchema
    :: (forall k v . HashMap k v -> [(k,v)])
    -> SchemaIndex
    -> IO DbSchema
  mkSchema toList schemaIndex =
    newDbSchemaForTesting toList Nothing schemaIndex LatestSchema
      readWriteContent def
  
  main :: IO ()
  main = withUnitTest $ do
    schema <- parseSchemaDir schemaSourceDir
  
    testRunner $ TestList
      [ TestLabel "serialize" $ TestCase $ do
          inventory <- schemaInventory <$> mkSchema HM.toList schema
          serializeTest inventory
      , TestLabel "serializeDeleted" $ TestCase $ do
          dbSchema <- mkSchema HM.toList schema
          let -- inventory serialization/deserialization should work if there
              -- are gaps in the Pid range.
              stored = toStoredSchema dbSchema
              pids = storedSchema_predicateIds stored
              newStored = stored {
                storedSchema_predicateIds = Map.mapKeys (*2) pids
                }
          newDbSchema <- fromStoredSchema Nothing newStored readWriteContent def
          serializeTest (schemaInventory newDbSchema)
      , TestLabel "determinism" $ TestList
        [ TestLabel "permuted schema files" $
          deterministicOnFilesTest schemaSourceDir
        , TestLabel "permuted HashMaps" $
          deterministicOnHashMaps schemaSourceDir
        ]
      ]
  
  deterministicOnFilesTest :: FilePath -> Test
  deterministicOnFilesTest schemaSourceDir = TestCase $ do
    files <- listDirectoryRecursive schemaSourceDir
    permutedFiles <- mapM catSchemaFiles (permuteList files)
    schemas <-
      mapM
        (either error (mkSchema HM.toList) . processOneSchema Nothing)
        permutedFiles
    testDeterminism schemas
  
  deterministicOnHashMaps :: FilePath -> Test
  deterministicOnHashMaps schemaSourceDir = TestCase $ do
    files <- catSchemaFiles =<< listDirectoryRecursive schemaSourceDir
    schemas1 <-
      either error (mkSchema HM.toList . (`SchemaIndex` [])) $
        processSchemaForTesting HM.toList Nothing files
    schemas2 <-
      either error (mkSchema (reverse . HM.toList) . (`SchemaIndex` [])) $
        processSchemaForTesting (reverse . HM.toList) Nothing files
    testDeterminism [schemas1, schemas2]
  
  --------------------------------------------------------------------------------
  
  testDeterminism :: [DbSchema] -> IO ()
  testDeterminism permutations = do
    assertBool "Test input is empty or a singleton" (length permutations > 1)
    inventories <- forM permutations $ \s -> do
      let i = schemaInventory s
      let !serial = Inventory.serialize i
      return (serial, s)
    let unique = Map.fromList inventories
        assertMsg = "Inventory is not deterministic: " <> show (length unique)
  
    assertBool assertMsg (length unique == 1)
  
  permuteList :: [a] -> [[a]]
  permuteList list = [list, reverse list]
  