packages feed

net-spider-0.4.2.0: test/NetSpider/WeaverSpec.hs

{-# LANGUAGE OverloadedStrings #-}
module NetSpider.WeaverSpec (main,spec) where

import Data.Foldable (foldl')
import Data.Hashable (Hashable)
import Data.Int (Int64)
import Data.IORef (newIORef, readIORef, writeIORef, modifyIORef')
import Data.Maybe (fromJust)
import Data.Text (Text)
import Test.Hspec

import NetSpider.Found (FoundNode(..), FoundLink(..), LinkState(..))
import NetSpider.Query
  ( policyOverwrite, policyAppend,
    foundNodePolicy, unifyLinkSamples
  )
import NetSpider.Timestamp (fromEpochMillisecond)
import NetSpider.Weaver
  ( Weaver, newWeaver,
    markAsVisited, addFoundNode,
    isVisited, getFoundNodes, getSnapshot,
    visitAllBoundaryNodes,
    getBoundaryNodes
  )

import SnapshotTestCase (SnapshotTestCase(..))
import qualified SnapshotTestCase

main :: IO ()
main = hspec spec

spec :: Spec
spec = describe "Weaver" $ do
  spec_visitedNodes
  spec_boundaryNodes
  describe "SnapshotTestCase" $ do
    mapM_ makeTestCase SnapshotTestCase.basics
  
spec_visitedNodes :: Spec
spec_visitedNodes = describe "visited nodes" $ do
  specify "mark and add" $ do
    let fn = FoundNode { subjectNode = 5,
                         foundAt = fromEpochMillisecond 100,
                         neighborLinks = [],
                         nodeAttributes = ()
                       }
        w :: Weaver Int () ()
        w = addFoundNode fn $ markAsVisited 10 $ markAsVisited 5 $ newWeaver policyOverwrite
    isVisited 1 w `shouldBe` False
    isVisited 5 w `shouldBe` True
    isVisited 10 w `shouldBe` True
    getFoundNodes 1 w `shouldBe` Nothing
    getFoundNodes 5 w `shouldBe` Just [fn]
    getFoundNodes 10 w `shouldBe` Just []
  specify "add and mark" $ do
    let fn = FoundNode { subjectNode = 5,
                         foundAt = fromEpochMillisecond 100,
                         neighborLinks = [],
                         nodeAttributes = ()
                       }
        w :: Weaver Int () ()
        w = markAsVisited 10 $ markAsVisited 5 $ addFoundNode fn $ newWeaver policyOverwrite
    isVisited 1 w `shouldBe` False
    isVisited 5 w `shouldBe` True
    isVisited 10 w `shouldBe` True
    getFoundNodes 1 w `shouldBe` Nothing
    getFoundNodes 5 w `shouldBe` Just [fn]
    getFoundNodes 10 w `shouldBe` Just []
  describe "FoundNodePolicy for Weaver" $ do
    let fn1 = FoundNode { subjectNode = 5,
                          foundAt = fromEpochMillisecond 100,
                          neighborLinks = [],
                          nodeAttributes = "foobar"
                        }
        fn2 = FoundNode { subjectNode = 5,
                          foundAt = fromEpochMillisecond 150,
                          neighborLinks = [],
                          nodeAttributes = "quux"
                        }
    specify "policyOverwrite should keep only the latest FoundNode" $ do
      let w :: Weaver Int String ()
          w = newWeaver policyOverwrite
      (getFoundNodes 5 $ addFoundNode fn1 w)
        `shouldBe` Just [fn1]
      (getFoundNodes 5 $ addFoundNode fn2 w)
        `shouldBe` Just [fn2]
      (getFoundNodes 5 $ addFoundNode fn1 $ addFoundNode fn2 w)
        `shouldBe` Just [fn2]
      (getFoundNodes 5 $ addFoundNode fn2 $ addFoundNode fn1 w)
        `shouldBe` Just [fn2]
    specify "policyAppend should keep all FoundNodes" $ do
      let w :: Weaver Int String ()
          w = newWeaver policyAppend
      (getFoundNodes 5 $ addFoundNode fn1 w)
        `shouldBe` Just [fn1]
      (getFoundNodes 5 $ addFoundNode fn2 w)
        `shouldBe` Just [fn2]
      (fromJust $ getFoundNodes 5 $ addFoundNode fn1 $ addFoundNode fn2 w)
        `shouldMatchList` [fn1, fn2]
      (fromJust $ getFoundNodes 5 $ addFoundNode fn2 $ addFoundNode fn1 w)
        `shouldMatchList` [fn1, fn2]

shouldReturnSet :: (HasCallStack, Eq a, Show a) => IO [a] -> [a] -> IO ()
shouldReturnSet action expected = do
  got <- action
  got `shouldMatchList` expected

spec_boundaryNodes :: Spec
spec_boundaryNodes = describe "boundary nodes" $ do
  let addFN ref_w fn = do
        new_w <- fmap (addFoundNode fn) $ readIORef ref_w
        writeIORef ref_w new_w
      getBN ref_w = fmap getBoundaryNodes $ readIORef ref_w
      makeFN :: Text -> [Text] -> Int64 -> FoundNode Text () ()
      makeFN sub_n tar_ns time =
        FoundNode { subjectNode = sub_n,
                    foundAt = fromEpochMillisecond time,
                    nodeAttributes = (),
                    neighborLinks = map makeFL tar_ns
                  }
      makeFL tar_n = FoundLink { targetNode = tar_n, linkState = LinkToTarget, linkAttributes = () }
      markV ref_w n = modifyIORef' ref_w $ markAsVisited n
  specify "policyOverwrite" $ do
    rweaver <- newIORef $ newWeaver policyOverwrite
    let addFN' = addFN rweaver
        getBN' = getBN rweaver
        markV' = markV rweaver
    getBN' `shouldReturnSet` []
    addFN' (makeFN "n1" [] 100)
    getBN' `shouldReturnSet` []
    addFN' (makeFN "n1" ["n2"] 200)
    getBN' `shouldReturnSet` ["n2"]
    addFN' (makeFN "n2" ["n3", "n4", "n5", "n1"] 250)
    getBN' `shouldReturnSet` ["n3", "n4", "n5"]
    addFN' (makeFN "n3" ["n4"] 200)
    getBN' `shouldReturnSet` ["n4", "n5"] -- should return unique node IDs
    
    addFN' (makeFN "n5" ["n1", "n6", "n7", "n8", "n2"] 200)
    getBN' `shouldReturnSet` ["n4", "n6", "n7", "n8"]
    addFN' (makeFN "n5" ["n4", "n9"] 250)
    getBN' `shouldReturnSet` ["n4", "n9"] -- overwrites the previous FoundNode
    addFN' (makeFN "n5" ["n3", "n7"] 220)
    getBN' `shouldReturnSet` ["n4", "n9"] -- get ignored.

    markV' "n5"
    getBN' `shouldReturnSet` ["n4", "n9"]
    markV' "n9"
    getBN' `shouldReturnSet` ["n4"]
  specify "policyAppend" $ do
    rweaver <- newIORef $ newWeaver policyAppend
    let addFN' = addFN rweaver
        getBN' = getBN rweaver
        markV' = markV rweaver
    getBN' `shouldReturnSet` []
    addFN' (makeFN "n1" ["n2"] 200)
    getBN' `shouldReturnSet` ["n2"]
    addFN' (makeFN "n1" ["n3"] 100)
    getBN' `shouldReturnSet` ["n2", "n3"]    
    addFN' (makeFN "n1" ["n4"] 300)
    getBN' `shouldReturnSet` ["n2", "n3", "n4"]

    addFN' (makeFN "n3" ["n5"] 200)
    getBN' `shouldReturnSet` ["n2", "n4", "n5"]
    markV' "n2"
    getBN' `shouldReturnSet` ["n4", "n5"]
  
  

addAllFoundNodes :: (Eq n, Hashable n) => [FoundNode n na la] -> Weaver n na la -> Weaver n na la
addAllFoundNodes fns weaver = foldl' (\w fn -> addFoundNode fn w) weaver fns

makeTestCase :: SnapshotTestCase -> Spec
makeTestCase SnapshotTestCase { caseName = name, caseInput = input, caseQuery = query, caseAssert = assert } = do
  specify name $ do
    let init_w = newWeaver $ foundNodePolicy query
        got_w = visitAllBoundaryNodes  -- SnapshotTestCase assumes unlimited number of traverse steps. So, mark all nodes as visited.
                $ addAllFoundNodes input init_w
        got_graph = getSnapshot (unifyLinkSamples query) got_w
    assert got_graph