1-- tests for transaction merging
4import ProjectM36.Attribute
5import ProjectM36.Relation
6import ProjectM36.Transaction
7import ProjectM36.TransactionGraph
9import qualified Data.ByteString.Lazy as BS
12import ProjectM36.RelationalExpression
13import qualified Data.UUID as U
14import qualified Data.Set as S
15import qualified Data.Map as M
16import qualified ProjectM36.DatabaseContext as DBC
17import Control.Monad.State hiding (join)
21 tcounts <- runTestTT testList
22 if errors tcounts + failures tcounts > 0 then exitFailure else exitSuccess
26 testSubGraphToFirstAncestorBasic,
27 testSubGraphToFirstAncestorSnipBranch,
28 testSubGraphToFirstAncestorMoreTransactions,
29 testSelectedBranchMerge,
30 testUnionMergeStrategy,
31 testUnionPreferMergeStrategy
34-- | Create a transaction graph with two branches and no changes between them.
40basicTransactionGraph :: IO TransactionGraph
41basicTransactionGraph = do
42 let bsGraph = bootstrapTransactionGraph uuidRoot DBC.empty --dateExamples
43 rootTrans = case transactionForHead "master" bsGraph of
45 Nothing -> error "bonk"
49 rootContext = transactionContext rootTrans
50 (_, bsGraph') <- addTransaction "branchA" (Transaction uuidA (TransactionInfo uuidRoot S.empty) rootContext) bsGraph
51 (_, bsGraph'') <- addTransaction "branchB" (Transaction uuidB (TransactionInfo uuidRoot S.empty) rootContext) bsGraph'
54addTransaction :: HeadName -> Transaction -> TransactionGraph -> IO (Transaction, TransactionGraph)
55addTransaction headName transaction graph = case addTransactionToGraph headName transaction graph of
56 Left err -> assertFailure (show err) >> error ""
57 Right (t,g) -> pure (t,g)
59fakeUUID :: Word8 -> TransactionId
60fakeUUID x = case U.fromByteString (BS.concat (replicate 4 w32)) of
61 Nothing -> error "impossible uuid"
63 where w32 = BS.pack (replicate 4 x)
65assertEither :: (Show a) => Either a b -> IO b
66assertEither x = case x of
67 Left err -> assertFailure (show err) >> undefined
70assertGraph :: TransactionGraph -> IO ()
71assertGraph graph = case validateGraph graph of
73 Just errs -> assertFailure (show errs)
75assertMaybe :: Maybe a -> String -> IO a
76assertMaybe x msg = case x of
77 Nothing -> assertFailure msg >> undefined
80-- | Test that a subgraph of the basic graph is equal to the original graph (nothing to filter).
81testSubGraphToFirstAncestorBasic :: Test
82testSubGraphToFirstAncestorBasic = TestCase $ do
83 graph <- basicTransactionGraph
85 transA <- assertMaybe (transactionForHead "branchA" graph) "failed to get branchA"
86 transB <- assertMaybe (transactionForHead "branchB" graph) "failed to get branchB"
87 subgraph <- assertEither $ subGraphOfFirstCommonAncestor graph (transactionHeadsForGraph graph) transA transB S.empty
88 assertEqual "no graph changes" subgraph graph
90-- | Test that a branch anchored at the root transaction is removed when using the first ancestor function.
91testSubGraphToFirstAncestorSnipBranch :: Test
92testSubGraphToFirstAncestorSnipBranch = TestCase $ do
93 baseGraph <- basicTransactionGraph
94 transA <- assertMaybe (transactionForHead "branchA" baseGraph) "failed to get branchA"
95 transB <- assertMaybe (transactionForHead "branchB" baseGraph) "failed to get branchB"
96 (_, graph) <- addTransaction "branchC" (Transaction (fakeUUID 12) (TransactionInfo (fakeUUID 1) S.empty) (transactionContext transA)) baseGraph
97 subgraph <- assertEither $ subGraphOfFirstCommonAncestor graph (transactionHeadsForGraph baseGraph) transA transB S.empty
99 assertEqual "failed to snip branch" baseGraph subgraph
101-- | Test that the subgraph function recurses properly through multiple parent transactions.
102testSubGraphToFirstAncestorMoreTransactions :: Test
103testSubGraphToFirstAncestorMoreTransactions = TestCase $ do
104 graph <- basicTransactionGraph
107 -- add another relvar to branchB
108 branchBTrans <- assertMaybe (transactionForHead "branchB" graph) "failed to get branchB head"
109 updatedBranchBContext <- case runState (evalContextExpr (Assign "branchBOnlyRelvar" (ExistingRelation relationTrue))) (transactionContext branchBTrans) of
110 (Just err, _) -> assertFailure (show err) >> undefined
111 (Nothing, context) -> pure context
112 (_, graph') <- addTransaction "branchB" (Transaction (fakeUUID 3) (TransactionInfo (transactionId branchBTrans) S.empty) updatedBranchBContext) graph
113 branchBTrans' <- assertMaybe (transactionForHead "branchB" graph') "failed to get branchB head"
114 assertEqual "branchB id 3" (fakeUUID 3) (transactionId branchBTrans')
116 -- add another transaction to branchA
117 branchATrans <- assertMaybe (transactionForHead "branchA" graph) "failed to get branchA head"
118 (_, graph'') <- addTransaction "branchA" (Transaction (fakeUUID 4) (TransactionInfo (transactionId branchATrans) S.empty) (transactionContext branchATrans)) graph'
119 branchATrans' <- assertMaybe (transactionForHead "branchA" graph'') "failed to get branchA head"
120 assertEqual "branchA id 4" (fakeUUID 4) (transactionId branchATrans')
123 let subGraphHeads = M.filter (\t -> elem (transactionId t) [fakeUUID 3, fakeUUID 4]) (transactionHeadsForGraph graph'')
124 subgraph <- assertEither $ subGraphOfFirstCommonAncestor graph'' subGraphHeads branchATrans' branchBTrans' S.empty
125 --verify that the subgraph includes both the heads and the common ancestor
126 let expectedTransSet = S.fromList (map fakeUUID [1,3,4])
127 assertBool "validate transactions in subgraph" (S.isProperSubsetOf expectedTransSet (S.map transactionId (transactionsForGraph subgraph)))
129testSelectedBranchMerge :: Test
130testSelectedBranchMerge = TestCase $ do
131 graph <- basicTransactionGraph
134 -- add another relvar to branchB
135 branchBTrans <- assertMaybe (transactionForHead "branchB" graph) "failed to get branchB head"
136 updatedBranchBContext <- case runState (evalContextExpr (Assign "branchBOnlyRelvar" (ExistingRelation relationTrue))) (transactionContext branchBTrans) of
137 (Just err, _) -> assertFailure (show err) >> undefined
138 (Nothing, context) -> pure context
139 (_, graph') <- addTransaction "branchB" (Transaction (fakeUUID 3) (TransactionInfo (transactionId branchBTrans) S.empty) updatedBranchBContext) graph
140 --create the merge transaction in the graph
141 let eGraph' = mergeTransactions (fakeUUID 4) (fakeUUID 10) (SelectedBranchMergeStrategy "branchA") ("branchA", "branchB") graph'
143 (_, graph'') <- assertEither eGraph'
146 --validate that the branchB remains
147 branchBTrans' <- assertMaybe (transactionForHead "branchB" graph'') "failed to find branchB head"
148 assertEqual "head of merged transaction was removed" (transactionId branchBTrans') (fakeUUID 3)
150 --validate that the branchB relvar does *not* appear in the merge because branchA was selected
151 mergeTrans <- assertEither (transactionForId (fakeUUID 4) graph'')
152 assertBool "branchOnlyRelvar is present in merge" (M.notMember "branchBOnlyRelvar" (relationVariables (transactionContext mergeTrans)))
154-- try various individual component conflicts and check for resolution
155testUnionPreferMergeStrategy :: Test
156testUnionPreferMergeStrategy = TestCase $ do
157 -- create a graph with a relvar conflict
158 graph <- basicTransactionGraph
159 branchATrans <- assertMaybe (transactionForHead "branchA" graph) "branchATrans"
160 branchBTrans <- assertMaybe (transactionForHead "branchB" graph) "branchBTrans"
161 branchBRelVar <- assertEither $ mkRelationFromList (attributesFromList [Attribute "conflict" IntAtomType]) []
162 let branchAContext = (transactionContext branchATrans) {relationVariables = M.insert conflictRelVarName branchARelVar (relationVariables (transactionContext branchATrans))}
163 branchARelVar = relationTrue
164 branchBContext = (transactionContext branchBTrans) {relationVariables = M.insert conflictRelVarName branchBRelVar (relationVariables (transactionContext branchBTrans))}
165 conflictRelVarName = "conflictRelVar"
167 (_, graph') <- addTransaction "branchA" (Transaction (fakeUUID 3) (TransactionInfo (transactionId branchATrans) S.empty) branchAContext) graph
168 (_, graph'') <- addTransaction "branchB" (Transaction (fakeUUID 4) (TransactionInfo (transactionId branchBTrans) S.empty) branchBContext) graph'
169 -- validate that the conflict is hidden because we preferred a branch
170 let merged = mergeTransactions (fakeUUID 5) (fakeUUID 3) (UnionPreferMergeStrategy "branchB") ("branchA", "branchB") graph''
172 Left err -> assertFailure ("expected merge success: " ++ show err)
173 Right (DisconnectedTransaction _ mergeContext, _) -> do
174 assertEqual "branchB relvar preferred in conflict" (Just branchBRelVar) (M.lookup conflictRelVarName (relationVariables mergeContext))
176-- try various individual component conflicts and check for merge failure
177testUnionMergeStrategy :: Test
178testUnionMergeStrategy = TestCase $ do
179 graph <- basicTransactionGraph
182 branchBTrans <- assertMaybe (transactionForHead "branchB" graph) "failed to get branchB head"
183 branchATrans <- assertMaybe (transactionForHead "branchA" graph) "failed to get branchA head"
184 -- add another relvar to branchB - branchBOnlyRelvar should appear in the merge
185 -- add inclusion dependency in branchA
187 let updatedBranchBContext = (transactionContext branchBTrans) {relationVariables = M.insert branchBOnlyRelVarName branchBOnlyRelVar (relationVariables (transactionContext branchBTrans)) }
188 updatedBranchAContext = (transactionContext branchATrans) {inclusionDependencies = M.insert branchAOnlyIncDepName branchAOnlyIncDep (inclusionDependencies (transactionContext branchATrans)) }
189 branchBOnlyRelVar = relationTrue
190 branchAOnlyIncDepName = "branchAOnlyIncDep"
191 branchBOnlyRelVarName = "branchBOnlyRelVar"
192 branchAOnlyIncDep = InclusionDependency (ExistingRelation relationTrue) (ExistingRelation relationTrue)
193 (_, graph') <- addTransaction "branchB" (Transaction (fakeUUID 3) (TransactionInfo (transactionId branchBTrans) S.empty) updatedBranchBContext) graph
194 ((DisconnectedTransaction _ mergeContext1), _) <- assertEither $ mergeTransactions (fakeUUID 5) (fakeUUID 10) UnionMergeStrategy ("branchA", "branchB") graph'
195 assertEqual "branchBOnlyRelVar should appear in the merge" (M.lookup branchBOnlyRelVarName (relationVariables mergeContext1)) (Just relationTrue)
196 (_, graph'') <- addTransaction "branchA" (Transaction (fakeUUID 4) (TransactionInfo (transactionId branchATrans) S.empty) updatedBranchAContext) graph'
197 let eMergeGraph = mergeTransactions (fakeUUID 5) (fakeUUID 3) UnionMergeStrategy ("branchA", "branchB") graph''
199 Left err -> assertFailure ("expected merge success: " ++ show err)
200 Right (_, mergeGraph) -> do
201 -- check that the new merge transaction has the correct parents
202 mergeTrans <- assertEither $ transactionForId (fakeUUID 5) mergeGraph
203 let mergeContext = transactionContext mergeTrans
204 assertEqual "merge transaction parents" (transactionParentIds mergeTrans) (S.fromList [fakeUUID 3, fakeUUID 4])
205 -- check that the new merge tranasction has elements from both A and B branches
206 assertEqual "merge transaction relvars" (Just relationTrue) (M.lookup branchBOnlyRelVarName (relationVariables mergeContext))
207 assertEqual "merge transaction incdeps" (Just branchAOnlyIncDep) (M.lookup branchAOnlyIncDepName (inclusionDependencies mergeContext))
208 -- test an expected conflict- add branchBOnlyRelVar with same name but different attributes
209 conflictRelVar <- assertEither $ mkRelationFromList (attributesFromList [Attribute "conflict" IntAtomType]) []
210 let conflictContextA = updatedBranchAContext {relationVariables = M.insert branchBOnlyRelVarName conflictRelVar (relationVariables updatedBranchAContext) }
211 conflictBranchATrans <- assertMaybe (transactionForHead "branchA" graph'') "retrieving head transaction for expected conflict"
212 (_, graph''') <- addTransaction "branchA" (Transaction (fakeUUID 6) (TransactionInfo (transactionId conflictBranchATrans) S.empty) conflictContextA) graph''
213 let failingMerge = mergeTransactions (fakeUUID 5) (fakeUUID 3) UnionMergeStrategy ("branchA", "branchB") graph'''
215 Right _ -> assertFailure "expected merge failure"
216 Left err -> assertEqual "merge failure" err (MergeTransactionError StrategyViolatesRelationVariableMergeError)
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