guardian-0.4.0.0: test/Development/Guardian/GraphSpec.hs
{-# LANGUAGE OverloadedLists #-}
{-# LANGUAGE OverloadedStrings #-}
{-# OPTIONS_GHC -Wno-type-defaults #-}
module Development.Guardian.GraphSpec where
import qualified Algebra.Graph as G
import qualified Algebra.Graph.ToGraph as AM
import qualified Data.HashMap.Strict as HM
import Data.List.NonEmpty (NonEmpty ((:|)))
import qualified Data.List.NonEmpty as NE
import qualified Data.Map.Strict as Map
import Development.Guardian.Graph
import Development.Guardian.Types
import qualified GHC.OldList as OL
import Test.Tasty
import Test.Tasty.HUnit
import Validation
{- |
@
A1--+ +--B1
| | |
| v v
| A2 --> B2
v |
A3 +-->B3
| |
| |
+---> C1 <----+
@
-}
packages1 :: PackageGraph
packages1 = G.edges [(a1, a3), (a1, a2), (a2, b2), (b1, b2), (b2, b3), (a3, c1), (b3, c1)]
where
(a1, a2, a3) = ("A1", "A2", "A3")
(b1, b2, b3) = ("B1", "B2", "B3")
c1 = "C1"
{- |
@ A B
+-------+ +-------+
| A1 |<-+----B1 |
| A2-+--+->B2 |
| A3 | | B3 |
+---+---+ +---+---+
| |
v C v
+---+----------+----+
| C1 |
| |
+-------------------+
@
-}
domains1 :: Domains
domains1 =
Domains $
HM.fromList
[ ("A", Domain {dependsOn = Just ["C"], packages = [a1, a2, a3]})
, ("B", Domain {dependsOn = Just ["C"], packages = [b1, b2, b3]})
, ("C", Domain {dependsOn = Nothing, packages = [c1]})
]
where
a1 = PackageDef {packageName = "A1", extraDeps = []}
a2 = PackageDef {packageName = "A2", extraDeps = [PackageDep "B2"]}
a3 = PackageDef {packageName = "A3", extraDeps = []}
b1 = PackageDef {packageName = "B1", extraDeps = []}
b2 = PackageDef {packageName = "B2", extraDeps = [DomainDep "A"]}
b3 = PackageDef {packageName = "B3", extraDeps = []}
c1 = PackageDef {packageName = "C1", extraDeps = []}
test_buildDomainInfo :: TestTree
test_buildDomainInfo =
testGroup
"buildDomainInfo"
[ testCase "domains1 is valid" $ do
let vinfo = buildDomainInfo domains1
isSuccess vinfo @? "Domains #1 should be valid, but got: " <> show vinfo
]
test_detectPackageCycle :: TestTree
test_detectPackageCycle =
testGroup
"detectPackageCycle"
[ testCase "validates packages1" $
detectPackageCycle packages1 @?= Success ()
, testCase "invalidates (packages1 + A2 -> A1)" $
detectPackageCycle (packages1 `G.overlay` G.edge "A2" "A1")
@?= Failure (CyclicPackageDep ("A2" :| ["A1"]))
]
test_validatePackageGraph :: TestTree
test_validatePackageGraph =
testGroup
"validatePackageGraph"
[ testCase "packages1 is valid, with B2 -> (A) redundant" $ do
doms <- fromDomainInfo $ buildDomainInfo domains1
case validatePackageGraph doms packages1 of
f@Failure {} -> assertFailure $ "Must success, but got: " <> show f
Success (OkWithDiagnostics Diagnostics {usedExceptionalRules = useds, redundantExtraDeps = reds})
| [("B2", [DomainDep "A"])] <- Map.toList reds
, [("A2", [PackageDep "B2"])] <- useds ->
pure ()
s@Success {} -> assertFailure $ "B2 -> (A) must be reported redundant, but got: " <> show s
, testCase "(packages1 + A2 -> B1) is invalid" $ do
doms <- fromDomainInfo $ buildDomainInfo domains1
let pg = packages1 `G.overlay` G.edge "A2" "B1"
case validatePackageGraph doms pg of
Failure
( DomainBoundaryViolation
{ fromDom = "A"
, toDom = "B"
, introducedBy = [("A2", "B1")]
}
:| []
) -> pure ()
f ->
assertFailure $
"Must fail reporting A2 -> B1 as invalid, but got: "
<> show f
, testCase ("detects package cycle in " <> show (AM.toAdjacencyMap $ packages1 `G.overlay` G.edge "A2" "A1")) $ do
doms <- fromDomainInfo $ buildDomainInfo domains1
let pg = packages1 `G.overlay` G.edge "A2" "A1"
case validatePackageGraph doms pg of
Failure (CyclicPackageDep cyc :| [])
| cyc `OL.elem` ["A2" :| ["A1"], "A1" :| ["A2"]] -> pure ()
f ->
assertFailure $
"Must fail reporting A2 -> A1 -> A2 as valid, but got: "
<> show f
]
fromDomainInfo :: Validation (NonEmpty DomainGraphError) DomainInfo -> IO DomainInfo
fromDomainInfo =
validation
(assertFailure . ("Invalid Domain: " <>) . show . NE.toList)
pure