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