Skip to content

Commit f8463ca

Browse files
committed
Add tests to size invariant
1 parent f344e35 commit f8463ca

File tree

2 files changed

+117
-0
lines changed

2 files changed

+117
-0
lines changed

tests/Size.hs

+100
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
{-# LANGUAGE CPP, GeneralizedNewtypeDeriving #-}
2+
3+
-- | Tests for size field invariant in 'HashMap' wrapper introduced in GitHub
4+
-- PR #170.
5+
6+
module Main (main) where
7+
8+
import Control.Monad.ST (ST)
9+
#if defined(STRICT)
10+
import qualified Data.HashMap.Strict as HM
11+
#else
12+
import qualified Data.HashMap.Lazy as HM
13+
#endif
14+
import Data.List (scanl')
15+
16+
import Test.QuickCheck (Arbitrary, Property, conjoin, oneof)
17+
import Test.Framework (Test, defaultMain, testGroup)
18+
19+
-- Key type that generates more hash collisions.
20+
newtype Key = K { unK :: Int }
21+
deriving (Arbitrary, Eq, Ord, Read, Show)
22+
23+
instance Hashable Key where
24+
hashWithSalt salt k = hashWithSalt salt (unK k) `mod` 20
25+
26+
-- Datatype representing the actions that can potentially change a hashmap's
27+
-- size modulo repetition i.e. 'mapMaybe' and 'filter' are essentially
28+
-- equivalent in how they modify a hashmap, so only one ('filter') is tested.
29+
data HashMapAction =
30+
| Insert Key Int
31+
| Delete Key
32+
| Union (HM.HashMap Key Int)
33+
| Intersection (HM.HashMap Key Int)
34+
| Difference (HM.HashMap Key Int)
35+
| Filter (Int -> Bool)
36+
37+
instance Arbitrary HashMapAction where
38+
arbitrary = oneof
39+
[ Insert <$> arbitrary <*> arbitrary
40+
, Delete <$> arbitrary
41+
, Union <$> arbitrary
42+
, Intersection <$> arbitrary
43+
, Difference <$> arbitrary
44+
, pure $ Filter even
45+
]
46+
47+
-- Simple way of representing a hashmap and its size without having to
48+
-- use 'size', which is the function to be tested. As such, its use is
49+
-- avoided and the 'Int' field of the tuple is used instead.
50+
type HashMapSt = (Int, HM.HashMap Key Int)
51+
52+
-- | Applies a 'HashMapAction' to 'HashMapSt', updating the hashmap's
53+
-- size after the operation.
54+
applyActionToState :: HashMapSt -> HashMapAction -> HashMapSt
55+
applyActionToState p@(sz, hm) (Insert k v)
56+
| HM.member k hm = (sz + 1, HM.insert k v hm)
57+
| otherwise = p
58+
applyActionToState p@(sz, hm) (Delete k)
59+
| HM.member k hm = p
60+
Nothing -> p
61+
| otherwise = (sz - 1, HM.delete k hm)
62+
applyActionToState (sz, hm) (Union hm') =
63+
let sz' = length $ HM.toList hm'
64+
lenIntersect = length [ k | k <- HM.keys hm, HM.member k hm' ]
65+
newLen = sz + sz' - lenIntersect
66+
in (newLen, HM.union hm hm')
67+
applyActionToState (sz, hm) (Intersection hm') =
68+
let lenIntersect = length [ k | k <- HM.keys hm, HM.member k hm' ]
69+
in (lenIntersect, HM.intersect hm hm')
70+
applyActionToState (sz, hm) (Difference hm')=
71+
let lenDiff = length [ k | k <- HM.keys hm, not $ HM.member k hm' ]
72+
(lenDiff, HM.difference hm hm')
73+
applyActionToState (sz, hm) (Filter pred) =
74+
let lenFilter = length [ (k, v) | (k, v) <- HM.elems hm, pred v ]
75+
in (lenFilter, HM.filter pred hm)
76+
77+
-- | Property to check that after each operation that may change a hashmap's
78+
-- size, the 'Int' field in the 'HashMap' wrapper always correctly represents
79+
-- the hashmap's size.
80+
sizeInvariantProperty :: [HashMapAction] -> Property
81+
sizeInvariantProperty actionList =
82+
conjoin .
83+
map (\(sz, hm) -> sz == HM.size hm) .
84+
scanl' applyActionToState (0, mempty) $ actionList
85+
86+
------------------------------------------------------------------------
87+
-- * Test list
88+
89+
tests :: [Test]
90+
tests = [
91+
testGroup "size invariant checks"
92+
[ sizeInvariantProperty
93+
]
94+
]
95+
96+
------------------------------------------------------------------------
97+
-- * Test harness
98+
99+
main :: IO ()
100+
main = defaultMain tests

unordered-containers.cabal

+17
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,23 @@ test-suite hashmap-strict-properties
8585
ghc-options: -Wall
8686
cpp-options: -DASSERTS -DSTRICT
8787

88+
test-suite hashmap-size-invariant
89+
hs-source-dirs: tests
90+
main-is: Size.hs
91+
type: exitcode-stdio-1.0
92+
93+
build-depends:
94+
base,
95+
containers >= 0.4,
96+
hashable >= 1.0.1.1,
97+
QuickCheck >= 2.4.0.1,
98+
test-framework >= 0.3.3,
99+
test-framework-quickcheck2 >= 0.2.9,
100+
unordered-containers
101+
102+
ghc-options: -Wall
103+
cpp-options: -DASSERTS -DSTRICT
104+
88105
test-suite hashset-properties
89106
hs-source-dirs: tests
90107
main-is: HashSetProperties.hs

0 commit comments

Comments
 (0)