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 TypeApplications #-}
  module IncrementalTest (main) where
  
  import Control.Monad
  import Data.ByteString (ByteString)
  import Data.Default
  import Data.List
  import Test.HUnit
  import System.Info
  import System.Exit
  
  import TestRunner
  
  import Glean.Backend.Types (completePredicates)
  import Glean.Init
  import Glean.Database.Close
  import Glean.Database.Types
  import Glean.Database.Ownership
  import Glean.Database.Test
  import Glean.Derive
  import Glean.Schema.Util (parseRef)
  import qualified Glean.Schema.GleanTest as Glean.Test
  import qualified Glean.Schema.GleanTest.Types as Glean.Test
  import Glean.Query.Angle
  import Glean.Query.Thrift
  import Glean.Typed hiding (end)
  import Glean.Types
  
  
  {-
       a
      / \
     b   c
      \ /
       d
  
  owners:
    nodes:
     A: a, b, c
     B: b, d
     C: c, d
     D: d
    edges:
     A: ab, ac
     B: bd
     C: cd
  -}
  mkGraph :: Env -> Repo -> IO ()
  mkGraph env repo =
    writeFactsIntoDB env repo [ Glean.Test.allPredicates ] $ do
      d <- withUnit "D" $
        makeFact @Glean.Test.Node (Glean.Test.Node_key "d")
      c <- withUnit "C" $ do
        c <- makeFact @Glean.Test.Node (Glean.Test.Node_key "c")
        makeFact_ @Glean.Test.Edge (Glean.Test.Edge_key c d)
        return c
      b <- withUnit "B" $ do
        b <- makeFact @Glean.Test.Node (Glean.Test.Node_key "b")
        makeFact_ @Glean.Test.Edge (Glean.Test.Edge_key b d)
        return b
      withUnit "A" $ do
        a <- makeFact @Glean.Test.Node (Glean.Test.Node_key "a")
        makeFact_ @Glean.Test.Edge (Glean.Test.Edge_key a b)
        makeFact_ @Glean.Test.Edge (Glean.Test.Edge_key a c)
  
  incrementalDB :: Env -> Repo -> Repo -> [ByteString] -> IO Repo
  incrementalDB env base inc prune = do
    kickOffTestDB env inc $ \kickOff ->
      kickOff { kickOff_dependencies = Just $
        Dependencies_pruned def {
          pruned_base = base,
          pruned_units = prune,
          pruned_exclude = True
        } }
    return inc
  
  incrementalTest :: Test
  incrementalTest = TestCase $
  
    -- build a DB with some units and a non-trivial fact graph
    withTestEnv [] $ \env -> do
      let base = Repo "base" "0"
      kickOffTestDB env base id
      mkGraph env base
      completeTestDB env base
  
      -- check the owners of Node "d"
      results <- runQuery_ env base $ query $
        predicate @Glean.Test.Node (rec $ field @"label" (string "d") end)
      case results of
        [Glean.Test.Node id _] -> do
          ownerExpr <- factOwnership env base (Fid id)
          print ownerExpr
          assertBool "owners" $
            case ownerExpr of
              Just (OrOwners [Unit x, Unit y, Unit z]) ->
                sort [x,y,z] == ["B","C","D"]
              _otherwise -> False
        _ -> assertFailure "query failed"
  
      -- ------------------------------------------------------------------
      -- exclude unit A
  
      inc <- incrementalDB env base (Repo "base-inc" "0") ["A"]
      completeTestDB env inc
  
      -- node "a" does not exist
      results <- runQuery_ env inc $ query $
        predicate @Glean.Test.Node (rec $ field @"label" (string "a") end)
      assertEqual "inc 0" [] results
  
      -- edge from "b" (and node "b") exists
      results <- runQuery_ env inc $ query $
        predicate @Glean.Test.Edge
          (rec $ field @"parent" (rec $ field @"label" (string "b") end) end)
      assertEqual "inc 1" 1 (length results)
  
      -- ------------------------------------------------------------------
      -- exclude unit D
  
      inc <- incrementalDB env base (Repo "base-inc" "1") ["D"]
      completeTestDB env inc
  
      -- edges from "b"/"c" to "d" (and therefore node "d") still exist
      results <- runQuery_ env inc $ query $
        predicate @Glean.Test.Edge
          (rec $ field @"child" (rec $ field @"label" (string "d") end) end)
      assertEqual "inc 2" 2 (length results)
  
      -- ------------------------------------------------------------------
      -- exclude all units
  
      inc <- incrementalDB env base (Repo "base-inc" "2") ["A","B","C","D"]
      completeTestDB env inc
  
      -- no nodes exist
      results <- runQuery_ env inc $ query $
        predicate @Glean.Test.Node wild
      assertEqual "inc 3" [] results
  
      -- ------------------------------------------------------------------
      -- exclude A and B, add some new facts
  
      inc <- incrementalDB env base (Repo "base-inc" "3") ["A","B"]
  
      -- the new graph will be
      --              B
      --              |
      --              A
      --             /
      --            C
      --            |
      --            D
  
      writeFactsIntoDB env inc [ Glean.Test.allPredicates ] $ do
        c <- withUnit "C" $ do
          makeFact @Glean.Test.Node (Glean.Test.Node_key "c")
        a <- withUnit "A" $ do
          a <- makeFact @Glean.Test.Node (Glean.Test.Node_key "a")
          makeFact_ @Glean.Test.Edge (Glean.Test.Edge_key a c)
          return a
        withUnit "B" $ do
          b <- makeFact @Glean.Test.Node (Glean.Test.Node_key "b")
          makeFact_ @Glean.Test.Edge (Glean.Test.Edge_key b a)
      completeTestDB env inc
  
      let
        edgesFrom s = fmap sort $ runQuery_ env inc $ query $
          var $ \n ->
            n `where_` [
              wild .= predicate @Glean.Test.Edge (rec $
                field @"parent" (rec $ field @"label" (string s) end) $
                field @"child" (rec $ field @"label" n end)
              end)
            ]
  
      -- check the edges
      r <- edgesFrom "c"
      assertEqual "inc 4" ["d"] r
  
      r <- edgesFrom "a"
      assertEqual "inc 5" ["c"] r
  
      r <- edgesFrom "b"
      assertEqual "inc 6" ["a"] r
  
  stackedIncrementalTest :: Test
  stackedIncrementalTest = TestCase $
    withTestEnv [] $ \env -> do
      let
        deriveAndFinish :: Env -> Repo -> IO ()
        deriveAndFinish env repo = do
          void $ completePredicates env repo (CompletePredicates_axiom def)
          derivePredicate env repo Nothing Nothing
            (parseRef "glean.test.RevEdge") Nothing
          derivePredicate env repo Nothing Nothing
            (parseRef "glean.test.IsParent") Nothing
          derivePredicate env repo Nothing Nothing
            (parseRef "glean.test.SkipRevEdge") Nothing
          completeTestDB env repo
  
        nodesAre test db ns = do
          results <- runQuery_ env db $ query $ predicate @Glean.Test.Node wild
          print results
          assertEqual test ns
            (sort [ x | Glean.Test.Node _ (Just (
                          Glean.Test.Node_key x)) <- results ])
        edgesAre test db es = do
          edges <- runQuery_ env db $ recursive $ query $
            predicate @Glean.Test.Edge wild
          assertEqual test es
            (sort [ (x,y) | Glean.Test.Edge _ (Just (
                      Glean.Test.Edge_key
                        (Glean.Test.Node _ (Just (Glean.Test.Node_key x)))
                        (Glean.Test.Node _ (Just (Glean.Test.Node_key y)))))
              <- edges ])
        parentsAre test db ns = do
          results <- runQuery_ env db $ recursive $ query $ predicate @Glean.Test.IsParent wild
          print results
          assertEqual test ns
            (sort [ x | Glean.Test.IsParent _ (Just (
                          Glean.Test.Node _ (Just (Glean.Test.Node_key x)))) <- results ])
  
        revEdgesAre test db es = do
          edges <- runQuery_ env db $ recursive $ query $
            predicate @Glean.Test.RevEdge wild
          assertEqual test es
            (sort [ (x,y) | Glean.Test.RevEdge _ (Just (
                      Glean.Test.RevEdge_key
                        (Glean.Test.Node _ (Just (Glean.Test.Node_key x)))
                        (Glean.Test.Node _ (Just (Glean.Test.Node_key y)))))
              <- edges ])
  
        skipRevEdgesAre test db es = do
          edges <- runQuery_ env db $ recursive $ query $
            predicate @Glean.Test.SkipRevEdge wild
          assertEqual test es
            (sort [ (x,y) | Glean.Test.SkipRevEdge _ (Just (
                      Glean.Test.SkipRevEdge_key
                        (Glean.Test.Node _ (Just (Glean.Test.Node_key x)))
                        (Glean.Test.Node _ (Just (Glean.Test.Node_key y)))))
              <- edges ])
  
      let base = Repo "base" "0"
      kickOffTestDB env base id
      mkGraph env base
      deriveAndFinish env base
      closeDatabase env base  -- test closing and re-opening the base DB
  
      {-
      base:
           a
          / \
         b   c
          \ /
           d
      -}
  
      inc <- incrementalDB env base (Repo "base-inc" "1") ["A","B"]
      writeFactsIntoDB env inc [ Glean.Test.allPredicates ] $ do
        d <- withUnit "D" $ do
          makeFact @Glean.Test.Node (Glean.Test.Node_key "d")
        a <- withUnit "A" $ do
          a <- makeFact @Glean.Test.Node (Glean.Test.Node_key "a")
          makeFact_ @Glean.Test.Edge (Glean.Test.Edge_key a d)
          return a
        withUnit "B" $ do
          b <- makeFact @Glean.Test.Node (Glean.Test.Node_key "b")
          makeFact_ @Glean.Test.Edge (Glean.Test.Edge_key b a)
      deriveAndFinish env inc
      closeDatabase env base  -- test closing and re-opening the incremental DB
  
      {-
      inc:
           b
           |
           a   c
            \ /
             d
      -}
  
      -- nodes are a,b,c,d
      nodesAre "stacked inc 0n" inc ["a", "b", "c", "d"]
      parentsAre "stacked inc 0p" inc ["a", "b", "c"]
      edgesAre "stacked inc 0e" inc [("a","d"),("b","a"),("c","d")]
      revEdgesAre "stacked inc 0r" inc [("a","b"),("d","a"),("d","c")]
      skipRevEdgesAre "stacked inc 0s" inc [("d","b")]
  
      -- exclude C, and add a new unit E
      inc2 <- incrementalDB env inc (Repo "base-inc" "2") ["C"]
      writeFactsIntoDB env inc2 [ Glean.Test.allPredicates ] $ do
        withUnit "E" $ do
          a <- makeFact @Glean.Test.Node (Glean.Test.Node_key "a")
          e <- makeFact @Glean.Test.Node (Glean.Test.Node_key "e")
          makeFact_ @Glean.Test.Edge (Glean.Test.Edge_key e a)
      deriveAndFinish env inc2
  
      {-
      inc2:
           b e
           |/
           a
            \
             d
      -}
  
      nodesAre "stacked inc 2n" inc2 ["a", "b", "d", "e"]
      edgesAre "stacked inc 2e" inc2 [("a","d"),("b","a"),("e","a")]
      parentsAre "stacked inc 2p" inc2 ["a", "b", "e"]
      revEdgesAre "stacked inc 2r" inc2 [("a","b"),("a","e"),("d","a")]
      skipRevEdgesAre "stacked inc 2s" inc2 [("d","b"),("d","e")]
  
      -- now exclude E
      inc3 <- incrementalDB env inc2 (Repo "base-inc" "3") ["E"]
      deriveAndFinish env inc3
  
      {-
      inc3:
           b
           |
           a
            \
             d
      -}
  
      nodesAre "stacked inc 3n" inc3 ["a", "b", "d"]
      edgesAre "stacked inc 3e" inc3 [("a","d"),("b","a")]
      parentsAre "stacked inc 3p" inc3 ["a", "b"]
      revEdgesAre "stacked inc 3r" inc3 [("a","b"),("d","a")]
      skipRevEdgesAre "stacked inc 3s" inc3 [("d","b")]
  
      -- The new edge E-A should induce retention of A, even if we exclude unit A
      inc4 <- incrementalDB env inc2 (Repo "base-inc" "4") ["A","B","D"]
      deriveAndFinish env inc4
  
      {-
      inc4:
             e
            /
           a
      -}
  
      nodesAre "stacked inc 4n" inc4 ["a", "e"]
      edgesAre "stacked inc 4e" inc4 [("e","a")]
      parentsAre "stacked inc 4p" inc4 ["e"]
      revEdgesAre "stacked inc 4r" inc4 [("a","e")]
      skipRevEdgesAre "stacked inc 4s" inc4 []
  
      inc5 <- incrementalDB env inc (Repo "base-inc" "5") ["C"]
      writeFactsIntoDB env inc5 [ Glean.Test.allPredicates ] $ do
        b <- withUnit "B" $ makeFact @Glean.Test.Node (Glean.Test.Node_key "b")
        withUnit "E" $ do
          e <- makeFact @Glean.Test.Node (Glean.Test.Node_key "e")
          makeFact_ @Glean.Test.Edge (Glean.Test.Edge_key b e)
      deriveAndFinish env inc5
  
      {-
      inc5:
           b---e
           |
           a
            \
             d
      -}
  
      nodesAre "stacked inc 5n" inc5 ["a", "b", "d", "e"]
      edgesAre "stacked inc 5e" inc5 [("a","d"),("b","a"),("b","e")]
      parentsAre "stacked inc 5p" inc5 ["a", "b"]
      revEdgesAre "stacked inc 5r" inc5 [("a","b"),("d","a"),("e","b")]
      skipRevEdgesAre "stacked inc 5s" inc5 [("d","b")]
  
      -- Check ownership of the `IsParent (Node "b")` fact in base-inc/5
      -- It should be B|E, because it was derived from B in the base DB
      -- and from E in the increment.
      results <- runQuery_ env inc5 $ recursive $ query $
        predicate @Glean.Test.IsParent $
          predicate @Glean.Test.Node $ rec $ field @"label" "b" end
      case results of
        [Glean.Test.IsParent id _] -> do
          ownerExpr <- factOwnership env inc5 (Fid id)
          assertBool "IsParent owner" $ case ownerExpr of
            Just (OrOwners [OrOwners [Unit b],OrOwners [Unit e]]) ->
              sort [b,e] == ["B","E"]
            _ -> False
        _ -> assertFailure "query failed"
  
      inc6 <- incrementalDB env inc5 (Repo "base-inc" "6") ["B"]
        -- excludes the edge b->a but node b remains because it is
        -- mentioned by the edge e->b
  
      {-
      inc6:
           b---e
  
           a
            \
             d
      -}
  
      nodesAre "stacked inc 6n" inc6 ["a", "b", "d", "e"]
      edgesAre "stacked inc 6e" inc6 [("a","d"),("b","e")]
      parentsAre "stacked inc 6p" inc6 ["a", "b"]
      revEdgesAre "stacked inc 6r" inc6 [("d","a"),("e","b")]
      skipRevEdgesAre "stacked inc 6s" inc6 []
  
      -- Check ownership of the `IsParent (Node "b")` fact in the new DB
      results <- runQuery_ env inc6 $ recursive $ query $
        predicate @Glean.Test.IsParent $
          predicate @Glean.Test.Node $ rec $ field @"label" "b" end
      case results of
        [Glean.Test.IsParent id _] -> do
          ownerExpr <- factOwnership env inc6 (Fid id)
          assertBool "IsParent owner" $ case ownerExpr of
            Just (OrOwners [OrOwners [Unit b],OrOwners [Unit e]]) ->
              sort [b,e] == ["B","E"]
            _ -> False
        _ -> assertFailure "query failed"
  
  
  stackedIncrementalTest2 :: Test
  stackedIncrementalTest2 = TestCase $
    withTestEnv [] $ \env -> do
      let
        deriveAndFinish :: Env -> Repo -> IO ()
        deriveAndFinish env repo = do
          void $ completePredicates env repo (CompletePredicates_axiom def)
          derivePredicate env repo Nothing Nothing
            (parseRef "glean.test.NodePair") Nothing
          completeTestDB env repo
  
        nodesAre test db ns = do
          results <- runQuery_ env db $ query $ predicate @Glean.Test.Node wild
          print results
          assertEqual test ns
            (sort [ x | Glean.Test.Node _ (Just (
                          Glean.Test.Node_key x)) <- results ])
        pairsAre test db es = do
          edges <- runQuery_ env db $ recursive $ query $
            predicate @Glean.Test.NodePair wild
          assertEqual test es
            (sort [ (x,y) | Glean.Test.NodePair _ (Just (
                      Glean.Test.NodePair_key
                        (Glean.Test.Node _ (Just (Glean.Test.Node_key x)))
                        (Glean.Test.Node _ (Just (Glean.Test.Node_key y)))))
              <- edges ])
  
      let base = Repo "base" "0"
      kickOffTestDB env base id
      writeFactsIntoDB env base [ Glean.Test.allPredicates ] $ do
        withUnit "X" $ do
          makeFact_ @Glean.Test.Node (Glean.Test.Node_key "a")
        withUnit "Y" $ do
          makeFact_ @Glean.Test.Node (Glean.Test.Node_key "c")
      deriveAndFinish env base
  
      {-
      base:
           a{X}<--->b{Y}
      -}
  
      inc <- incrementalDB env base (Repo "base-inc" "1") ["Y"]
      writeFactsIntoDB env inc [ Glean.Test.allPredicates ] $ do
        a <- withUnit "X" $
          makeFact @Glean.Test.Node (Glean.Test.Node_key "a")
        withUnit "Z" $ do
          makeFact_ @Glean.Test.Node (Glean.Test.Node_key "c")
          makeFact_ @Glean.Test.Node (Glean.Test.Node_key "d")
        withUnit "E" $
          makeFact_ @Glean.Test.Edge (Glean.Test.Edge_key a a)
      closeDatabase env inc -- we should be able to close and re-open here
      deriveAndFinish env inc
  
      {-
      inc:
  
      c{Z}-----d{Z}
         \    /
          \  /
           a{X} <--- Edge{Z}
      -}
      nodesAre "0n" inc ["a","c","d"]
      pairsAre "0p" inc [
        ("a","c"),("a","d"),("c","a"),("c","d"),("d","a"),("d","c") ]
  
      -- check the owner of node "a"
      results <- runQuery_ env inc $ query $
        predicate @Glean.Test.Node $ rec $ field @"label" "a" end
      case results of
        [Glean.Test.Node id _] -> do
          ownerExpr <- factOwnership env inc (Fid id)
          print ownerExpr
          assertBool "owners" $
            case ownerExpr of
              -- {X} is the original owner, {{X},X,E} is the propagated
              -- ownership from the stacked DB.
              Just (OrOwners [OrOwners [Unit "X"], Unit x, Unit e]) ->
                sort [x,e] == ["E", "X"]
              _otherwise -> False
        _ -> assertFailure "query failed"
  
      inc2 <- incrementalDB env inc (Repo "base-inc" "2") ["X"]
      deriveAndFinish env inc2
      -- exclude X, but node "a" should be kept alive by Edge{Z}
      -- and hence the pairs should all still be alive
      {-
      inc2:
  
      c{Z}-----d{Z}
         \    /
          \  /
           a{X} <--- Edge{Z}
      -}
      nodesAre "1n" inc2 ["a","c","d"]
      pairsAre "1p" inc2 [
        ("a","c"),("a","d"),("c","a"),("c","d"),("d","a"),("d","c") ]
  
      -- excluding "E" should make "a" and its derived pairs go away
      inc3 <- incrementalDB env inc2 (Repo "base-inc" "3") ["E"]
      deriveAndFinish env inc3
      {-
      inc3:
  
      c{Z}-----d{Z}
      -}
      nodesAre "1n" inc3 ["c","d"]
      pairsAre "1p" inc3 [("c","d"),("d","c")]
  
  -- tickled a bug in the storage of ownership information
  dupSetTest :: Test
  dupSetTest = TestCase $
    withTestEnv [] $ \env -> do
      let base = Repo "base" "0"
      kickOffTestDB env base id
      writeFactsIntoDB env base [ Glean.Test.allPredicates ] $ do
        withUnit "A" $
          makeFact_ @Glean.Test.Node (Glean.Test.Node_key "a")
        withUnit "B" $
          makeFact_ @Glean.Test.Node (Glean.Test.Node_key "b")
      writeFactsIntoDB env base [ Glean.Test.allPredicates ] $ do
        withUnit "A" $
          makeFact_ @Glean.Test.Node (Glean.Test.Node_key "aa")
        withUnit "B" $
          makeFact_ @Glean.Test.Node (Glean.Test.Node_key "bb")
      completeTestDB env base
  
      inc <- incrementalDB env base (Repo "base-inc" "0") ["B"]
  
      results <- runQuery_ env inc $ query $
        predicate @Glean.Test.Node wild
      assertEqual "dupSetTest" 2 (length results)
  
  orphanTest :: Test
  orphanTest = TestCase $
    withTestEnv [] $ \env -> do
      let base = Repo "base" "0"
      kickOffTestDB env base id
      writeFactsIntoDB env base [ Glean.Test.allPredicates ] $ do
        d <- withUnit "D" $
          makeFact @Glean.Test.Node (Glean.Test.Node_key "d")
        c <- withUnit "C" $ do
          c <- makeFact @Glean.Test.Node (Glean.Test.Node_key "c")
          makeFact_ @Glean.Test.Edge (Glean.Test.Edge_key c d)
          return c
        b <- withUnit "B" $ do
          b <- makeFact @Glean.Test.Node (Glean.Test.Node_key "b")
          makeFact_ @Glean.Test.Edge (Glean.Test.Edge_key b d)
          return b
        -- these facts are orphans:
        a <- makeFact @Glean.Test.Node (Glean.Test.Node_key "a")
        makeFact_ @Glean.Test.Edge (Glean.Test.Edge_key a b)
        makeFact_ @Glean.Test.Edge (Glean.Test.Edge_key a c)
      completeTestDB env base
  
      -- exclude all of B, C, D
      inc <- incrementalDB env base (Repo "base-inc" "0") ["B","C","D"]
  
      -- no nodes should exist
      results <- runQuery_ env inc $ query $
        predicate @Glean.Test.Node wild
      assertEqual "orphan 0" [] results
  
      -- no edges should exist
      results <- runQuery_ env inc $ query $
        predicate @Glean.Test.Edge
          (rec $ field @"parent" (rec $ field @"label" (string "a") end) end)
      assertEqual "orphan 1" [] results
  
      -- exclude just B
      inc <- incrementalDB env base (Repo "base-inc" "1") ["B"]
  
      -- 2 nodes now
      results <- runQuery_ env inc $ query $
        predicate @Glean.Test.Node wild
      assertEqual "orphan 2" 2 (length results)
  
      -- one edge
      results <- runQuery_ env inc $ query $
        predicate @Glean.Test.Edge
          (rec $ field @"child" (rec $ field @"label" (string "d") end) end)
      assertEqual "orphan 3" 1 (length results)
  
  externalDerivationTest :: Test
  externalDerivationTest = TestList
    [ TestLabel "add fact with dependencies" $ TestCase $
      withDB $ \env repo -> do
        writeFactsIntoDB env repo [ Glean.Test.allPredicates ] $ do
          a <- makeFact @Glean.Test.Node (Glean.Test.Node_key "a")
          b <- makeFact @Glean.Test.Node (Glean.Test.Node_key "b")
          p <- makeFact @Glean.Test.StringPair (Glean.Test.StringPair_key "a" "b")
          derivedFrom [idOf (getId a), idOf (getId b)] [p]
  
        void $ completePredicates env repo $ CompletePredicates_derived $
          CompleteDerivedPredicate $
          PredicateRef "glean.test.StringPair" 6
  
        owners <- ownersOf env repo $ query $
          predicate @Glean.Test.StringPair wild
  
        assertEqual "owners"
          (show [Just $ AndOwners
            [ OrOwners [Unit "B"]
            , OrOwners [Unit "A"]
            ] ])
          (show owners)
  
    , TestLabel "multiple predicates in the same batch" $ TestCase $
      withDB $ \env repo -> do
        writeFactsIntoDB env repo [ Glean.Test.allPredicates ] $ do
          a <- makeFact @Glean.Test.Node (Glean.Test.Node_key "a")
          p <- makeFact @Glean.Test.StringPair (Glean.Test.StringPair_key "a" "a")
          derivedFrom [idOf (getId a)] [p]
  
          b <- makeFact @Glean.Test.Node (Glean.Test.Node_key "b")
          n <- makeFact @Glean.Test.Name "b"
          derivedFrom [idOf (getId b)] [n]
  
        void $ completePredicates env repo $ CompletePredicates_derived $
          CompleteDerivedPredicate $
          PredicateRef "glean.test.StringPair" 6
  
        void $ completePredicates env repo $ CompletePredicates_derived $
          CompleteDerivedPredicate $
          PredicateRef "glean.test.Name" 6
  
        owners <- ownersOf env repo $ query $
          predicate @Glean.Test.StringPair wild
        assertEqual "StringPair owners"
          (show [Just $ OrOwners [Unit "A"]])
          (show owners)
  
        owners <- ownersOf env repo $ query $
          predicate @Glean.Test.Name wild
        assertEqual "Name owners"
          (show [Just $ OrOwners [Unit "B"]])
          (show owners)
    ]
    where
    ownersOf env repo q = do
      results <- runQuery_ env repo q
      let fids = map (idOf . getId) results
      traverse (factOwnership env repo) fids
  
    withDB act =
      withTestEnv [setCompactOnCompletion] $ \env -> do
        let repo = Repo "base" "0"
        kickOffTestDB env repo id
        writeFactsIntoDB env repo [ Glean.Test.allPredicates ] $ do
          void $ withUnit "A" $
            makeFact @Glean.Test.Node (Glean.Test.Node_key "a")
          void $ withUnit "B" $
            makeFact @Glean.Test.Node (Glean.Test.Node_key "b")
        void $ completePredicates env repo $ CompletePredicates_axiom def
        act env repo
  
  deriveTest :: Test
  deriveTest = TestCase $
    withTestEnv [setCompactOnCompletion] $ \env -> do
      let base = Repo "base" "0"
      kickOffTestDB env base id
      mkGraph env base
      void $ completePredicates env base (CompletePredicates_axiom def)
  
      derivePredicate env base Nothing Nothing
        (parseRef "glean.test.RevEdge") Nothing
      derivePredicate env base Nothing Nothing
        (parseRef "glean.test.SkipRevEdge") Nothing
  
      completeTestDB env base
  
      -- check the owners of SkipRevEdge d->a
      results <- runQuery_ env base $ query $
        predicate @Glean.Test.SkipRevEdge $
          rec $ field @"child" (
            rec $ field @"label" (string "d") end
          ) end
      case results of
        [Glean.Test.SkipRevEdge id _] -> do
          ownerExpr <- factOwnership env base (Fid id)
          print ownerExpr
          assertBool "owners" $
            case ownerExpr of
              Just (OrOwners [
                  AndOwners [OrOwners [Unit a],OrOwners [Unit b]],
                  AndOwners [OrOwners [Unit c],OrOwners [Unit d]]
                ]) ->
                sort [a,b,c,d] == ["A", "A", "B", "C"]
              _otherwise -> False
        _ -> assertFailure "query failed"
  
      -- should have two RevEdges, d->b and d->c
      results <- runQuery_ env base $ query $
        predicate @Glean.Test.RevEdge
          (rec $ field @"child" (rec $ field @"label" (string "d") end) end)
      assertEqual "derived 1" 2 (length results)
  
      -- We have one SkipRevEdge fact, d->a
      results <- runQuery_ env base $ query $
        predicate @Glean.Test.SkipRevEdge wild
      assertEqual "derived 2" 1 (length results)
  
      inc <- incrementalDB env base (Repo "base-inc" "1") ["B"]
  
      -- should have pruned the RevEdge from d->b
      results <- runQuery_ env inc $ query $
        predicate @Glean.Test.RevEdge
          (rec $ field @"child" (rec $ field @"label" (string "d") end) end)
      assertEqual "derived 3" 1 (length results)
  
      -- The SkipRevEdge fact d->a should be visible if
      --    (A && B) || (A && C)
  
      -- visible if we hide B
      results <- runQuery_ env inc $ query $
        predicate @Glean.Test.SkipRevEdge wild
      assertEqual "derived 4" 1 (length results)
  
      -- invisible if we hide A
      inc <- incrementalDB env base (Repo "base-inc" "2") ["A"]
  
      results <- runQuery_ env inc $ query $
        predicate @Glean.Test.SkipRevEdge wild
      assertEqual "derived 5" 0 (length results)
  
      -- visible if we hide D
      inc <- incrementalDB env base (Repo "base-inc" "3") ["D"]
  
      results <- runQuery_ env inc $ query $
        predicate @Glean.Test.SkipRevEdge wild
      assertEqual "derived 6" 1 (length results)
  
  restartIndexing :: Test
  restartIndexing = TestCase $
    withTestEnv [] $ \env -> do
      let repo = Repo "base" "0"
      kickOffTestDB env repo id
      writeFactsIntoDB env repo [ Glean.Test.allPredicates ] $ do
        withUnit "A" $
          makeFact_ @Glean.Test.Node (Glean.Test.Node_key "a")
      closeDatabase env repo
      -- After closing and re-opening, add more facts to the "A" unit.
      -- This tests loadOwnershipUnitCounters()
      writeFactsIntoDB env repo [ Glean.Test.allPredicates ] $ do
        withUnit "A" $
          makeFact_ @Glean.Test.Node (Glean.Test.Node_key "b")
      completeTestDB env repo
  
      results <- runQuery_ env repo $ recursive $ query $
        predicate @Glean.Test.Node $ rec $ field @"label" "a" end
      case results of
        [Glean.Test.Node id _] -> do
          ownerExpr <- factOwnership env repo (Fid id)
          assertBool "Node owner" $ case ownerExpr of
            Just (OrOwners [Unit "A"]) -> True
            _ -> False
        _ -> assertFailure "query failed"
  
  main :: IO ()
  main = do
    if System.Info.arch == "x86_64"
      then main_
      else do
        putStrLn "Incremental Glean not supported on non-x86_64 (see ownership.{h/cpp})"
        exitWith (ExitFailure 1)
  
  main_ :: IO ()
  main_ = withUnitTest $ testRunner $ TestList
    [ TestLabel "incrementalTest" incrementalTest
    , TestLabel "dupSetTest" dupSetTest
    , TestLabel "orphanTest" orphanTest
    , TestLabel "deriveTest" deriveTest
    , TestLabel "externalDerivationTest" externalDerivationTest
    , TestLabel "stackedIncrementalTest" stackedIncrementalTest
    , TestLabel "stackedIncrementalTest2" stackedIncrementalTest2
    , TestLabel "restartIndexing" restartIndexing
    ]
  