hstox-0.0.1: src/testsuite/Network/Tox/DHT/KBucketsSpec.hs
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE Trustworthy #-}
module Network.Tox.DHT.KBucketsSpec where
import Test.Hspec
import Test.QuickCheck
import Control.Monad (when)
import qualified Data.Map as Map
import Data.Proxy (Proxy (..))
import Network.Tox.Crypto.Key (PublicKey)
import qualified Network.Tox.DHT.Distance as Distance
import Network.Tox.DHT.KBuckets (KBuckets)
import qualified Network.Tox.DHT.KBuckets as KBuckets
import Network.Tox.EncodingSpec
import qualified Network.Tox.NodeInfo.NodeInfo as NodeInfo
makeInputKey :: Int -> Char -> PublicKey
makeInputKey pos digit =
read $ "\"" ++ map (const '0') [0 .. pos - 1] ++ digit : map (const '0') [pos .. 63] ++ "\""
getAllBuckets :: KBuckets -> [[KBuckets.KBucketEntry]]
getAllBuckets kBuckets =
map (Map.elems . KBuckets.bucketNodes) (Map.elems (KBuckets.buckets kBuckets))
spec :: Spec
spec = do
readShowSpec (Proxy :: Proxy KBuckets)
it "has no buckets with more than bucketSize elements" $
property $ \kBuckets ->
case map length $ getAllBuckets kBuckets of
[] -> return ()
sizes -> maximum sizes `shouldSatisfy` (<= KBuckets.bucketSize kBuckets)
it "does not accept adding a NodeInfo with the baseKey as publicKey" $
property $ \kBuckets nodeInfo ->
KBuckets.addNode nodeInfo { NodeInfo.publicKey = KBuckets.baseKey kBuckets } kBuckets
`shouldBe`
kBuckets
it "adding a node to an empty k-buckets always succeeds if baseKey <> nodeKey" $
property $ \baseKey nodeInfo ->
let
empty = KBuckets.empty baseKey
kBuckets = KBuckets.addNode nodeInfo empty
in
if baseKey == NodeInfo.publicKey nodeInfo
then kBuckets `shouldBe` empty
else kBuckets `shouldNotBe` empty
it "removing a node twice has no effect" $
property $ \baseKey nodeInfo ->
let
empty = KBuckets.empty baseKey
afterAdd = KBuckets.addNode nodeInfo empty
afterRemove0 = KBuckets.removeNode (NodeInfo.publicKey nodeInfo) afterAdd
afterRemove1 = KBuckets.removeNode (NodeInfo.publicKey nodeInfo) afterRemove0
in
afterRemove0 `shouldBe` afterRemove1
it "adding a node twice has no effect" $
property $ \baseKey nodeInfo ->
let
empty = KBuckets.empty baseKey
afterAdd0 = KBuckets.addNode nodeInfo empty
afterAdd1 = KBuckets.addNode nodeInfo afterAdd0
in
afterAdd0 `shouldBe` afterAdd1
describe "KBucketEntry" $ do
it "contains the same base key as the enclosing KBuckets" $
property $ \kBuckets ->
all (KBuckets.baseKey kBuckets ==) $ concatMap (map KBuckets.entryBaseKey) $ getAllBuckets kBuckets
it "never contains a NodeInfo with the public key equal to the base key" $
property $ \kBuckets ->
notElem (KBuckets.baseKey kBuckets) $ concatMap (map $ NodeInfo.publicKey . KBuckets.entryNode) $ getAllBuckets kBuckets
describe "bucketIndex" $ do
it "returns an integer between 0 and 255 for any two non-equal keys" $
property $ \k1 k2 ->
when (k1 /= k2) $
-- In our implementation, this is guaranteed by the type system, as
-- we're using Word8, which can only represent values in this range.
KBuckets.bucketIndex k1 k2 `shouldSatisfy` \case
Nothing -> False
Just index -> index >= 0 && index <= 255
it "is undefined for two equal keys" $
property $ \k ->
KBuckets.bucketIndex k k `shouldBe` Nothing
it "returns a larger index for smaller distances and smaller index for larger distances" $
property $ \k1 k2 k3 ->
let
d = Distance.xorDistance k1
i = KBuckets.bucketIndex k1
in
if d k2 <= d k3
then i k2 >= i k3
else i k2 <= i k3
it "produces indices 0..255 for each bit set in the key" $
let
zeroKey = read "\"0000000000000000000000000000000000000000000000000000000000000000\""
inputs = zeroKey : concatMap (\pos -> map (makeInputKey pos) ['8', '4', '2', '1']) [0 .. 63]
outputs = Nothing : map Just [0 .. 255]
in
map (KBuckets.bucketIndex zeroKey) inputs `shouldBe` outputs