1-- tests for transaction merging
4import ProjectM36.Attribute
5import ProjectM36.Relation
6import ProjectM36.Transaction
7import ProjectM36.TransactionGraph
10import qualified ProjectM36.DisconnectedTransaction as Discon
11import qualified ProjectM36.DatabaseContext as DBC
12import ProjectM36.RelationalExpression
14import qualified Data.ByteString.Lazy as BS
17import qualified Data.UUID as U
18import qualified Data.Set as S
19import qualified Data.Map as M
22import Data.Time.Calendar
24import Control.Monad.State
26{-# ANN module ("HLint: ignore Reduce duplication" :: String) #-}
29testTime = UTCTime (fromGregorian 1980 01 01) (secondsToDiffTime 1000)
33 tcounts <- runTestTT testList
34 if errors tcounts + failures tcounts > 0 then exitFailure else exitSuccess
38 testSubGraphToFirstAncestorBasic,
39 testSubGraphToFirstAncestorSnipBranch,
40 testSubGraphToFirstAncestorMoreTransactions,
41 testSelectedBranchMerge,
42 testUnionMergeStrategy,
43 testUnionPreferMergeStrategy,
44 testUnionMergeIncDepViolation
47-- | Create a transaction graph with two branches and no changes between them.
54createTrans :: TransactionId -> TransactionInfo -> DatabaseContext -> Transaction
55createTrans tid info ctx = Transaction tid info (Schemas ctx M.empty)
57basicTransactionGraph :: IO TransactionGraph
58basicTransactionGraph = do
59 let bsGraph = bootstrapTransactionGraph testTime uuidRoot DBC.basicDatabaseContext
60 rootTrans = fromMaybe (error "bonk") (transactionForHead "master" bsGraph)
64 rootContext = concreteDatabaseContext rootTrans
65 (_, bsGraph') <- addTransaction "branchA" (createTrans uuidA (TransactionInfo uuidRoot S.empty testTime) rootContext) bsGraph
66 (_, bsGraph'') <- addTransaction "branchB" (createTrans uuidB (TransactionInfo uuidRoot S.empty testTime) rootContext) bsGraph'
69addTransaction :: HeadName -> Transaction -> TransactionGraph -> IO (Transaction, TransactionGraph)
70addTransaction headName transaction graph = case addTransactionToGraph headName transaction graph of
71 Left err -> assertFailure (show err) >> error ""
72 Right (t,g) -> pure (t,g)
74fakeUUID :: Word8 -> TransactionId
75fakeUUID x = fromMaybe (error "impossible uuid") (U.fromByteString (BS.concat (replicate 4 w32)))
76 where w32 = BS.pack (replicate 4 x)
78assertEither :: (Show a) => Either a b -> IO b
79assertEither x = case x of
80 Left err -> assertFailure (show err) >> undefined
83assertGraph :: TransactionGraph -> IO ()
84assertGraph graph = case validateGraph graph of
86 Just errs -> assertFailure (show errs)
88assertMaybe :: Maybe a -> String -> IO a
89assertMaybe x msg = case x of
90 Nothing -> assertFailure msg >> undefined
93-- | Test that a subgraph of the basic graph is equal to the original graph (nothing to filter).
94testSubGraphToFirstAncestorBasic :: Test
95testSubGraphToFirstAncestorBasic = TestCase $ do
96 graph <- basicTransactionGraph
98 transA <- assertMaybe (transactionForHead "branchA" graph) "failed to get branchA"
99 transB <- assertMaybe (transactionForHead "branchB" graph) "failed to get branchB"
100 subgraph <- assertEither $ subGraphOfFirstCommonAncestor graph (transactionHeadsForGraph graph) transA transB S.empty
101 let graphEq graphArg = S.map transactionId (transactionsForGraph graphArg)
102 assertEqual "no graph changes" (graphEq subgraph) (graphEq graph)
104-- | Test that a branch anchored at the root transaction is removed when using the first ancestor function.
105testSubGraphToFirstAncestorSnipBranch :: Test
106testSubGraphToFirstAncestorSnipBranch = TestCase $ do
107 baseGraph <- basicTransactionGraph
108 transA <- assertMaybe (transactionForHead "branchA" baseGraph) "failed to get branchA"
109 transB <- assertMaybe (transactionForHead "branchB" baseGraph) "failed to get branchB"
110 (_, graph) <- addTransaction "branchC" (createTrans (fakeUUID 12) (TransactionInfo (fakeUUID 1) S.empty testTime) (concreteDatabaseContext transA)) baseGraph
111 subgraph <- assertEither $ subGraphOfFirstCommonAncestor graph (transactionHeadsForGraph baseGraph) transA transB S.empty
113 let graphEq graphArg = S.map transactionId (transactionsForGraph graphArg)
114 assertEqual "failed to snip branch" (graphEq baseGraph) (graphEq subgraph)
116-- | Test that the subgraph function recurses properly through multiple parent transactions.
117testSubGraphToFirstAncestorMoreTransactions :: Test
118testSubGraphToFirstAncestorMoreTransactions = TestCase $ do
119 graph <- basicTransactionGraph
122-- add another relvar to branchB
123 branchBTrans <- assertMaybe (transactionForHead "branchB" graph) "failed to get branchB head"
124 (updatedBranchBContext,_,_) <- case runState (evalDatabaseContextExpr (Assign "branchBOnlyRelvar" (ExistingRelation relationTrue))) (freshDatabaseState (concreteDatabaseContext branchBTrans)) of
125 (Left err, _) -> assertFailure (show err) >> undefined
126 (Right (), context) -> pure context
127 (_, graph') <- addTransaction "branchB" (createTrans (fakeUUID 3) (TransactionInfo (transactionId branchBTrans) S.empty testTime) updatedBranchBContext) graph
128 branchBTrans' <- assertMaybe (transactionForHead "branchB" graph') "failed to get branchB head"
129 assertEqual "branchB id 3" (fakeUUID 3) (transactionId branchBTrans')
131 -- add another transaction to branchA
132 branchATrans <- assertMaybe (transactionForHead "branchA" graph) "failed to get branchA head"
133 (_, graph'') <- addTransaction "branchA" (createTrans (fakeUUID 4) (TransactionInfo (transactionId branchATrans) S.empty testTime) (concreteDatabaseContext branchATrans)) graph'
134 branchATrans' <- assertMaybe (transactionForHead "branchA" graph'') "failed to get branchA head"
135 assertEqual "branchA id 4" (fakeUUID 4) (transactionId branchATrans')
138 let subGraphHeads = M.filter (\t -> transactionId t `elem` [fakeUUID 3, fakeUUID 4]) (transactionHeadsForGraph graph'')
139 subgraph <- assertEither $ subGraphOfFirstCommonAncestor graph'' subGraphHeads branchATrans' branchBTrans' S.empty
140 --verify that the subgraph includes both the heads and the common ancestor
141 let expectedTransSet = S.fromList (map fakeUUID [1,3,4])
142 assertBool "validate transactions in subgraph" (S.isProperSubsetOf expectedTransSet (S.map transactionId (transactionsForGraph subgraph)))
144testSelectedBranchMerge :: Test
145testSelectedBranchMerge = TestCase $ do
146 graph <- basicTransactionGraph
149 -- add another relvar to branchB
150 branchBTrans <- assertMaybe (transactionForHead "branchB" graph) "failed to get branchB head"
151 (updatedBranchBContext,_,_) <- case runState (evalDatabaseContextExpr (Assign "branchBOnlyRelvar" (ExistingRelation relationTrue))) (freshDatabaseState (concreteDatabaseContext branchBTrans)) of
152 (Left err, _) -> assertFailure (show err) >> undefined
153 (Right (), context) -> pure context
154 (_, graph') <- addTransaction "branchB" (createTrans (fakeUUID 3) (TransactionInfo (transactionId branchBTrans) S.empty testTime) updatedBranchBContext) graph
155 --create the merge transaction in the graph
156 let eGraph' = mergeTransactions testTime (fakeUUID 4) (fakeUUID 10) (SelectedBranchMergeStrategy "branchA") ("branchA", "branchB") graph'
158 (_, graph'') <- assertEither eGraph'
161 --validate that the branchB remains
162 branchBTrans' <- assertMaybe (transactionForHead "branchB" graph'') "failed to find branchB head"
163 assertEqual "head of merged transaction was removed" (transactionId branchBTrans') (fakeUUID 3)
165 --validate that the branchB relvar does *not* appear in the merge because branchA was selected
166 mergeTrans <- assertEither (transactionForId (fakeUUID 4) graph'')
167 assertBool "branchOnlyRelvar is present in merge" (M.notMember "branchBOnlyRelvar" (relationVariables (concreteDatabaseContext mergeTrans)))
169-- try various individual component conflicts and check for resolution
170testUnionPreferMergeStrategy :: Test
171testUnionPreferMergeStrategy = TestCase $ do
172 -- create a graph with a relvar conflict
173 graph <- basicTransactionGraph
174 branchATrans <- assertMaybe (transactionForHead "branchA" graph) "branchATrans"
175 branchBTrans <- assertMaybe (transactionForHead "branchB" graph) "branchBTrans"
176 branchBRelVar <- assertEither $ mkRelationFromList (attributesFromList [Attribute "conflict" IntAtomType]) []
177 let branchAContext = (concreteDatabaseContext branchATrans) {relationVariables = M.insert conflictRelVarName branchARelVar (relationVariables (concreteDatabaseContext branchATrans))}
178 branchARelVar = relationTrue
179 branchBContext = (concreteDatabaseContext branchBTrans) {relationVariables = M.insert conflictRelVarName branchBRelVar (relationVariables (concreteDatabaseContext branchBTrans))}
180 conflictRelVarName = "conflictRelVar"
182 (_, graph') <- addTransaction "branchA" (createTrans (fakeUUID 3) (TransactionInfo (transactionId branchATrans) S.empty testTime) branchAContext) graph
183 (_, graph'') <- addTransaction "branchB" (createTrans (fakeUUID 4) (TransactionInfo (transactionId branchBTrans) S.empty testTime) branchBContext) graph'
184 -- validate that the conflict is hidden because we preferred a branch
185 let merged = mergeTransactions testTime (fakeUUID 5) (fakeUUID 3) (UnionPreferMergeStrategy "branchB") ("branchA", "branchB") graph''
187 Left err -> assertFailure ("expected merge success: " ++ show err)
189 assertEqual "branchB relvar preferred in conflict" (Just branchBRelVar) (M.lookup conflictRelVarName (relationVariables (Discon.concreteDatabaseContext discon)))
191-- try various individual component conflicts and check for merge failure
192testUnionMergeStrategy :: Test
193testUnionMergeStrategy = TestCase $ do
194 graph <- basicTransactionGraph
197 branchBTrans <- assertMaybe (transactionForHead "branchB" graph) "failed to get branchB head"
198 branchATrans <- assertMaybe (transactionForHead "branchA" graph) "failed to get branchA head"
199 -- add another relvar to branchB - branchBOnlyRelvar should appear in the merge
200 -- add inclusion dependency in branchA
202 let updatedBranchBContext = (concreteDatabaseContext branchBTrans) {relationVariables = M.insert branchBOnlyRelVarName branchBOnlyRelVar (relationVariables (concreteDatabaseContext branchBTrans)) }
203 updatedBranchAContext = (concreteDatabaseContext branchATrans) {inclusionDependencies = M.insert branchAOnlyIncDepName branchAOnlyIncDep (inclusionDependencies (concreteDatabaseContext branchATrans)) }
204 branchBOnlyRelVar = relationTrue
205 branchAOnlyIncDepName = "branchAOnlyIncDep"
206 branchBOnlyRelVarName = "branchBOnlyRelVar"
207 branchAOnlyIncDep = InclusionDependency (ExistingRelation relationTrue) (ExistingRelation relationTrue)
208 (_, graph') <- addTransaction "branchB" (createTrans (fakeUUID 3) (TransactionInfo (transactionId branchBTrans) S.empty testTime) updatedBranchBContext) graph
209 (discon, _) <- assertEither $ mergeTransactions testTime (fakeUUID 5) (fakeUUID 10) UnionMergeStrategy ("branchA", "branchB") graph'
210 assertEqual "branchBOnlyRelVar should appear in the merge" (M.lookup branchBOnlyRelVarName (relationVariables (Discon.concreteDatabaseContext discon))) (Just relationTrue)
211 (_, graph'') <- addTransaction "branchA" (createTrans (fakeUUID 4) (TransactionInfo (transactionId branchATrans) S.empty testTime) updatedBranchAContext) graph'
212 let eMergeGraph = mergeTransactions testTime (fakeUUID 5) (fakeUUID 3) UnionMergeStrategy ("branchA", "branchB") graph''
214 Left err -> assertFailure ("expected merge success: " ++ show err)
215 Right (_, mergeGraph) -> do
216 -- check that the new merge transaction has the correct parents
217 mergeTrans <- assertEither $ transactionForId (fakeUUID 5) mergeGraph
218 let mergeContext = concreteDatabaseContext mergeTrans
219 assertEqual "merge transaction parents" (transactionParentIds mergeTrans) (S.fromList [fakeUUID 3, fakeUUID 4])
220 -- check that the new merge tranasction has elements from both A and B branches
221 assertEqual "merge transaction relvars" (Just relationTrue) (M.lookup branchBOnlyRelVarName (relationVariables mergeContext))
222 assertEqual "merge transaction incdeps" (Just branchAOnlyIncDep) (M.lookup branchAOnlyIncDepName (inclusionDependencies mergeContext))
223 -- test an expected conflict- add branchBOnlyRelVar with same name but different attributes
224 conflictRelVar <- assertEither $ mkRelationFromList (attributesFromList [Attribute "conflict" IntAtomType]) []
225 let conflictContextA = updatedBranchAContext {relationVariables = M.insert branchBOnlyRelVarName conflictRelVar (relationVariables updatedBranchAContext) }
226 conflictBranchATrans <- assertMaybe (transactionForHead "branchA" graph'') "retrieving head transaction for expected conflict"
227 (_, graph''') <- addTransaction "branchA" (createTrans (fakeUUID 6) (TransactionInfo (transactionId conflictBranchATrans) S.empty testTime) conflictContextA) graph''
228 let failingMerge = mergeTransactions testTime (fakeUUID 5) (fakeUUID 3) UnionMergeStrategy ("branchA", "branchB") graph'''
230 Right _ -> assertFailure "expected merge failure"
231 Left err -> assertEqual "merge failure" err (MergeTransactionError StrategyViolatesRelationVariableMergeError)
233-- test that a merge will fail if a constraint is violated
234testUnionMergeIncDepViolation :: Test
235testUnionMergeIncDepViolation = TestCase $ do
236 graph <- basicTransactionGraph
239 branchBTrans <- assertMaybe (transactionForHead "branchB" graph) "failed to get branchB head"
240 branchATrans <- assertMaybe (transactionForHead "branchA" graph) "failed to get branchA head"
242 --add relvar and key constraint to both branches
243 let eRel val = mkRelationFromList (attributesFromList [Attribute "x" IntAtomType, Attribute "y" IntAtomType]) [[IntAtom 1, IntAtom val]]
245 Right branchArv = eRel 2
246 Right branchBrv = eRel 3
247 branchAContext = (concreteDatabaseContext branchBTrans) {relationVariables = M.singleton rvName branchArv}
248 branchBContext = (concreteDatabaseContext branchBTrans) {relationVariables = M.singleton rvName branchBrv,
249 inclusionDependencies = M.singleton incDepName incDep}
251 incDep = inclusionDependencyForKey (AttributeNames (S.singleton "x")) (RelationVariable "x" ())
254 --add the rv in new commits to both branches
255 (_, graph') <- addTransaction "branchB" (createTrans (fakeUUID 3) (TransactionInfo (transactionId branchBTrans) S.empty testTime) branchBContext) graph
257 (_, graph'') <- addTransaction "branchA" (createTrans (fakeUUID 4) (TransactionInfo (transactionId branchATrans) S.empty testTime) branchAContext) graph'
259 --check that the union merge fails due to a violated constraint
260 let eMerge = mergeTransactions testTime (fakeUUID 5) (fakeUUID 3) UnionMergeStrategy ("branchA", "branchB") graph''
262 Left (InclusionDependencyCheckError incDepName') -> assertEqual "incdep violation name" incDepName incDepName'
263 Left err -> assertFailure ("other error: " ++ show err)
264 Right _ -> assertFailure "constraint violation missing"
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list