b9-1.1.0: src/tests/B9/RepositorySpec.hs
module B9.RepositorySpec
( spec,
filterRepoImagesMapReturnsAllAndOnlyImagesSatisfieingTheFilterPredicate,
filterRepoImagesMapReturnsAllAndOnlyReposSatisfieingTheFilterPredicate,
allSharedImagesWithRepoReturnsAllNonEmptyRepos,
allSharedImagesWithRepoReturnsOnlyReposContainedInTheParameter,
allSharedImagesWithRepoReturnsAllImages,
allSharedImagesWithRepoReturnsOnlyPairsSuchThatTheImageIsContainedInTheRepository,
maxSharedImageOfAllReposReturnsNonNothingIfInputHasImages,
maxSharedImageOfAllReposReturnsTheMaximumImage,
maxSharedImageOfAllReposReturnsAValidRepoImagePair,
)
where
import B9.DiskImages
import B9.Repository
import Data.Foldable (any)
import qualified Data.Map as Map
import Data.Maybe
import qualified Data.Set as Set
import Test.Hspec
import Test.QuickCheck
spec :: HasCallStack => Spec
spec =
describe
"Repository"
( do
describe
"dropAllButNLatestSharedImages"
( do
it
"returns a set that is disjunct with the result of keepNLatestSharedImages"
( property
( \(Positive n) sharedImages ->
let dropped = dropAllButNLatestSharedImages n sharedImages
kept = keepNLatestSharedImages n sharedImages
in dropped `Set.disjoint` kept
)
)
it
"returns a set with at most n * number of unique names entries, for all n >= 0"
( property
( \(Positive n) sharedImages ->
let actual = keepNLatestSharedImages n sharedImages
noUniqueNames =
Set.size (Set.map sharedImageName sharedImages)
in Set.size actual <= n * noUniqueNames
)
)
)
describe
"keepNLatestSharedImages"
( do
it
"returns a set with a plausible number of elements"
( property
( \n sharedImages ->
let actual = keepNLatestSharedImages n sharedImages
noUniqueNames =
Set.size (Set.map sharedImageName sharedImages)
in label
"number of output elements < (max 0 n) * number of unqiue image names"
(Set.size actual <= max 0 n * noUniqueNames)
.&&. classify
(n > 0)
"n > 0"
( n > 0
==> label
"the output contains as many sharedImageNames as the input"
(Set.size (Set.map sharedImageName actual) === noUniqueNames)
.&&. label
"keepNLatestSharedImages with n == 1 returns exactly as many elements as there are unique names"
(Set.size (keepNLatestSharedImages 1 sharedImages) === noUniqueNames)
.&&. label
"the newest entry in the output is the newest entry in the input"
(Set.lookupMax actual === Set.lookupMax sharedImages)
)
)
)
it
"returns the input unaltered if n >= number of input images"
( property
( \(Positive n) sharedImages ->
let actual = keepNLatestSharedImages n sharedImages
in n >= Set.size sharedImages ==> sharedImages === actual
)
)
it
"returns a set with at most n * number of unique names entries, for all n >= 0"
( property
( \(Positive n) sharedImages ->
let actual = keepNLatestSharedImages n sharedImages
noUniqueNames =
Set.size (Set.map sharedImageName sharedImages)
in Set.size actual <= n * noUniqueNames
)
)
)
describe
"filterRepoImagesMap"
( do
it
"returns all- and only repos matching the repo predicate"
(property (withMaxSuccess 20 filterRepoImagesMapReturnsAllAndOnlyReposSatisfieingTheFilterPredicate))
it
"returns all- and only images matching the image predicate"
(property (withMaxSuccess 20 filterRepoImagesMapReturnsAllAndOnlyImagesSatisfieingTheFilterPredicate))
it "is idempotent" $
property
( withMaxSuccess
20
( \(Fun _ repoPred) (Fun _ p) repoImgMap ->
let expected = filterRepoImagesMap repoPred p repoImgMap
actual = filterRepoImagesMap repoPred p expected
in expected === actual
)
)
)
describe
"lookupCachedImages"
( it
"returns only shared images that are cached"
(property lookupCachedImagesReturnsOnlyImagesFromCache)
)
describe
"allSharedImagesWithRepo"
( do
it
"returns all repositories that are not empty"
(property allSharedImagesWithRepoReturnsAllNonEmptyRepos)
it
"returns only repositories that are in the input"
(property allSharedImagesWithRepoReturnsOnlyReposContainedInTheParameter)
it
"returns all images"
(property allSharedImagesWithRepoReturnsAllImages)
it
"returns only pairs where the image is in the repository"
(property allSharedImagesWithRepoReturnsOnlyPairsSuchThatTheImageIsContainedInTheRepository)
)
describe
"maxSharedImageOfAllRepos"
( do
it
"returns a non-Nothing value of the input has any images and Nothing otherwise"
(property maxSharedImageOfAllReposReturnsNonNothingIfInputHasImages)
it
"returns the maximum of all images"
(property maxSharedImageOfAllReposReturnsTheMaximumImage)
it
"returns a pair where the image is in the repository"
(property maxSharedImageOfAllReposReturnsAValidRepoImagePair)
)
)
matchesSomeButNotAll :: Foldable t => (a -> Bool) -> t a -> Bool
matchesSomeButNotAll p xs = any p xs && any (not . p) xs
filterRepoImagesMapReturnsAllAndOnlyImagesSatisfieingTheFilterPredicate ::
Fun SharedImage Bool -> RepoImagesMap -> Property
filterRepoImagesMapReturnsAllAndOnlyImagesSatisfieingTheFilterPredicate (Fun _ p) t =
let i' = runCodeUnderTest p
i'Complement = runCodeUnderTest (not . p)
i = allSharedImages t
runCodeUnderTest = allSharedImages . flip (filterRepoImagesMap (const True)) t
in classify
(not (p `any` i))
"predicate matches no image"
(null i' && i'Complement == i)
.||. classify
(p `matchesSomeButNotAll` i)
"predicate matches some images"
( p `all` i'
&& not (p `any` i'Complement)
&& Set.union i' i'Complement == i
&& null (Set.intersection i' i'Complement)
)
.||. classify
(p `all` i)
"predicate matches all images"
(null i'Complement && i' == i)
filterRepoImagesMapReturnsAllAndOnlyReposSatisfieingTheFilterPredicate ::
Fun Repository Bool -> RepoImagesMap -> Property
filterRepoImagesMapReturnsAllAndOnlyReposSatisfieingTheFilterPredicate (Fun _ p) t =
let i' = runCodeUnderTest p
i'Complement = runCodeUnderTest (not . p)
i = allRepositories t
runCodeUnderTest = allRepositories . flip (flip filterRepoImagesMap (const True)) t
in classify
(not (p `any` i))
"predicate matches no repo"
(null i' && i'Complement == i)
.||. classify
(p `matchesSomeButNotAll` i)
"predicate matches some repos"
( p `all` i'
&& not (p `any` i'Complement)
&& Set.union i' i'Complement == i
&& null (Set.intersection i' i'Complement)
)
.||. classify
(p `all` i)
"predicate matches all repos"
(null i'Complement && i' == i)
lookupCachedImagesReturnsOnlyImagesFromCache :: SharedImageName -> RepoImagesMap -> Property
lookupCachedImagesReturnsOnlyImagesFromCache sn table =
let result = lookupCachedImages sn table
in Set.intersection
result
(fromMaybe (Set.empty) (Map.lookup Cache table))
=== result
allSharedImagesWithRepoReturnsAllNonEmptyRepos :: RepoImagesMap -> Property
allSharedImagesWithRepoReturnsAllNonEmptyRepos t =
let nonEmptyRepos =
Map.foldrWithKey
(\repo imgs acc -> if null imgs then acc else Set.insert repo acc)
Set.empty
t
reposReturned =
Set.map snd (allSharedImagesWithRepo t)
in reposReturned === nonEmptyRepos
allSharedImagesWithRepoReturnsOnlyReposContainedInTheParameter ::
RepoImagesMap -> Bool
allSharedImagesWithRepoReturnsOnlyReposContainedInTheParameter t =
let allReposReturned = Set.map snd (allSharedImagesWithRepo t)
in allReposReturned `Set.isSubsetOf` allRepositories t
allSharedImagesWithRepoReturnsAllImages :: RepoImagesMap -> Property
allSharedImagesWithRepoReturnsAllImages t =
let allImagesReturned = Set.map fst (allSharedImagesWithRepo t)
in allImagesReturned === allSharedImages t
allSharedImagesWithRepoReturnsOnlyPairsSuchThatTheImageIsContainedInTheRepository ::
RepoImagesMap -> Bool
allSharedImagesWithRepoReturnsOnlyPairsSuchThatTheImageIsContainedInTheRepository t =
let validPair (i, r) =
maybe False (Set.member i) (Map.lookup r t)
in all validPair (allSharedImagesWithRepo t)
maxSharedImageOfAllReposReturnsNonNothingIfInputHasImages :: RepoImagesMap -> Property
maxSharedImageOfAllReposReturnsNonNothingIfInputHasImages t =
isJust (maxSharedImageOfAllRepos t) =/= null (allSharedImages t)
maxSharedImageOfAllReposReturnsTheMaximumImage :: RepoImagesMap -> Property
maxSharedImageOfAllReposReturnsTheMaximumImage t =
not (null (allSharedImages t))
==> fmap fst (maxSharedImageOfAllRepos t) === Just (maximum (allSharedImages t))
maxSharedImageOfAllReposReturnsAValidRepoImagePair :: RepoImagesMap -> Property
maxSharedImageOfAllReposReturnsAValidRepoImagePair t =
case maxSharedImageOfAllRepos t of
Just (i, r) ->
label
"got result"
(Just i === (Map.lookup r t >>= Set.lookupMax))
Nothing ->
label "got no result" True