Skip to content

Tests: Increase variation in generated tree shapes #442

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 17 commits into from
Apr 27, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 17 additions & 36 deletions .github/workflows/haskell-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@
#
# For more information, see https://github.com/haskell-CI/haskell-ci
#
# version: 0.14.1
# version: 0.14.3
#
# REGENDATA ("0.14.1",["github","unordered-containers.cabal"])
# REGENDATA ("0.14.3",["github","unordered-containers.cabal"])
#
name: Haskell-CI
on:
Expand Down Expand Up @@ -50,44 +50,34 @@ jobs:
- compiler: ghc-8.8.4
compilerKind: ghc
compilerVersion: 8.8.4
setup-method: hvr-ppa
setup-method: ghcup
allow-failure: false
- compiler: ghc-8.6.5
compilerKind: ghc
compilerVersion: 8.6.5
setup-method: hvr-ppa
setup-method: ghcup
allow-failure: false
- compiler: ghc-8.4.4
compilerKind: ghc
compilerVersion: 8.4.4
setup-method: hvr-ppa
setup-method: ghcup
allow-failure: false
- compiler: ghc-8.2.2
compilerKind: ghc
compilerVersion: 8.2.2
setup-method: hvr-ppa
setup-method: ghcup
allow-failure: false
fail-fast: false
steps:
- name: apt
run: |
apt-get update
apt-get install -y --no-install-recommends gnupg ca-certificates dirmngr curl git software-properties-common libtinfo5
if [ "${{ matrix.setup-method }}" = ghcup ]; then
mkdir -p "$HOME/.ghcup/bin"
curl -sL https://downloads.haskell.org/ghcup/0.1.17.3/x86_64-linux-ghcup-0.1.17.3 > "$HOME/.ghcup/bin/ghcup"
chmod a+x "$HOME/.ghcup/bin/ghcup"
"$HOME/.ghcup/bin/ghcup" install ghc "$HCVER"
"$HOME/.ghcup/bin/ghcup" install cabal 3.6.2.0
else
apt-add-repository -y 'ppa:hvr/ghc'
apt-get update
apt-get install -y "$HCNAME"
mkdir -p "$HOME/.ghcup/bin"
curl -sL https://downloads.haskell.org/ghcup/0.1.17.3/x86_64-linux-ghcup-0.1.17.3 > "$HOME/.ghcup/bin/ghcup"
chmod a+x "$HOME/.ghcup/bin/ghcup"
"$HOME/.ghcup/bin/ghcup" install cabal 3.6.2.0
fi
apt-get install -y --no-install-recommends gnupg ca-certificates dirmngr curl git software-properties-common libtinfo5 libnuma-dev
mkdir -p "$HOME/.ghcup/bin"
curl -sL https://downloads.haskell.org/ghcup/0.1.17.5/x86_64-linux-ghcup-0.1.17.5 > "$HOME/.ghcup/bin/ghcup"
chmod a+x "$HOME/.ghcup/bin/ghcup"
"$HOME/.ghcup/bin/ghcup" install ghc "$HCVER"
"$HOME/.ghcup/bin/ghcup" install cabal 3.6.2.0
env:
HCKIND: ${{ matrix.compilerKind }}
HCNAME: ${{ matrix.compiler }}
Expand All @@ -99,20 +89,11 @@ jobs:
echo "CABAL_DIR=$HOME/.cabal" >> "$GITHUB_ENV"
echo "CABAL_CONFIG=$HOME/.cabal/config" >> "$GITHUB_ENV"
HCDIR=/opt/$HCKIND/$HCVER
if [ "${{ matrix.setup-method }}" = ghcup ]; then
HC=$HOME/.ghcup/bin/$HCKIND-$HCVER
echo "HC=$HC" >> "$GITHUB_ENV"
echo "HCPKG=$HOME/.ghcup/bin/$HCKIND-pkg-$HCVER" >> "$GITHUB_ENV"
echo "HADDOCK=$HOME/.ghcup/bin/haddock-$HCVER" >> "$GITHUB_ENV"
echo "CABAL=$HOME/.ghcup/bin/cabal-3.6.2.0 -vnormal+nowrap" >> "$GITHUB_ENV"
else
HC=$HCDIR/bin/$HCKIND
echo "HC=$HC" >> "$GITHUB_ENV"
echo "HCPKG=$HCDIR/bin/$HCKIND-pkg" >> "$GITHUB_ENV"
echo "HADDOCK=$HCDIR/bin/haddock" >> "$GITHUB_ENV"
echo "CABAL=$HOME/.ghcup/bin/cabal-3.6.2.0 -vnormal+nowrap" >> "$GITHUB_ENV"
fi

HC=$HOME/.ghcup/bin/$HCKIND-$HCVER
echo "HC=$HC" >> "$GITHUB_ENV"
echo "HCPKG=$HOME/.ghcup/bin/$HCKIND-pkg-$HCVER" >> "$GITHUB_ENV"
echo "HADDOCK=$HOME/.ghcup/bin/haddock-$HCVER" >> "$GITHUB_ENV"
echo "CABAL=$HOME/.ghcup/bin/cabal-3.6.2.0 -vnormal+nowrap" >> "$GITHUB_ENV"
HCNUMVER=$(${HC} --numeric-version|perl -ne '/^(\d+)\.(\d+)\.(\d+)(\.(\d+))?$/; print(10000 * $1 + 100 * $2 + ($3 == 0 ? $5 != 1 : $3))')
echo "HCNUMVER=$HCNUMVER" >> "$GITHUB_ENV"
echo "ARG_TESTS=--enable-tests" >> "$GITHUB_ENV"
Expand Down
3 changes: 3 additions & 0 deletions cabal.haskell-ci
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,6 @@ constraint-set debug

installed: -containers
installed: -binary

-- Avoid HVR's PPA due to outage on 2022-04-27
ghcup-jobs: True
22 changes: 9 additions & 13 deletions tests/Properties/HashMapLazy.hs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{-# LANGUAGE CPP #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE TypeApplications #-}
{-# OPTIONS_GHC -fno-warn-orphans #-} -- because of Arbitrary (HashMap k v)

-- | Tests for the 'Data.HashMap.Lazy' module. We test functions by
Expand All @@ -26,6 +27,7 @@ import Test.QuickCheck.Function (Fun, apply)
import Test.QuickCheck.Poly (A, B)
import Test.Tasty (TestTree, testGroup)
import Test.Tasty.QuickCheck (testProperty)
import Util.Key (Key, keyToInt)

import qualified Data.Foldable as Foldable
import qualified Data.List as List
Expand All @@ -40,15 +42,9 @@ import qualified Data.HashMap.Lazy as HM
import qualified Data.Map.Lazy as M
#endif

-- Key type that generates more hash collisions.
newtype Key = K { unK :: Int }
deriving (Arbitrary, Eq, Ord, Read, Show, Num)

instance Hashable Key where
hashWithSalt salt k = hashWithSalt salt (unK k) `mod` 20

instance (Eq k, Hashable k, Arbitrary k, Arbitrary v) => Arbitrary (HashMap k v) where
arbitrary = fmap (HM.fromList) arbitrary
arbitrary = HM.fromList <$> arbitrary
shrink = fmap HM.fromList . shrink . HM.toList

------------------------------------------------------------------------
-- * Properties
Expand Down Expand Up @@ -284,7 +280,7 @@ pUnionWithKey xs ys = M.unionWithKey go (M.fromList xs) `eq_`
HM.unionWithKey go (HM.fromList xs) $ ys
where
go :: Key -> Int -> Int -> Int
go (K k) i1 i2 = k - i1 + i2
go k i1 i2 = keyToInt k - i1 + i2

pUnions :: [[(Key, Int)]] -> Property
pUnions xss = M.toAscList (M.unions (map M.fromList xss)) ===
Expand Down Expand Up @@ -332,7 +328,7 @@ pIntersectionWithKey xs ys = M.intersectionWithKey go (M.fromList xs) `eq_`
HM.intersectionWithKey go (HM.fromList xs) $ ys
where
go :: Key -> Int -> Int -> Int
go (K k) i1 i2 = k - i1 - i2
go k i1 i2 = keyToInt k - i1 - i2

------------------------------------------------------------------------
-- ** Folds
Expand Down Expand Up @@ -394,7 +390,7 @@ pFoldr' = (List.sort . M.foldr' (:) []) `eq` (List.sort . HM.foldr' (:) [])

pMapMaybeWithKey :: [(Key, Int)] -> Property
pMapMaybeWithKey = M.mapMaybeWithKey f `eq_` HM.mapMaybeWithKey f
where f k v = guard (odd (unK k + v)) >> Just (v + 1)
where f k v = guard (odd (keyToInt k + v)) >> Just (v + 1)

pMapMaybe :: [(Key, Int)] -> Property
pMapMaybe = M.mapMaybe f `eq_` HM.mapMaybe f
Expand All @@ -405,7 +401,7 @@ pFilter = M.filter odd `eq_` HM.filter odd

pFilterWithKey :: [(Key, Int)] -> Property
pFilterWithKey = M.filterWithKey p `eq_` HM.filterWithKey p
where p k v = odd (unK k + v)
where p k v = odd (keyToInt k + v)

------------------------------------------------------------------------
-- ** Conversions
Expand Down Expand Up @@ -433,7 +429,7 @@ pFromListWith kvs = (M.toAscList $ M.fromListWith Op kvsM) ===
pFromListWithKey :: [(Key, Int)] -> Property
pFromListWithKey kvs = (M.toAscList $ M.fromListWithKey combine kvsM) ===
(toAscList $ HM.fromListWithKey combine kvsM)
where kvsM = fmap (\(K k,v) -> (Leaf k, Leaf v)) kvs
where kvsM = fmap (\(k,v) -> (Leaf (keyToInt k), Leaf v)) kvs
combine k v1 v2 = Op k (Op v1 v2)

pToList :: [(Key, Int)] -> Property
Expand Down
19 changes: 6 additions & 13 deletions tests/Properties/HashSet.hs
Original file line number Diff line number Diff line change
@@ -1,29 +1,20 @@
{-# LANGUAGE CPP #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}

-- | Tests for the 'Data.HashSet' module. We test functions by
-- comparing them to @Set@ from @containers@.

module Properties.HashSet (tests) where

import Data.Hashable (Hashable (hashWithSalt))
import Data.Ord (comparing)
import Test.QuickCheck (Arbitrary, Property, property, (===), (==>))
import Test.QuickCheck (Property, property, (===), (==>))
import Test.Tasty (TestTree, testGroup)
import Test.Tasty.QuickCheck (testProperty)
import Util.Key (Key, incKey, keyToInt)

import qualified Data.Foldable as Foldable
import qualified Data.HashSet as S
import qualified Data.List as List
import qualified Data.Set as Set

-- Key type that generates more hash collisions.
newtype Key = K { unK :: Int }
deriving (Arbitrary, Enum, Eq, Integral, Num, Ord, Read, Show, Real)

instance Hashable Key where
hashWithSalt salt k = hashWithSalt salt (unK k) `mod` 20

------------------------------------------------------------------------
-- * Properties

Expand Down Expand Up @@ -128,7 +119,7 @@ pUnion xs ys = Set.union (Set.fromList xs) `eq_`
-- ** Transformations

pMap :: [Key] -> Property
pMap = Set.map (+ 1) `eq_` S.map (+ 1)
pMap = Set.map incKey `eq_` S.map incKey

------------------------------------------------------------------------
-- ** Folds
Expand All @@ -150,7 +141,9 @@ foldl'Set = Set.foldl'
-- ** Filter

pFilter :: [Key] -> Property
pFilter = Set.filter odd `eq_` S.filter odd
pFilter = Set.filter p `eq_` S.filter p
where
p = odd . keyToInt

------------------------------------------------------------------------
-- ** Conversions
Expand Down
35 changes: 10 additions & 25 deletions tests/Strictness.hs
Original file line number Diff line number Diff line change
@@ -1,42 +1,27 @@
{-# LANGUAGE CPP #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
{-# OPTIONS_GHC -fno-warn-orphans #-} -- because of Arbitrary (HashMap k v)

module Strictness (tests) where

import Control.Arrow (second)
import Control.Monad (guard)
import Data.Foldable (foldl')
import Data.Hashable (Hashable (hashWithSalt))
import Data.Hashable (Hashable)
import Data.HashMap.Strict (HashMap)
import Data.Maybe (fromMaybe, isJust)
import Test.ChasingBottoms.IsBottom
import Test.QuickCheck (Arbitrary (arbitrary), Property, (.&&.),
(===))
import Test.QuickCheck (Arbitrary (..), Property, (.&&.), (===))
import Test.QuickCheck.Function
import Test.QuickCheck.Poly (A)
import Test.Tasty (TestTree, testGroup)
import Test.Tasty.QuickCheck (testProperty)
import Text.Show.Functions ()
import Util.Key (Key)

import qualified Data.HashMap.Strict as HM

-- Key type that generates more hash collisions.
newtype Key = K { unK :: Int }
deriving (Arbitrary, Eq, Ord, Show)

instance Hashable Key where
hashWithSalt salt k = hashWithSalt salt (unK k) `mod` 20

instance (Arbitrary k, Arbitrary v, Eq k, Hashable k) =>
Arbitrary (HashMap k v) where
arbitrary = HM.fromList `fmap` arbitrary

instance Show (Int -> Int) where
show _ = "<function>"

instance Show (Int -> Int -> Int) where
show _ = "<function>"
instance (Eq k, Hashable k, Arbitrary k, Arbitrary v) => Arbitrary (HashMap k v) where
arbitrary = HM.fromList <$> arbitrary
shrink = fmap HM.fromList . shrink . HM.toList

------------------------------------------------------------------------
-- * Properties
Expand Down Expand Up @@ -84,8 +69,8 @@ pInsertWithValueStrict f k v m
pFromListKeyStrict :: Bool
pFromListKeyStrict = isBottom $ HM.fromList [(undefined :: Key, 1 :: Int)]

pFromListValueStrict :: Bool
pFromListValueStrict = isBottom $ HM.fromList [(K 1, undefined)]
pFromListValueStrict :: Key -> Bool
pFromListValueStrict k = isBottom $ HM.fromList [(k, undefined)]

pFromListWithKeyStrict :: (Int -> Int -> Int) -> Bool
pFromListWithKeyStrict f =
Expand Down
68 changes: 68 additions & 0 deletions tests/Util/Key.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE TypeApplications #-}

module Util.Key (Key(..), keyToInt, incKey, collisionAtHash) where

import Data.Bits (bit, (.&.))
import Data.Hashable (Hashable (hashWithSalt))
import Data.Word (Word16)
import GHC.Generics (Generic)
import Test.QuickCheck (Arbitrary (..), Gen, Large)

import qualified Test.QuickCheck as QC

-- Key type that generates more hash collisions.
data Key = K
{ hash :: !Int
-- ^ The hash of the key
, _x :: !SmallSum
-- ^ Additional data, so we can have collisions for any hash
} deriving (Eq, Ord, Read, Show, Generic)

instance Hashable Key where
hashWithSalt _ (K h _) = h

data SmallSum = A | B | C | D
deriving (Eq, Ord, Read, Show, Generic, Enum, Bounded)

instance Arbitrary SmallSum where
arbitrary = QC.arbitraryBoundedEnum
shrink = shrinkSmallSum

shrinkSmallSum :: SmallSum -> [SmallSum]
shrinkSmallSum A = []
shrinkSmallSum B = [A]
shrinkSmallSum C = [A, B]
shrinkSmallSum D = [A, B, C]

instance Arbitrary Key where
arbitrary = K <$> arbitraryHash <*> arbitrary
shrink = QC.genericShrink

arbitraryHash :: Gen Int
arbitraryHash = do
let gens =
[ (2, (fromIntegral . QC.getLarge) <$> arbitrary @(Large Word16))
, (1, QC.getSmall <$> arbitrary)
, (1, QC.getLarge <$> arbitrary)
]
i <- QC.frequency gens
moreCollisions' <- QC.elements [moreCollisions, id]
pure (moreCollisions' i)

-- | Mask out most bits to produce more collisions
moreCollisions :: Int -> Int
moreCollisions w = fromIntegral (w .&. mask)

mask :: Int
mask = sum [bit n | n <- [0, 3, 8, 14, 61]]

keyToInt :: Key -> Int
keyToInt (K h x) = h * fromEnum x

incKey :: Key -> Key
incKey (K h x) = K (h + 1) x

-- | 4 colliding keys at a given hash.
collisionAtHash :: Int -> (Key, Key, Key, Key)
collisionAtHash h = (K h A, K h B, K h C, K h D)
1 change: 1 addition & 0 deletions unordered-containers.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ test-suite unordered-containers-tests
Properties.HashSet
Properties.List
Strictness
Util.Key

build-depends:
base,
Expand Down